[ 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:
parent
575bbae7b9
commit
26811772c9
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add variation options block
|
|
@ -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';
|
||||
|
|
|
@ -15,3 +15,4 @@
|
|||
@import 'tab/editor.scss';
|
||||
@import 'variations/editor.scss';
|
||||
@import 'password/editor.scss';
|
||||
@import 'variation-options/editor.scss';
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 } );
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { BlockAttributes } from '@wordpress/blocks';
|
||||
|
||||
export interface VariationOptionsBlockAttributes extends BlockAttributes {
|
||||
description: string;
|
||||
}
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.woocommerce-new-attribute-modal {
|
||||
min-width: 50%;
|
||||
.components-notice.is-info {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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();
|
||||
} );
|
||||
} );
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Add 'variable' to supported post types for product block editor
|
|
@ -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' ) ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue