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:
Maikel Perez 2024-03-11 15:28:51 -03:00 committed by GitHub
parent 1e7b8c4004
commit 277fa6b005
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 254 additions and 43 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Edit Custom Fields for New Product Editor

View File

@ -21,7 +21,5 @@
"inserter": false,
"lock": false,
"__experimentalToolbar": false
},
"usesContext": [ "postType" ],
"editorStyle": "file:./editor.css"
}
}

View File

@ -1,2 +0,0 @@
.wp-block-woocommerce-product-custom-fields-field {
}

View File

@ -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";

View File

@ -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 }
/>
) }
</>
);
}

View File

@ -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>
);
}

View File

@ -0,0 +1,2 @@
export * from './edit-modal';
export * from './types';

View File

@ -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%;
}
}

View File

@ -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;
};

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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 }

View File

@ -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 };
}

View File

@ -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 */