diff --git a/packages/js/product-editor/changelog/add-43808-common-options b/packages/js/product-editor/changelog/add-43808-common-options
new file mode 100644
index 00000000000..a2827f42cf9
--- /dev/null
+++ b/packages/js/product-editor/changelog/add-43808-common-options
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Create attribute control custom empty state
diff --git a/packages/js/product-editor/src/blocks/index.ts b/packages/js/product-editor/src/blocks/index.ts
index 61176ee33da..a07a311e090 100644
--- a/packages/js/product-editor/src/blocks/index.ts
+++ b/packages/js/product-editor/src/blocks/index.ts
@@ -23,7 +23,6 @@ export { init as initTag } from './product-fields/tag';
export { init as initInventoryQuantity } from './product-fields/inventory-quantity';
export { init as initToggle } from './generic/toggle';
export { init as attributesInit } from './product-fields/attributes';
-export { init as initVariations } from './product-fields/variations';
export { init as initRequirePassword } from './product-fields/password';
export { init as initProductDetailsSectionDescription } from './product-fields/product-details-section-description';
export { init as initProductList } from './product-fields/product-list';
diff --git a/packages/js/product-editor/src/blocks/product-fields/variation-options/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/variation-options/edit.tsx
index 24b99091e1b..3d3bb7490d5 100644
--- a/packages/js/product-editor/src/blocks/product-fields/variation-options/edit.tsx
+++ b/packages/js/product-editor/src/blocks/product-fields/variation-options/edit.tsx
@@ -3,6 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { BlockAttributes } from '@wordpress/blocks';
+import { Button } from '@wordpress/components';
import {
createElement,
createInterpolateElement,
@@ -26,9 +27,13 @@ import { useEntityProp, useEntityId } from '@wordpress/core-data';
* Internal dependencies
*/
import { useProductAttributes } from '../../../hooks/use-product-attributes';
-import { AttributeControl } from '../../../components/attribute-control';
+import {
+ AttributeControl,
+ AttributeControlEmptyStateProps,
+} from '../../../components/attribute-control';
import { useProductVariationsHelper } from '../../../hooks/use-product-variations-helper';
import { ProductEditorBlockEditProps } from '../../../types';
+import { ProductTShirt } from './images';
export function Edit( {
attributes: blockAttributes,
@@ -113,6 +118,49 @@ export function Edit( {
} ) );
}
+ function renderCustomEmptyState( {
+ addAttribute,
+ }: AttributeControlEmptyStateProps ) {
+ return (
+
+
+
+
+ { __(
+ 'Sell your product in multiple variations like size or color.',
+ 'woocommerce'
+ ) }
+
+
+
+
+
+
+
+
+ );
+ }
+
return (
! attr.variation )
.map( ( attr ) => attr.id ) }
diff --git a/packages/js/product-editor/src/blocks/product-fields/variation-options/editor.scss b/packages/js/product-editor/src/blocks/product-fields/variation-options/editor.scss
index 18c919965e3..ee754a446d8 100644
--- a/packages/js/product-editor/src/blocks/product-fields/variation-options/editor.scss
+++ b/packages/js/product-editor/src/blocks/product-fields/variation-options/editor.scss
@@ -1,4 +1,45 @@
.wp-block-woocommerce-product-variations-options-field {
+ &__empty-state {
+ border: 1px dashed $gray-400;
+ border-radius: 2px;
+ padding: $grid-unit-60 $grid-unit-20;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: $grid-unit-30;
+
+ &-image {
+ display: inline-flex;
+ gap: $grid-unit-05;
+
+ &-product {
+ height: 66px;
+ aspect-ratio: 1 / 1;
+ color: $gray-100;
+
+ &:first-child {
+ height: $grid-unit-70;
+ color: $gray-200;
+ }
+
+ &:last-child {
+ height: 50px;
+ color: $gray-300;
+ }
+ }
+ }
+
+ &-description {
+ margin: 0;
+ }
+
+ &-actions {
+ display: inline-flex;
+ gap: 12px;
+ }
+ }
+
.woocommerce-sortable {
padding: 0;
}
diff --git a/packages/js/product-editor/src/blocks/product-fields/variation-options/images/ProductTShirt.tsx b/packages/js/product-editor/src/blocks/product-fields/variation-options/images/ProductTShirt.tsx
new file mode 100644
index 00000000000..37220081ca4
--- /dev/null
+++ b/packages/js/product-editor/src/blocks/product-fields/variation-options/images/ProductTShirt.tsx
@@ -0,0 +1,41 @@
+/**
+ * External dependencies
+ */
+import { useInstanceId } from '@wordpress/compose';
+import { createElement } from '@wordpress/element';
+
+export function ProductTShirt( props: React.SVGProps< SVGSVGElement > ) {
+ const clipPathId = useInstanceId( ProductTShirt, 'clip-path' ) as string;
+
+ return (
+
+ );
+}
diff --git a/packages/js/product-editor/src/blocks/product-fields/variation-options/images/index.ts b/packages/js/product-editor/src/blocks/product-fields/variation-options/images/index.ts
new file mode 100644
index 00000000000..2002c05faec
--- /dev/null
+++ b/packages/js/product-editor/src/blocks/product-fields/variation-options/images/index.ts
@@ -0,0 +1 @@
+export * from './ProductTShirt';
diff --git a/packages/js/product-editor/src/blocks/product-fields/variations/block.json b/packages/js/product-editor/src/blocks/product-fields/variations/block.json
deleted file mode 100644
index c471504d33f..00000000000
--- a/packages/js/product-editor/src/blocks/product-fields/variations/block.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
- "name": "woocommerce/product-variations-fields",
- "title": "Product variations fields",
- "category": "woocommerce",
- "description": "The product variations.",
- "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"
-}
diff --git a/packages/js/product-editor/src/blocks/product-fields/variations/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/variations/edit.tsx
deleted file mode 100644
index 8c986095caf..00000000000
--- a/packages/js/product-editor/src/blocks/product-fields/variations/edit.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-/**
- * External dependencies
- */
-import classNames from 'classnames';
-import { Button } from '@wordpress/components';
-import { useWooBlockProps } from '@woocommerce/block-templates';
-import { Product, ProductAttribute } from '@woocommerce/data';
-import { recordEvent } from '@woocommerce/tracks';
-import { createElement, useState } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
-import {
- // @ts-expect-error no exported member.
- useInnerBlocksProps,
-} from '@wordpress/block-editor';
-// 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 { sanitizeHTML } from '../../../utils/sanitize-html';
-import { VariationsBlockAttributes } from './types';
-import { EmptyVariationsImage } from '../../../images/empty-variations-image';
-import { NewAttributeModal } from '../../../components/attribute-control/new-attribute-modal';
-import {
- EnhancedProductAttribute,
- useProductAttributes,
-} from '../../../hooks/use-product-attributes';
-import { getAttributeId } from '../../../components/attribute-control/utils';
-import { useProductVariationsHelper } from '../../../hooks/use-product-variations-helper';
-import { hasAttributesUsedForVariations } from '../../../utils';
-import { TRACKS_SOURCE } from '../../../constants';
-import { ProductEditorBlockEditProps } from '../../../types';
-
-export function Edit( {
- attributes,
-}: ProductEditorBlockEditProps< VariationsBlockAttributes > ) {
- const { description } = attributes;
-
- const { generateProductVariations } = useProductVariationsHelper();
- const [ isNewModalVisible, setIsNewModalVisible ] = useState( false );
- const [ productAttributes, setProductAttributes ] = useEntityProp<
- Product[ 'attributes' ]
- >( 'postType', 'product', 'attributes' );
- const [ , setDefaultProductAttributes ] = useEntityProp<
- Product[ 'default_attributes' ]
- >( 'postType', 'product', 'default_attributes' );
-
- const { attributes: variationOptions, handleChange } = useProductAttributes(
- {
- allAttributes: productAttributes,
- isVariationAttributes: true,
- productId: useEntityId( 'postType', 'product' ),
- onChange( values, defaultAttributes ) {
- setProductAttributes( values );
- setDefaultProductAttributes( defaultAttributes );
- generateProductVariations( values, defaultAttributes );
- },
- }
- );
-
- const hasAttributes = hasAttributesUsedForVariations( productAttributes );
-
- const blockProps = useWooBlockProps( attributes, {
- className: classNames( {
- 'wp-block-woocommerce-product-variations-fields--has-attributes':
- hasAttributes,
- } ),
- } );
- const innerBlockProps = useInnerBlocksProps(
- {
- className:
- 'wp-block-woocommerce-product-variations-fields__content',
- },
- { templateLock: 'all' }
- );
-
- const openNewModal = () => {
- setIsNewModalVisible( true );
- recordEvent( 'product_options_add_first_option' );
- };
-
- const closeNewModal = () => {
- setIsNewModalVisible( false );
- };
-
- const handleAdd = ( newOptions: EnhancedProductAttribute[] ) => {
- const addedAttributesOnly = newOptions.filter(
- ( newAttr ) =>
- ! variationOptions.some(
- ( attr: ProductAttribute ) =>
- getAttributeId( newAttr ) === getAttributeId( attr )
- )
- );
- recordEvent( 'product_options_add', {
- source: TRACKS_SOURCE,
- options: addedAttributesOnly.map( ( attribute ) => ( {
- attribute: attribute.name,
- values: attribute.options,
- } ) ),
- } );
-
- handleChange( addedAttributesOnly );
- closeNewModal();
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- { isNewModalVisible && (
-
{
- recordEvent(
- 'product_options_modal_cancel_button_click'
- );
- closeNewModal();
- } }
- onAdd={ handleAdd }
- onAddAnother={ () => {
- recordEvent(
- 'product_add_options_modal_add_another_option_button_click'
- );
- } }
- onRemoveItem={ () => {
- recordEvent(
- 'product_add_options_modal_remove_option_button_click'
- );
- } }
- selectedAttributeIds={ variationOptions.map(
- ( attr ) => attr.id
- ) }
- disabledAttributeIds={ productAttributes
- .filter( ( attr ) => ! attr.variation )
- .map( ( attr ) => attr.id ) }
- termsAutoSelection="all"
- />
- ) }
-
- );
-}
diff --git a/packages/js/product-editor/src/blocks/product-fields/variations/editor.scss b/packages/js/product-editor/src/blocks/product-fields/variations/editor.scss
deleted file mode 100644
index f018751df6e..00000000000
--- a/packages/js/product-editor/src/blocks/product-fields/variations/editor.scss
+++ /dev/null
@@ -1,31 +0,0 @@
-.wp-block-woocommerce-product-variations-fields {
- margin-top: $grid-unit-80;
-
- &--has-attributes {
- .wp-block-woocommerce-product-variations-fields__heading {
- display: none;
- }
-
- .wp-block-woocommerce-product-variations-fields__content {
- display: block;
- }
- }
-
- &__heading {
- text-align: center;
- }
-
- &__heading-image-container {
- padding-top: $grid-unit-60;
- padding-bottom: $grid-unit-60;
- }
-
- &__heading-description {
- margin: 0;
- padding-bottom: $grid-unit-30;
- }
-
- &__content {
- display: none;
- }
-}
diff --git a/packages/js/product-editor/src/blocks/product-fields/variations/index.ts b/packages/js/product-editor/src/blocks/product-fields/variations/index.ts
deleted file mode 100644
index f5e901e8288..00000000000
--- a/packages/js/product-editor/src/blocks/product-fields/variations/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Internal dependencies
- */
-import blockConfiguration from './block.json';
-import { Edit } from './edit';
-import { registerProductEditorBlockType } from '../../../utils';
-
-const { name, ...metadata } = blockConfiguration;
-
-export { metadata, name };
-
-export const settings = {
- example: {},
- edit: Edit,
-};
-
-export function init() {
- return registerProductEditorBlockType( {
- name,
- metadata: metadata as never,
- settings: settings as never,
- } );
-}
diff --git a/packages/js/product-editor/src/blocks/product-fields/variations/types.ts b/packages/js/product-editor/src/blocks/product-fields/variations/types.ts
deleted file mode 100644
index 759a1579a36..00000000000
--- a/packages/js/product-editor/src/blocks/product-fields/variations/types.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * External dependencies
- */
-import { BlockAttributes } from '@wordpress/blocks';
-
-export interface VariationsBlockAttributes extends BlockAttributes {
- description: string;
-}
diff --git a/packages/js/product-editor/src/blocks/style.scss b/packages/js/product-editor/src/blocks/style.scss
index 44d7c17cba1..40cefecf1dc 100644
--- a/packages/js/product-editor/src/blocks/style.scss
+++ b/packages/js/product-editor/src/blocks/style.scss
@@ -15,7 +15,6 @@
@import "product-fields/shipping-dimensions/editor.scss";
@import "product-fields/summary/editor.scss";
@import "generic/tab/editor.scss";
-@import "product-fields/variations/editor.scss";
@import "product-fields/password/editor.scss";
@import "product-fields/product-list/editor.scss";
@import "product-fields/product-details-section-description/editor.scss";
diff --git a/packages/js/product-editor/src/components/attribute-control/attribute-control.tsx b/packages/js/product-editor/src/components/attribute-control/attribute-control.tsx
index 01b5f320a6b..faa1c0c5159 100644
--- a/packages/js/product-editor/src/components/attribute-control/attribute-control.tsx
+++ b/packages/js/product-editor/src/components/attribute-control/attribute-control.tsx
@@ -35,43 +35,7 @@ import { RemoveConfirmationModal } from '../remove-confirmation-modal';
import { TRACKS_SOURCE } from '../../constants';
import { AttributeEmptyStateSkeleton } from './attribute-empty-state-skeleton';
import { SectionActions } from '../block-slot-fill';
-
-type AttributeControlProps = {
- value: EnhancedProductAttribute[];
- onAdd?: ( attribute: EnhancedProductAttribute[] ) => void;
- onAddAnother?: () => void;
- onRemoveItem?: () => void;
- onChange: ( value: ProductAttribute[] ) => void;
- onEdit?: ( attribute: ProductAttribute ) => void;
- onRemove?: ( attribute: ProductAttribute ) => void;
- onRemoveCancel?: ( attribute: ProductAttribute ) => void;
- onNewModalCancel?: () => void;
- onNewModalClose?: () => void;
- onNewModalOpen?: () => void;
- onEditModalCancel?: ( attribute?: ProductAttribute ) => void;
- onEditModalClose?: ( attribute?: ProductAttribute ) => void;
- onEditModalOpen?: ( attribute?: ProductAttribute ) => void;
- onNoticeDismiss?: () => void;
- createNewAttributesAsGlobal?: boolean;
- useRemoveConfirmationModal?: boolean;
- disabledAttributeIds?: number[];
- termsAutoSelection?: 'first' | 'all';
- defaultVisibility?: boolean;
- uiStrings?: {
- notice?: string | React.ReactElement;
- emptyStateSubtitle?: string;
- newAttributeListItemLabel?: string;
- newAttributeModalTitle?: string;
- newAttributeModalDescription?: string | React.ReactElement;
- newAttributeModalNotice?: string;
- customAttributeHelperMessage?: string;
- attributeRemoveLabel?: string;
- attributeRemoveConfirmationMessage?: string;
- attributeRemoveConfirmationModalMessage?: string;
- globalAttributeHelperMessage?: string;
- disabledAttributeMessage?: string;
- };
-};
+import { AttributeControlProps } from './types';
export const AttributeControl: React.FC< AttributeControlProps > = ( {
value,
@@ -89,6 +53,7 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
onRemove = () => {},
onRemoveCancel = () => {},
onNoticeDismiss = () => {},
+ renderCustomEmptyState = () => ,
uiStrings,
createNewAttributesAsGlobal = false,
useRemoveConfirmationModal = false,
@@ -109,6 +74,8 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
...uiStrings,
};
const [ isNewModalVisible, setIsNewModalVisible ] = useState( false );
+ const [ defaultAttributeSearch, setDefaultAttributeSearch ] =
+ useState< string >();
const [ removingAttribute, setRemovingAttribute ] =
useState< null | ProductAttribute >();
const [ currentAttributeId, setCurrentAttributeId ] = useState<
@@ -161,6 +128,7 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
const closeNewModal = () => {
setIsNewModalVisible( false );
+ setDefaultAttributeSearch( undefined );
onNewModalClose();
};
@@ -311,6 +279,7 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
}
termsAutoSelection={ termsAutoSelection }
defaultVisibility={ defaultVisibility }
+ defaultSearch={ defaultAttributeSearch }
/>
) }
@@ -376,9 +345,14 @@ export const AttributeControl: React.FC< AttributeControlProps > = ( {
} }
/>
) }
- { ! isMobileViewport && value.length === 0 && (
-
- ) }
+ { ! isMobileViewport &&
+ value.length === 0 &&
+ renderCustomEmptyState( {
+ addAttribute( search ) {
+ setDefaultAttributeSearch( search );
+ openNewModal();
+ },
+ } ) }
);
};
diff --git a/packages/js/product-editor/src/components/attribute-control/index.ts b/packages/js/product-editor/src/components/attribute-control/index.ts
index 3220152ee6e..f2f7ebfbbbb 100644
--- a/packages/js/product-editor/src/components/attribute-control/index.ts
+++ b/packages/js/product-editor/src/components/attribute-control/index.ts
@@ -1 +1,2 @@
export * from './attribute-control';
+export * from './types';
diff --git a/packages/js/product-editor/src/components/attribute-control/new-attribute-modal.tsx b/packages/js/product-editor/src/components/attribute-control/new-attribute-modal.tsx
index f76dc77e743..a8d36672c01 100644
--- a/packages/js/product-editor/src/components/attribute-control/new-attribute-modal.tsx
+++ b/packages/js/product-editor/src/components/attribute-control/new-attribute-modal.tsx
@@ -51,6 +51,7 @@ type NewAttributeModalProps = {
disabledAttributeMessage?: string;
termsAutoSelection?: 'first' | 'all';
defaultVisibility?: boolean;
+ defaultSearch?: string;
};
type AttributeForm = {
@@ -84,6 +85,7 @@ export const NewAttributeModal: React.FC< NewAttributeModalProps > = ( {
),
termsAutoSelection,
defaultVisibility = false,
+ defaultSearch,
} ) => {
const scrollAttributeIntoView = ( index: number ) => {
setTimeout( () => {
@@ -202,11 +204,15 @@ export const NewAttributeModal: React.FC< NewAttributeModalProps > = ( {
return () => clearTimeout( timeoutId );
}, [] );
+ const initialAttribute = {
+ name: defaultSearch,
+ } as EnhancedProductAttribute;
+
return (
<>