Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit Custom Fields for New Product Editor #45396

Merged
merged 11 commits into from
Mar 11, 2024
4 changes: 4 additions & 0 deletions packages/js/product-editor/changelog/add-44169-edit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Edit Custom Fields for New Product Editor
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,5 @@
"inserter": false,
"lock": false,
"__experimentalToolbar": false
},
"usesContext": [ "postType" ],
"editorStyle": "file:./editor.css"
}
}

This file was deleted.

1 change: 0 additions & 1 deletion packages/js/product-editor/src/blocks/style.scss
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,96 @@
/**
* 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';

/**
* 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>
octaedro marked this conversation as resolved.
Show resolved Hide resolved
<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 }
/>
) }
</>
);
}
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './edit-modal';
export * from './types';
Original file line number Diff line number Diff line change
@@ -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%;
}
}
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import "./empty-state/style.scss";
@import "./edit-modal/style.scss";

.woocommerce-product-custom-fields {
&__table {
Expand All @@ -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;
}
}
Expand Down
12 changes: 12 additions & 0 deletions packages/js/product-editor/src/components/text-control/style.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}
1 change: 1 addition & 0 deletions packages/js/product-editor/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down