Edit Custom Fields for New Product Editor (#45396)
* Create edit modal * Add update function to the useCustomFields hook * Integrate the EditModal in the CustomFields component * Add edition and validation logic to the custom field EditModal component * Fix text control validation error styles * Focus the name field when its invalid * Fix linter error * Fix edit modal min width and controls width * Add changelog file * Fix text overflow in custom fields table * Remove non needed block style file
This commit is contained in:
parent
1e7b8c4004
commit
277fa6b005
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Edit Custom Fields for New Product Editor
|
|
@ -21,7 +21,5 @@
|
||||||
"inserter": false,
|
"inserter": false,
|
||||||
"lock": false,
|
"lock": false,
|
||||||
"__experimentalToolbar": false
|
"__experimentalToolbar": false
|
||||||
},
|
}
|
||||||
"usesContext": [ "postType" ],
|
|
||||||
"editorStyle": "file:./editor.css"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
.wp-block-woocommerce-product-custom-fields-field {
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
@import "product-fields/attributes/editor.scss";
|
@import "product-fields/attributes/editor.scss";
|
||||||
@import "product-fields/description/editor.scss";
|
@import "product-fields/description/editor.scss";
|
||||||
@import "product-fields/catalog-visibility/editor.scss";
|
@import "product-fields/catalog-visibility/editor.scss";
|
||||||
@import "product-fields/custom-fields/editor.scss";
|
|
||||||
@import "product-fields/custom-fields-toggle/editor.scss";
|
@import "product-fields/custom-fields-toggle/editor.scss";
|
||||||
@import "product-fields/downloads/editor.scss";
|
@import "product-fields/downloads/editor.scss";
|
||||||
@import "product-fields/images/editor.scss";
|
@import "product-fields/images/editor.scss";
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { createElement } from '@wordpress/element';
|
import { Button } from '@wordpress/components';
|
||||||
|
import { createElement, Fragment, useState } from '@wordpress/element';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
@ -9,17 +10,39 @@ import classNames from 'classnames';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { useCustomFields } from '../../hooks/use-custom-fields';
|
import { useCustomFields } from '../../hooks/use-custom-fields';
|
||||||
|
import { EditModal } from './edit-modal';
|
||||||
import { EmptyState } from './empty-state';
|
import { EmptyState } from './empty-state';
|
||||||
|
import type { Metadata } from '../../types';
|
||||||
import type { CustomFieldsProps } from './types';
|
import type { CustomFieldsProps } from './types';
|
||||||
|
|
||||||
export function CustomFields( { className, ...props }: CustomFieldsProps ) {
|
export function CustomFields( { className, ...props }: CustomFieldsProps ) {
|
||||||
const { customFields } = useCustomFields();
|
const { customFields, updateCustomField } = useCustomFields();
|
||||||
|
const [ selectedCustomField, setSelectedCustomField ] =
|
||||||
|
useState< Metadata< string > >();
|
||||||
|
|
||||||
if ( customFields.length === 0 ) {
|
if ( customFields.length === 0 ) {
|
||||||
return <EmptyState />;
|
return <EmptyState />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function customFieldEditButtonClickHandler(
|
||||||
|
customField: Metadata< string >
|
||||||
|
) {
|
||||||
|
return function handleCustomFieldEditButtonClick() {
|
||||||
|
setSelectedCustomField( customField );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditModalUpdate( customField: Metadata< string > ) {
|
||||||
|
updateCustomField( customField );
|
||||||
|
setSelectedCustomField( undefined );
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditModalCancel() {
|
||||||
|
setSelectedCustomField( undefined );
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<table
|
<table
|
||||||
{ ...props }
|
{ ...props }
|
||||||
className={ classNames(
|
className={ classNames(
|
||||||
|
@ -46,10 +69,28 @@ export function CustomFields( { className, ...props }: CustomFieldsProps ) {
|
||||||
<td className="woocommerce-product-custom-fields__table-datacell">
|
<td className="woocommerce-product-custom-fields__table-datacell">
|
||||||
{ customField.value }
|
{ customField.value }
|
||||||
</td>
|
</td>
|
||||||
<td className="woocommerce-product-custom-fields__table-datacell"></td>
|
<td className="woocommerce-product-custom-fields__table-datacell">
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={ customFieldEditButtonClickHandler(
|
||||||
|
customField
|
||||||
|
) }
|
||||||
|
>
|
||||||
|
{ __( 'Edit', 'woocommerce' ) }
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) ) }
|
) ) }
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{ selectedCustomField && (
|
||||||
|
<EditModal
|
||||||
|
initialValue={ selectedCustomField }
|
||||||
|
onUpdate={ handleEditModalUpdate }
|
||||||
|
onCancel={ handleEditModalCancel }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { Button, Modal } from '@wordpress/components';
|
||||||
|
import { createElement, useState, useRef } from '@wordpress/element';
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import type { FocusEvent } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { TextControl } from '../../text-control';
|
||||||
|
import type { Metadata } from '../../../types';
|
||||||
|
import type { EditModalProps } from './types';
|
||||||
|
|
||||||
|
function validateName( value: string ) {
|
||||||
|
if ( value.startsWith( '_' ) ) {
|
||||||
|
return __(
|
||||||
|
'The name cannot begin with the underscore (_) character.',
|
||||||
|
'woocommerce'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditModal( {
|
||||||
|
initialValue,
|
||||||
|
onUpdate,
|
||||||
|
onCancel,
|
||||||
|
...props
|
||||||
|
}: EditModalProps ) {
|
||||||
|
const [ customField, setCustomField ] =
|
||||||
|
useState< Metadata< string > >( initialValue );
|
||||||
|
const [ validationError, setValidationError ] = useState< string >();
|
||||||
|
const nameTextRef = useRef< HTMLInputElement >( null );
|
||||||
|
|
||||||
|
function renderTitle() {
|
||||||
|
return sprintf(
|
||||||
|
/* translators: %s: the name of the custom field */
|
||||||
|
__( 'Edit %s', 'woocommerce' ),
|
||||||
|
customField.key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNameChange( key: string ) {
|
||||||
|
setCustomField( ( current ) => ( { ...current, key } ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNameBlur( event: FocusEvent< HTMLInputElement > ) {
|
||||||
|
const error = validateName( event.target.value );
|
||||||
|
setValidationError( error );
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleValueChange( value: string ) {
|
||||||
|
setCustomField( ( current ) => ( { ...current, value } ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdateButtonClick() {
|
||||||
|
const error = validateName( customField.key );
|
||||||
|
if ( error ) {
|
||||||
|
setValidationError( error );
|
||||||
|
nameTextRef.current?.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate( customField );
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
shouldCloseOnClickOutside={ false }
|
||||||
|
{ ...props }
|
||||||
|
title={ renderTitle() }
|
||||||
|
onRequestClose={ onCancel }
|
||||||
|
className={ classNames(
|
||||||
|
'woocommerce-product-custom-fields__edit-modal',
|
||||||
|
props.className
|
||||||
|
) }
|
||||||
|
>
|
||||||
|
<TextControl
|
||||||
|
ref={ nameTextRef }
|
||||||
|
label={ __( 'Name', 'woocommerce' ) }
|
||||||
|
error={ validationError }
|
||||||
|
value={ customField.key }
|
||||||
|
onChange={ handleNameChange }
|
||||||
|
onBlur={ handleNameBlur }
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextControl
|
||||||
|
label={ __( 'Value', 'woocommerce' ) }
|
||||||
|
value={ customField.value }
|
||||||
|
onChange={ handleValueChange }
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="woocommerce-product-custom-fields__edit-modal-actions">
|
||||||
|
<Button variant="secondary" onClick={ onCancel }>
|
||||||
|
{ __( 'Cancel', 'woocommerce' ) }
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button variant="primary" onClick={ handleUpdateButtonClick }>
|
||||||
|
{ __( 'Update', 'woocommerce' ) }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './edit-modal';
|
||||||
|
export * from './types';
|
|
@ -0,0 +1,15 @@
|
||||||
|
.woocommerce-product-custom-fields__edit-modal {
|
||||||
|
min-width: 75%;
|
||||||
|
|
||||||
|
&-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: $grid-unit-40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-base-control {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { Modal } from '@wordpress/components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { Metadata } from '../../../types';
|
||||||
|
|
||||||
|
export type EditModalProps = Omit<
|
||||||
|
Modal.Props,
|
||||||
|
'title' | 'onRequestClose' | 'children'
|
||||||
|
> & {
|
||||||
|
initialValue: Metadata< string >;
|
||||||
|
onUpdate( value: Metadata< string > ): void;
|
||||||
|
onCancel(): void;
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
@import "./empty-state/style.scss";
|
@import "./empty-state/style.scss";
|
||||||
|
@import "./edit-modal/style.scss";
|
||||||
|
|
||||||
.woocommerce-product-custom-fields {
|
.woocommerce-product-custom-fields {
|
||||||
&__table {
|
&__table {
|
||||||
|
@ -22,12 +23,13 @@
|
||||||
&-datacell {
|
&-datacell {
|
||||||
padding-top: $grid-unit-30;
|
padding-top: $grid-unit-30;
|
||||||
padding-bottom: $grid-unit-30;
|
padding-bottom: $grid-unit-30;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 0;
|
padding: 0 2px 0 0;
|
||||||
gap: $grid-unit;
|
gap: $grid-unit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
.woocommerce-product-text-control {
|
||||||
|
&.has-error {
|
||||||
|
.components-input-control__container
|
||||||
|
.components-input-control__backdrop {
|
||||||
|
border-color: $studio-red-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-base-control__help {
|
||||||
|
color: $studio-red-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,9 +33,13 @@ export const TextControl = forwardRef( function ForwardedTextControl(
|
||||||
<InputControl
|
<InputControl
|
||||||
{ ...props }
|
{ ...props }
|
||||||
ref={ ref }
|
ref={ ref }
|
||||||
className={ classNames( className, {
|
className={ classNames(
|
||||||
|
'woocommerce-product-text-control',
|
||||||
|
className,
|
||||||
|
{
|
||||||
'has-error': error,
|
'has-error': error,
|
||||||
} ) }
|
}
|
||||||
|
) }
|
||||||
label={
|
label={
|
||||||
<Label
|
<Label
|
||||||
label={ label }
|
label={ label }
|
||||||
|
|
|
@ -37,5 +37,16 @@ export function useCustomFields<
|
||||||
setMetas( [ ...internalMetas, ...newValue ] );
|
setMetas( [ ...internalMetas, ...newValue ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
return { customFields, setCustomFields };
|
function updateCustomField( customField: T ) {
|
||||||
|
setCustomFields( ( current ) =>
|
||||||
|
current.map( ( field ) => {
|
||||||
|
if ( customField.id && field.id === customField.id ) {
|
||||||
|
return customField;
|
||||||
|
}
|
||||||
|
return field;
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { customFields, setCustomFields, updateCustomField };
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
@import "components/require-password/styles.scss";
|
@import "components/require-password/styles.scss";
|
||||||
@import "components/schedule-publish-modal/style.scss";
|
@import "components/schedule-publish-modal/style.scss";
|
||||||
@import "components/custom-fields/style.scss";
|
@import "components/custom-fields/style.scss";
|
||||||
|
@import "components/text-control/style.scss";
|
||||||
|
|
||||||
/* Field Blocks */
|
/* Field Blocks */
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue