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,
|
||||
"lock": 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/description/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/downloads/editor.scss";
|
||||
@import "product-fields/images/editor.scss";
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { createElement, Fragment, useState } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
@ -9,47 +10,87 @@ import classNames from 'classnames';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { useCustomFields } from '../../hooks/use-custom-fields';
|
||||
import { EditModal } from './edit-modal';
|
||||
import { EmptyState } from './empty-state';
|
||||
import type { Metadata } from '../../types';
|
||||
import type { CustomFieldsProps } from './types';
|
||||
|
||||
export function CustomFields( { className, ...props }: CustomFieldsProps ) {
|
||||
const { customFields } = useCustomFields();
|
||||
const { customFields, updateCustomField } = useCustomFields();
|
||||
const [ selectedCustomField, setSelectedCustomField ] =
|
||||
useState< Metadata< string > >();
|
||||
|
||||
if ( customFields.length === 0 ) {
|
||||
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 (
|
||||
<table
|
||||
{ ...props }
|
||||
className={ classNames(
|
||||
'woocommerce-product-custom-fields__table',
|
||||
className
|
||||
) }
|
||||
>
|
||||
<thead>
|
||||
<tr className="woocommerce-product-custom-fields__table-row">
|
||||
<th>{ __( 'Name', 'woocommerce' ) }</th>
|
||||
<th>{ __( 'Value', 'woocommerce' ) }</th>
|
||||
<th>{ __( 'Actions', 'woocommerce' ) }</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ customFields.map( ( customField ) => (
|
||||
<tr
|
||||
className="woocommerce-product-custom-fields__table-row"
|
||||
key={ customField.id ?? customField.key }
|
||||
>
|
||||
<td className="woocommerce-product-custom-fields__table-datacell">
|
||||
{ customField.key }
|
||||
</td>
|
||||
<td className="woocommerce-product-custom-fields__table-datacell">
|
||||
{ customField.value }
|
||||
</td>
|
||||
<td className="woocommerce-product-custom-fields__table-datacell"></td>
|
||||
<>
|
||||
<table
|
||||
{ ...props }
|
||||
className={ classNames(
|
||||
'woocommerce-product-custom-fields__table',
|
||||
className
|
||||
) }
|
||||
>
|
||||
<thead>
|
||||
<tr className="woocommerce-product-custom-fields__table-row">
|
||||
<th>{ __( 'Name', 'woocommerce' ) }</th>
|
||||
<th>{ __( 'Value', 'woocommerce' ) }</th>
|
||||
<th>{ __( 'Actions', 'woocommerce' ) }</th>
|
||||
</tr>
|
||||
) ) }
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ customFields.map( ( customField ) => (
|
||||
<tr
|
||||
className="woocommerce-product-custom-fields__table-row"
|
||||
key={ customField.id ?? customField.key }
|
||||
>
|
||||
<td className="woocommerce-product-custom-fields__table-datacell">
|
||||
{ customField.key }
|
||||
</td>
|
||||
<td className="woocommerce-product-custom-fields__table-datacell">
|
||||
{ customField.value }
|
||||
</td>
|
||||
<td className="woocommerce-product-custom-fields__table-datacell">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={ customFieldEditButtonClickHandler(
|
||||
customField
|
||||
) }
|
||||
>
|
||||
{ __( 'Edit', 'woocommerce' ) }
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
) ) }
|
||||
</tbody>
|
||||
</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 "./edit-modal/style.scss";
|
||||
|
||||
.woocommerce-product-custom-fields {
|
||||
&__table {
|
||||
|
@ -22,12 +23,13 @@
|
|||
&-datacell {
|
||||
padding-top: $grid-unit-30;
|
||||
padding-bottom: $grid-unit-30;
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0;
|
||||
padding: 0 2px 0 0;
|
||||
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
|
||||
{ ...props }
|
||||
ref={ ref }
|
||||
className={ classNames( className, {
|
||||
'has-error': error,
|
||||
} ) }
|
||||
className={ classNames(
|
||||
'woocommerce-product-text-control',
|
||||
className,
|
||||
{
|
||||
'has-error': error,
|
||||
}
|
||||
) }
|
||||
label={
|
||||
<Label
|
||||
label={ label }
|
||||
|
|
|
@ -37,5 +37,16 @@ export function useCustomFields<
|
|||
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/schedule-publish-modal/style.scss";
|
||||
@import "components/custom-fields/style.scss";
|
||||
@import "components/text-control/style.scss";
|
||||
|
||||
/* Field Blocks */
|
||||
|
||||
|
|
Loading…
Reference in New Issue