[ Product Block Editor ] Create Variation options block (#39256)

* Add support for variable products

* Add 'hello world' block to variations tab

* Add product-section block to template

* Add AttributeControl component to screen

* Add changelog

* Change labels

* Make a copy of AttributeControl to VariationOptionsControl to allow the fields to evolve separately in future

* Fix tests

* Add changelog to woocommerce

* Fix alert error

* Remove copied control and start adapting attribute control to handle both scenarios

* Add -field to block name

* Revert "Add -field to block name"

This reverts commit 50e1ee66e27ffb2df22ea7f6a4f78d1577a273f5.

* Revert "Revert "Add -field to block name""

This reverts commit eee0441c6532f9fa8cf8383d9699fd503dd054ae.

* Extract more labels

* Hide drag handle in variation options
This commit is contained in:
Nathan Silveira 2023-07-28 14:40:18 -03:00 committed by Kyle Nel
parent 575bbae7b9
commit 26811772c9
No known key found for this signature in database
15 changed files with 190 additions and 14 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add variation options block

View File

@ -22,3 +22,4 @@ export { init as initToggle } from './toggle';
export { init as attributesInit } from './attributes';
export { init as initVariations } from './variations';
export { init as initRequirePassword } from './password';
export { init as initVariationOptions } from './variation-options';

View File

@ -15,3 +15,4 @@
@import 'tab/editor.scss';
@import 'variations/editor.scss';
@import 'password/editor.scss';
@import 'variation-options/editor.scss';

View File

@ -0,0 +1,26 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "woocommerce/product-variations-options-field",
"title": "Product variations options",
"category": "woocommerce",
"description": "The product variations options.",
"keywords": [ "products", "variations" ],
"textdomain": "default",
"attributes": {
"description": {
"type": "string",
"__experimentalRole": "content"
}
},
"supports": {
"align": false,
"html": false,
"multiple": false,
"reusable": false,
"inserter": false,
"lock": false,
"__experimentalToolbar": false
},
"editorStyle": "file:./editor.css"
}

View File

@ -0,0 +1,58 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { createElement } from '@wordpress/element';
import { ProductAttribute } from '@woocommerce/data';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
// eslint-disable-next-line @woocommerce/dependency-group
import { useEntityProp, useEntityId } from '@wordpress/core-data';
/**
* Internal dependencies
*/
import { useProductAttributes } from '../../hooks/use-product-attributes';
import { AttributeControl } from '../../components/attribute-control';
export function Edit() {
const blockProps = useBlockProps();
const [ entityAttributes, setEntityAttributes ] = useEntityProp<
ProductAttribute[]
>( 'postType', 'product', 'attributes' );
const { attributes, handleChange } = useProductAttributes( {
allAttributes: entityAttributes,
onChange: setEntityAttributes,
isVariationAttributes: true,
productId: useEntityId( 'postType', 'product' ),
} );
return (
<div { ...blockProps }>
<AttributeControl
value={ attributes }
onChange={ handleChange }
uiStrings={ {
globalAttributeHelperMessage: '',
customAttributeHelperMessage: '',
newAttributeModalNotice: '',
newAttributeModalTitle: __(
'Add variation options',
'woocommerce'
),
attributeRemoveLabel: __(
'Remove variation option',
'woocommerce'
),
attributeRemoveConfirmationMessage: __(
'Remove this variation option?',
'woocommerce'
),
} }
/>
</div>
);
}

View File

@ -0,0 +1,17 @@
.wp-block-woocommerce-product-variations-options-field {
.woocommerce-sortable {
padding: 0;
}
.woocommerce-list-item {
background: none;
border: none;
border-bottom: 1px solid $gray-200;
padding-left: 0;
grid-template-columns: 26% auto 90px;
}
.woocommerce-sortable__handle {
display: none;
}
}

View File

@ -0,0 +1,29 @@
/**
* External dependencies
*/
import { createElement } from '@wordpress/element';
import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { initBlock } from '../../utils/init-blocks';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { VariationOptionsBlockAttributes } from './types';
const { name, ...metadata } =
blockConfiguration as BlockConfiguration< VariationOptionsBlockAttributes >;
export { metadata, name };
export const settings: Partial<
BlockConfiguration< VariationOptionsBlockAttributes >
> = {
example: {},
edit: Edit,
};
export function init() {
return initBlock( { name, metadata, settings } );
}

View File

@ -0,0 +1,8 @@
/**
* External dependencies
*/
import { BlockAttributes } from '@wordpress/blocks';
export interface VariationOptionsBlockAttributes extends BlockAttributes {
description: string;
}

View File

@ -48,6 +48,10 @@ type AttributeControlProps = {
emptyStateSubtitle?: string;
newAttributeListItemLabel?: string;
newAttributeModalTitle?: string;
newAttributeModalNotice?: string;
customAttributeHelperMessage?: string;
attributeRemoveLabel?: string;
attributeRemoveConfirmationMessage?: string;
globalAttributeHelperMessage: string;
};
};
@ -65,16 +69,24 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
onEditModalOpen = () => {},
onRemove = () => {},
onRemoveCancel = () => {},
uiStrings,
} ) => {
uiStrings = {
newAttributeModalTitle: undefined,
emptyStateSubtitle: undefined,
newAttributeListItemLabel: __( 'Add attributes', 'woocommerce' ),
newAttributeListItemLabel: __( 'Add new', 'woocommerce' ),
globalAttributeHelperMessage: __(
`You can change the attribute's name in <link>Attributes</link>.`,
'woocommerce'
),
},
} ) => {
newAttributeModalNotice: __(
'By default, attributes are filterable and visible on the product page. You can change these settings for each attribute separately later.',
'woocommerce'
),
attributeRemoveConfirmationMessage: __(
'Remove this attribute?',
'woocommerce'
),
...uiStrings,
};
const [ isNewModalVisible, setIsNewModalVisible ] = useState( false );
const [ currentAttributeId, setCurrentAttributeId ] = useState<
null | string
@ -97,7 +109,7 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
const handleRemove = ( attribute: ProductAttribute ) => {
// eslint-disable-next-line no-alert
if ( window.confirm( __( 'Remove this attribute?', 'woocommerce' ) ) ) {
if ( window.confirm( uiStrings?.attributeRemoveConfirmationMessage ) ) {
handleChange(
value.filter(
( attr ) =>
@ -213,6 +225,7 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
{ sortedAttributes.map( ( attr ) => (
<AttributeListItem
attribute={ attr }
removeLabel={ uiStrings?.attributeRemoveLabel }
key={ getAttributeId( attr ) }
onEditClick={ () => openEditModal( attr ) }
onRemoveClick={ () => handleRemove( attr ) }
@ -224,6 +237,7 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
{ isNewModalVisible && (
<NewAttributeModal
title={ uiStrings.newAttributeModalTitle }
notice={ uiStrings.newAttributeModalNotice }
onCancel={ () => {
closeNewModal();
onNewModalCancel();
@ -240,6 +254,9 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
__( 'Edit %s', 'woocommerce' ),
currentAttribute.name
) }
customAttributeHelperMessage={
uiStrings.customAttributeHelperMessage
}
globalAttributeHelperMessage={ createInterpolateElement(
uiStrings.globalAttributeHelperMessage,
{

View File

@ -1,4 +1,5 @@
.woocommerce-new-attribute-modal {
min-width: 50%;
.components-notice.is-info {
margin-left: 0;
margin-right: 0;

View File

@ -229,9 +229,11 @@ export const NewAttributeModal: React.FC< NewAttributeModalProps > = ( {
} }
className="woocommerce-new-attribute-modal"
>
<Notice isDismissible={ false }>
<p>{ notice }</p>
</Notice>
{ notice && (
<Notice isDismissible={ false }>
<p>{ notice }</p>
</Notice>
) }
<div className="woocommerce-new-attribute-modal__body">
<table className="woocommerce-new-attribute-modal__table">

View File

@ -113,11 +113,11 @@ describe( 'AttributeControl', () => {
} );
describe( 'empty state', () => {
it( 'should show subtitle and "Add attributes" button', () => {
it( 'should show subtitle and "Add new" button', () => {
const { queryByText } = render(
<AttributeControl value={ [] } onChange={ () => {} } />
);
expect( queryByText( 'Add attributes' ) ).toBeInTheDocument();
expect( queryByText( 'Add new' ) ).toBeInTheDocument();
} );
} );

View File

@ -21,7 +21,7 @@ type AttributeListItemProps = {
export const AttributeListItem: React.FC< AttributeListItemProps > = ( {
attribute,
editLabel = __( 'edit', 'woocommerce' ),
editLabel = __( 'Edit', 'woocommerce' ),
removeLabel = __( 'Remove attribute', 'woocommerce' ),
onDragStart,
onDragEnd,

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Add 'variable' to supported post types for product block editor

View File

@ -25,7 +25,7 @@ class Init {
*
* @var array
*/
private $supported_post_types = array( 'simple' );
private $supported_post_types = array( 'simple', 'variable' );
/**
* Redirection controller.
@ -727,7 +727,15 @@ class Init {
'</strong>'
),
),
array(),
array(
array(
'woocommerce/product-section',
array(
'title' => __( 'Variation options', 'woocommerce' ),
),
array( array( 'woocommerce/product-variations-options-field' ) ),
),
),
),
),
)