diff --git a/packages/js/data/changelog/update-39453_simple_to_variable_product b/packages/js/data/changelog/update-39453_simple_to_variable_product
new file mode 100644
index 00000000000..19ed596c31b
--- /dev/null
+++ b/packages/js/data/changelog/update-39453_simple_to_variable_product
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+
+Add new user preference to UserPreferences type.
diff --git a/packages/js/data/src/user/types.ts b/packages/js/data/src/user/types.ts
index 0cd89db8d1d..32d524f1e59 100644
--- a/packages/js/data/src/user/types.ts
+++ b/packages/js/data/src/user/types.ts
@@ -26,6 +26,7 @@ export type UserPreferences = {
};
taxes_report_columns?: string;
variable_product_tour_shown?: string;
+ variable_product_block_tour_shown?: string;
variations_report_columns?: string;
};
diff --git a/packages/js/product-editor/changelog/update-39453_simple_to_variable_product b/packages/js/product-editor/changelog/update-39453_simple_to_variable_product
new file mode 100644
index 00000000000..85e2bf77427
--- /dev/null
+++ b/packages/js/product-editor/changelog/update-39453_simple_to_variable_product
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add isInSelectedTab context to tab blocks for use in nested blocks.
diff --git a/packages/js/product-editor/changelog/update-39453_simple_to_variable_product_2 b/packages/js/product-editor/changelog/update-39453_simple_to_variable_product_2
new file mode 100644
index 00000000000..c472d5a6b57
--- /dev/null
+++ b/packages/js/product-editor/changelog/update-39453_simple_to_variable_product_2
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Update variation options block to auto create variations upon options update.
diff --git a/packages/js/product-editor/src/blocks/catalog-visibility/index.ts b/packages/js/product-editor/src/blocks/catalog-visibility/index.ts
index 276ba069731..efbd7fe8dc2 100644
--- a/packages/js/product-editor/src/blocks/catalog-visibility/index.ts
+++ b/packages/js/product-editor/src/blocks/catalog-visibility/index.ts
@@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { CatalogVisibilityBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/checkbox/index.ts b/packages/js/product-editor/src/blocks/checkbox/index.ts
index 15144b2b28d..6b8110f1433 100644
--- a/packages/js/product-editor/src/blocks/checkbox/index.ts
+++ b/packages/js/product-editor/src/blocks/checkbox/index.ts
@@ -1,11 +1,16 @@
+/**
+ * External dependencies
+ */
+import { BlockConfiguration } from '@wordpress/blocks';
+
/**
* Internal dependencies
*/
-import initBlock from '../../utils/init-block';
-import metadata from './block.json';
+import { initBlock } from '../../utils/init-block';
+import blockConfiguration from './block.json';
import { Edit } from './edit';
-const { name } = metadata;
+const { name, ...metadata } = blockConfiguration as BlockConfiguration;
export { metadata, name };
diff --git a/packages/js/product-editor/src/blocks/description/index.ts b/packages/js/product-editor/src/blocks/description/index.ts
index 15144b2b28d..6b8110f1433 100644
--- a/packages/js/product-editor/src/blocks/description/index.ts
+++ b/packages/js/product-editor/src/blocks/description/index.ts
@@ -1,11 +1,16 @@
+/**
+ * External dependencies
+ */
+import { BlockConfiguration } from '@wordpress/blocks';
+
/**
* Internal dependencies
*/
-import initBlock from '../../utils/init-block';
-import metadata from './block.json';
+import { initBlock } from '../../utils/init-block';
+import blockConfiguration from './block.json';
import { Edit } from './edit';
-const { name } = metadata;
+const { name, ...metadata } = blockConfiguration as BlockConfiguration;
export { metadata, name };
diff --git a/packages/js/product-editor/src/blocks/inventory-email/index.ts b/packages/js/product-editor/src/blocks/inventory-email/index.ts
index f022db49254..d8ad7f199c5 100644
--- a/packages/js/product-editor/src/blocks/inventory-email/index.ts
+++ b/packages/js/product-editor/src/blocks/inventory-email/index.ts
@@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { InventoryEmailBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/inventory-quantity/index.ts b/packages/js/product-editor/src/blocks/inventory-quantity/index.ts
index d52ac90ea2f..1e756b0a120 100644
--- a/packages/js/product-editor/src/blocks/inventory-quantity/index.ts
+++ b/packages/js/product-editor/src/blocks/inventory-quantity/index.ts
@@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { TrackInventoryBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/inventory-sku/index.ts b/packages/js/product-editor/src/blocks/inventory-sku/index.ts
index 15144b2b28d..6b8110f1433 100644
--- a/packages/js/product-editor/src/blocks/inventory-sku/index.ts
+++ b/packages/js/product-editor/src/blocks/inventory-sku/index.ts
@@ -1,11 +1,16 @@
+/**
+ * External dependencies
+ */
+import { BlockConfiguration } from '@wordpress/blocks';
+
/**
* Internal dependencies
*/
-import initBlock from '../../utils/init-block';
-import metadata from './block.json';
+import { initBlock } from '../../utils/init-block';
+import blockConfiguration from './block.json';
import { Edit } from './edit';
-const { name } = metadata;
+const { name, ...metadata } = blockConfiguration as BlockConfiguration;
export { metadata, name };
diff --git a/packages/js/product-editor/src/blocks/password/index.ts b/packages/js/product-editor/src/blocks/password/index.ts
index f2052d92a26..ecda64c9723 100644
--- a/packages/js/product-editor/src/blocks/password/index.ts
+++ b/packages/js/product-editor/src/blocks/password/index.ts
@@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { RequirePasswordBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/pricing/index.ts b/packages/js/product-editor/src/blocks/pricing/index.ts
index b8d779c809e..5d624f6f8ed 100644
--- a/packages/js/product-editor/src/blocks/pricing/index.ts
+++ b/packages/js/product-editor/src/blocks/pricing/index.ts
@@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { PricingBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/radio/index.ts b/packages/js/product-editor/src/blocks/radio/index.ts
index 701e003d2b5..49fa2e00137 100644
--- a/packages/js/product-editor/src/blocks/radio/index.ts
+++ b/packages/js/product-editor/src/blocks/radio/index.ts
@@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { RadioBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/regular-price/index.ts b/packages/js/product-editor/src/blocks/regular-price/index.ts
index f838788b632..c1651d35880 100644
--- a/packages/js/product-editor/src/blocks/regular-price/index.ts
+++ b/packages/js/product-editor/src/blocks/regular-price/index.ts
@@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { SalePriceBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/sale-price/index.ts b/packages/js/product-editor/src/blocks/sale-price/index.ts
index f838788b632..c1651d35880 100644
--- a/packages/js/product-editor/src/blocks/sale-price/index.ts
+++ b/packages/js/product-editor/src/blocks/sale-price/index.ts
@@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { SalePriceBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/schedule-sale/index.ts b/packages/js/product-editor/src/blocks/schedule-sale/index.ts
index e3893b6f2ac..1e5ea6faf5d 100644
--- a/packages/js/product-editor/src/blocks/schedule-sale/index.ts
+++ b/packages/js/product-editor/src/blocks/schedule-sale/index.ts
@@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { ScheduleSalePricingBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/section/index.ts b/packages/js/product-editor/src/blocks/section/index.ts
index 70224538e05..2fc3fe0fa23 100644
--- a/packages/js/product-editor/src/blocks/section/index.ts
+++ b/packages/js/product-editor/src/blocks/section/index.ts
@@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { SectionBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/shipping-class/index.ts b/packages/js/product-editor/src/blocks/shipping-class/index.ts
index 2f0d4ef3046..ae58594e10e 100644
--- a/packages/js/product-editor/src/blocks/shipping-class/index.ts
+++ b/packages/js/product-editor/src/blocks/shipping-class/index.ts
@@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { ShippingClassBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/shipping-dimensions/index.ts b/packages/js/product-editor/src/blocks/shipping-dimensions/index.ts
index f79c1241adf..25b3e065d70 100644
--- a/packages/js/product-editor/src/blocks/shipping-dimensions/index.ts
+++ b/packages/js/product-editor/src/blocks/shipping-dimensions/index.ts
@@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { ShippingDimensionsBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/tab/block.json b/packages/js/product-editor/src/blocks/tab/block.json
index 42ead1c7713..fc69d24c329 100644
--- a/packages/js/product-editor/src/blocks/tab/block.json
+++ b/packages/js/product-editor/src/blocks/tab/block.json
@@ -27,6 +27,9 @@
"lock": false,
"__experimentalToolbar": false
},
+ "providesContext": {
+ "isInSelectedTab": "isSelected"
+ },
"usesContext": [ "selectedTab" ],
"editorStyle": "file:./editor.css",
"templateLock": "contentOnly"
diff --git a/packages/js/product-editor/src/blocks/tab/edit.tsx b/packages/js/product-editor/src/blocks/tab/edit.tsx
index ad351b61d55..86624c577a1 100644
--- a/packages/js/product-editor/src/blocks/tab/edit.tsx
+++ b/packages/js/product-editor/src/blocks/tab/edit.tsx
@@ -4,25 +4,35 @@
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import classnames from 'classnames';
import { createElement } from '@wordpress/element';
-import type { BlockAttributes } from '@wordpress/blocks';
+import type { BlockAttributes, BlockEditProps } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { TabButton } from './tab-button';
+export interface TabBlockAttributes extends BlockAttributes {
+ id: string;
+ title: string;
+ order: number;
+ isSelected?: boolean;
+}
+
export function Edit( {
+ setAttributes,
attributes,
context,
-}: {
- attributes: BlockAttributes;
+}: BlockEditProps< TabBlockAttributes > & {
context?: {
selectedTab?: string | null;
};
} ) {
const blockProps = useBlockProps();
- const { id, title, order } = attributes;
+ const { id, title, order, isSelected: contextIsSelected } = attributes;
const isSelected = context?.selectedTab === id;
+ if ( isSelected !== contextIsSelected ) {
+ setAttributes( { isSelected } );
+ }
const classes = classnames( 'wp-block-woocommerce-product-tab__content', {
'is-selected': isSelected,
diff --git a/packages/js/product-editor/src/blocks/tab/index.ts b/packages/js/product-editor/src/blocks/tab/index.ts
index 15144b2b28d..1c048cec59f 100644
--- a/packages/js/product-editor/src/blocks/tab/index.ts
+++ b/packages/js/product-editor/src/blocks/tab/index.ts
@@ -1,17 +1,25 @@
+/**
+ * External dependencies
+ */
+import { BlockConfiguration } from '@wordpress/blocks';
+
/**
* Internal dependencies
*/
-import initBlock from '../../utils/init-block';
-import metadata from './block.json';
-import { Edit } from './edit';
+import { initBlock } from '../../utils/init-block';
+import blockConfiguration from './block.json';
+import { Edit, TabBlockAttributes } from './edit';
-const { name } = metadata;
+const { name, ...metadata } =
+ blockConfiguration as BlockConfiguration< TabBlockAttributes >;
export { metadata, name };
-export const settings = {
+export const settings: Partial< BlockConfiguration< TabBlockAttributes > > = {
example: {},
edit: Edit,
};
-export const init = () => initBlock( { name, metadata, settings } );
+export function init() {
+ initBlock( { name, metadata, settings } );
+}
diff --git a/packages/js/product-editor/src/blocks/toggle/index.ts b/packages/js/product-editor/src/blocks/toggle/index.ts
index d4c46ef9b1f..ca9896724dd 100644
--- a/packages/js/product-editor/src/blocks/toggle/index.ts
+++ b/packages/js/product-editor/src/blocks/toggle/index.ts
@@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { ToggleBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/variation-items/block.json b/packages/js/product-editor/src/blocks/variation-items/block.json
index 1be886724b0..f29fc947aee 100644
--- a/packages/js/product-editor/src/blocks/variation-items/block.json
+++ b/packages/js/product-editor/src/blocks/variation-items/block.json
@@ -22,5 +22,6 @@
"lock": false,
"__experimentalToolbar": false
},
+ "usesContext": [ "isInSelectedTab" ],
"editorStyle": "file:./editor.css"
}
diff --git a/packages/js/product-editor/src/blocks/variation-items/edit.tsx b/packages/js/product-editor/src/blocks/variation-items/edit.tsx
index 0549c6e3c3a..50bed1e45d3 100644
--- a/packages/js/product-editor/src/blocks/variation-items/edit.tsx
+++ b/packages/js/product-editor/src/blocks/variation-items/edit.tsx
@@ -2,19 +2,29 @@
* External dependencies
*/
import { useBlockProps } from '@wordpress/block-editor';
+import { BlockEditProps } from '@wordpress/blocks';
import { createElement } from '@wordpress/element';
/**
* Internal dependencies
*/
import { VariationsTable } from '../../components/variations-table';
+import { VariationOptionsBlockAttributes } from './types';
+import { VariableProductTour } from './variable-product-tour';
-export function Edit() {
+export function Edit( {
+ context,
+}: BlockEditProps< VariationOptionsBlockAttributes > & {
+ context?: {
+ isInSelectedTab?: boolean;
+ };
+} ) {
const blockProps = useBlockProps();
return (
+ { context?.isInSelectedTab && }
);
}
diff --git a/packages/js/product-editor/src/blocks/variation-items/editor.scss b/packages/js/product-editor/src/blocks/variation-items/editor.scss
index 7f71df3b059..e2b824187a2 100644
--- a/packages/js/product-editor/src/blocks/variation-items/editor.scss
+++ b/packages/js/product-editor/src/blocks/variation-items/editor.scss
@@ -1,2 +1,13 @@
.wp-block-woocommerce-product-variations-items-field {
}
+
+.variation-items-product-tour {
+ .tour-kit-spotlight {
+ border-radius: $gap-smaller;
+ padding: $gap-large;
+ }
+ .tour-kit-frame__container,
+ .woocommerce-tour-kit-step {
+ border-radius: $gap-smaller;
+ }
+}
diff --git a/packages/js/product-editor/src/blocks/variation-items/index.ts b/packages/js/product-editor/src/blocks/variation-items/index.ts
index 8ef0d0cfd76..18412dd99c7 100644
--- a/packages/js/product-editor/src/blocks/variation-items/index.ts
+++ b/packages/js/product-editor/src/blocks/variation-items/index.ts
@@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { VariationOptionsBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/variation-items/variable-product-tour.tsx b/packages/js/product-editor/src/blocks/variation-items/variable-product-tour.tsx
new file mode 100644
index 00000000000..15a01260669
--- /dev/null
+++ b/packages/js/product-editor/src/blocks/variation-items/variable-product-tour.tsx
@@ -0,0 +1,138 @@
+/**
+ * External dependencies
+ */
+import { createElement, useEffect, useRef, useState } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { TourKit, TourKitTypes } from '@woocommerce/components';
+import {
+ EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME,
+ useUserPreferences,
+} from '@woocommerce/data';
+import { recordEvent } from '@woocommerce/tracks';
+import { useSelect } from '@wordpress/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 { useEntityId } from '@wordpress/core-data';
+
+/**
+ * Internal dependencies
+ */
+import { DEFAULT_PER_PAGE_OPTION } from '../../constants';
+
+export const VariableProductTour: React.FC = () => {
+ const [ isTourOpen, setIsTourOpen ] = useState( false );
+ const productId = useEntityId( 'postType', 'product' );
+ const prevTotalCount = useRef< undefined | number >();
+
+ const { totalCount } = useSelect(
+ ( select ) => {
+ const { getProductVariationsTotalCount } = select(
+ EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME
+ );
+ const requestParams = {
+ product_id: productId,
+ page: 1,
+ per_page: DEFAULT_PER_PAGE_OPTION,
+ order: 'asc',
+ orderby: 'menu_order',
+ };
+ return {
+ totalCount:
+ getProductVariationsTotalCount< number >( requestParams ),
+ };
+ },
+ [ productId ]
+ );
+
+ const {
+ updateUserPreferences,
+ variable_product_block_tour_shown: hasShownTour,
+ } = useUserPreferences();
+
+ const config: TourKitTypes.WooConfig = {
+ placement: 'top',
+ steps: [
+ {
+ referenceElements: {
+ desktop:
+ '.wp-block-woocommerce-product-variation-items-field',
+ },
+ focusElement: {
+ desktop:
+ '.wp-block-woocommerce-product-variation-items-field',
+ },
+ meta: {
+ name: 'product-variations-2',
+ heading: __(
+ '⚡️ This product now has variations',
+ 'woocommerce'
+ ),
+ descriptions: {
+ desktop: __(
+ 'From now on, you’ll manage pricing, shipping, and inventory for each variation individually—just like any other product in your store.',
+ 'woocommerce'
+ ),
+ },
+ primaryButton: {
+ text: __( 'Got it', 'woocommerce' ),
+ },
+ },
+ },
+ ],
+ options: {
+ classNames: [ 'variation-items-product-tour' ],
+ // WooTourKit does not handle merging of default options properly,
+ // so we need to duplicate the effects options here.
+ effects: {
+ arrowIndicator: true,
+ spotlight: {
+ interactivity: {
+ enabled: true,
+ },
+ },
+ },
+ callbacks: {
+ onStepViewOnce: () => {
+ recordEvent( 'variable_product_block_tour_shown', {
+ variable_count: totalCount,
+ } );
+ },
+ },
+ popperModifiers: [
+ {
+ name: 'offset',
+ options: {
+ // 24px for additional padding and 8px for arrow.
+ offset: [ 0, 32 ],
+ },
+ },
+ ],
+ },
+ closeHandler: () => {
+ updateUserPreferences( {
+ variable_product_block_tour_shown: 'yes',
+ } );
+ setIsTourOpen( false );
+
+ recordEvent( 'variable_product_block_tour_dismissed' );
+ },
+ };
+
+ useEffect( () => {
+ const isFirstVariation =
+ prevTotalCount.current !== totalCount &&
+ totalCount > 0 &&
+ prevTotalCount.current === 0;
+ prevTotalCount.current = totalCount;
+ if ( isFirstVariation && ! isTourOpen ) {
+ setIsTourOpen( true );
+ }
+ }, [ totalCount ] );
+
+ if ( hasShownTour === 'yes' || ! isTourOpen ) {
+ return null;
+ }
+
+ return ;
+};
diff --git a/packages/js/product-editor/src/blocks/variation-options/edit.tsx b/packages/js/product-editor/src/blocks/variation-options/edit.tsx
index 3b33bf8468a..543cafc5379 100644
--- a/packages/js/product-editor/src/blocks/variation-options/edit.tsx
+++ b/packages/js/product-editor/src/blocks/variation-options/edit.tsx
@@ -23,6 +23,7 @@ import {
useProductAttributes,
} from '../../hooks/use-product-attributes';
import { AttributeControl } from '../../components/attribute-control';
+import { useProductVariationsHelper } from '../../hooks/use-product-variations-helper';
function manageDefaultAttributes( values: EnhancedProductAttribute[] ) {
return values.reduce< Product[ 'default_attributes' ] >(
@@ -45,6 +46,7 @@ function manageDefaultAttributes( values: EnhancedProductAttribute[] ) {
export function Edit() {
const blockProps = useBlockProps();
+ const { generateProductVariations } = useProductVariationsHelper();
const [ entityAttributes, setEntityAttributes ] = useEntityProp<
ProductAttribute[]
@@ -64,6 +66,7 @@ export function Edit() {
onChange( values ) {
setEntityAttributes( values );
setEntityDefaultAttributes( manageDefaultAttributes( values ) );
+ generateProductVariations( values );
},
} );
diff --git a/packages/js/product-editor/src/blocks/variation-options/index.ts b/packages/js/product-editor/src/blocks/variation-options/index.ts
index 888b5cc021d..ad48fe3aa9e 100644
--- a/packages/js/product-editor/src/blocks/variation-options/index.ts
+++ b/packages/js/product-editor/src/blocks/variation-options/index.ts
@@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { VariationOptionsBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/blocks/variations/edit.tsx b/packages/js/product-editor/src/blocks/variations/edit.tsx
index 9b6a40e27c0..7cc7853f655 100644
--- a/packages/js/product-editor/src/blocks/variations/edit.tsx
+++ b/packages/js/product-editor/src/blocks/variations/edit.tsx
@@ -34,6 +34,7 @@ import {
useProductAttributes,
} from '../../hooks/use-product-attributes';
import { getAttributeId } from '../../components/attribute-control/utils';
+import { useProductVariationsHelper } from '../../hooks/use-product-variations-helper';
function hasAttributesUsedForVariations(
productAttributes: Product[ 'attributes' ]
@@ -56,6 +57,7 @@ export function Edit( {
}: BlockEditProps< VariationsBlockAttributes > ) {
const { description } = attributes;
+ const { generateProductVariations } = useProductVariationsHelper();
const [ isNewModalVisible, setIsNewModalVisible ] = useState( false );
const [ productAttributes, setProductAttributes ] = useEntityProp<
Product[ 'attributes' ]
@@ -74,6 +76,7 @@ export function Edit( {
setDefaultProductAttributes(
getFirstOptionFromEachAttribute( values )
);
+ generateProductVariations( values );
},
}
);
diff --git a/packages/js/product-editor/src/blocks/variations/index.ts b/packages/js/product-editor/src/blocks/variations/index.ts
index d08313a98b2..2dc2e6bbc84 100644
--- a/packages/js/product-editor/src/blocks/variations/index.ts
+++ b/packages/js/product-editor/src/blocks/variations/index.ts
@@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks';
/**
* Internal dependencies
*/
-import { initBlock } from '../../utils/init-blocks';
+import { initBlock } from '../../utils/init-block';
import blockConfiguration from './block.json';
import { Edit } from './edit';
import { VariationsBlockAttributes } from './types';
diff --git a/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx b/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx
index bcebdcfd431..b6948599b4a 100644
--- a/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx
+++ b/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx
@@ -24,6 +24,13 @@ jest.mock( '@woocommerce/navigation', () => ( {
getQuery: jest.fn().mockReturnValue( {} ),
} ) );
+const blockProps = {
+ setAttributes: () => {},
+ className: '',
+ clientId: '',
+ isSelected: false,
+};
+
function MockTabs( { onChange = jest.fn() } ) {
const [ selected, setSelected ] = useState< string | null >( null );
const mockContext = {
@@ -39,15 +46,18 @@ function MockTabs( { onChange = jest.fn() } ) {
} }
/>
diff --git a/packages/js/product-editor/src/components/variations-table/variations-table.tsx b/packages/js/product-editor/src/components/variations-table/variations-table.tsx
index 4f16b59accf..c57d599e91c 100644
--- a/packages/js/product-editor/src/components/variations-table/variations-table.tsx
+++ b/packages/js/product-editor/src/components/variations-table/variations-table.tsx
@@ -5,6 +5,7 @@ import { __ } from '@wordpress/i18n';
import { Button, Spinner, Tooltip } from '@wordpress/components';
import {
EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME,
+ ProductAttribute,
ProductVariation,
} from '@woocommerce/data';
import {
@@ -23,7 +24,7 @@ import { CurrencyContext } from '@woocommerce/currency';
// 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 { useEntityId } from '@wordpress/core-data';
+import { useEntityId, useEntityProp } from '@wordpress/core-data';
/**
* Internal dependencies
@@ -46,6 +47,16 @@ export function VariationsTable() {
const [ isUpdating, setIsUpdating ] = useState< Record< string, boolean > >(
{}
);
+ const [ entityAttributes ] = useEntityProp< ProductAttribute[] >(
+ 'postType',
+ 'product',
+ 'attributes'
+ );
+ const variableAttributeTags = entityAttributes
+ .filter( ( attr ) => attr.variation )
+ .map( ( attr ) => attr.options )
+ .flat();
+
const productId = useEntityId( 'postType', 'product' );
const context = useContext( CurrencyContext );
const { formatAmount } = context;
@@ -114,34 +125,45 @@ export function VariationsTable() {
{ variations.map( ( variation ) => (
- { variation.attributes.map( ( attribute ) => {
- const tag = (
- /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
- /* @ts-ignore Additional props are not required. */
-
- );
+ { variation.attributes
+ .filter( ( attribute ) =>
+ variableAttributeTags.includes(
+ attribute.option
+ )
+ )
+ .map( ( attribute ) => {
+ const tag = (
+ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
+ /* @ts-ignore Additional props are not required. */
+
+ );
- return attribute.option.length <=
- PRODUCT_VARIATION_TITLE_LIMIT ? (
- tag
- ) : (
-
- { tag }
-
- );
- } ) }
+ return attribute.option.length <=
+ PRODUCT_VARIATION_TITLE_LIMIT ? (
+ tag
+ ) : (
+
+ { tag }
+
+ );
+ } ) }
void;
+ onChange: ( attributes: ProductAttribute[] ) => void;
productId?: number;
};
@@ -79,11 +79,11 @@ export function useProductAttributes( {
};
const getAugmentedAttributes = (
- atts: ProductAttribute[],
+ atts: EnhancedProductAttribute[],
variation: boolean,
startPosition: number
- ) => {
- return atts.map( ( attribute, index ) => ( {
+ ): ProductAttribute[] => {
+ return atts.map( ( { isDefault, terms, ...attribute }, index ) => ( {
...attribute,
variation,
position: startPosition + index,
diff --git a/packages/js/product-editor/src/hooks/use-product-variations-helper.ts b/packages/js/product-editor/src/hooks/use-product-variations-helper.ts
new file mode 100644
index 00000000000..e105654997d
--- /dev/null
+++ b/packages/js/product-editor/src/hooks/use-product-variations-helper.ts
@@ -0,0 +1,77 @@
+/**
+ * External dependencies
+ */
+import { useDispatch } from '@wordpress/data';
+import { useEntityProp } from '@wordpress/core-data';
+import { useCallback, useState } from '@wordpress/element';
+import {
+ Product,
+ EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME,
+} from '@woocommerce/data';
+
+/**
+ * Internal dependencies
+ */
+import { EnhancedProductAttribute } from './use-product-attributes';
+
+export function useProductVariationsHelper() {
+ const [ productId ] = useEntityProp< number >(
+ 'postType',
+ 'product',
+ 'id'
+ );
+ const { saveEntityRecord } = useDispatch( 'core' );
+ const {
+ generateProductVariations: _generateProductVariations,
+ invalidateResolutionForStoreSelector,
+ } = useDispatch( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME );
+
+ const [ isGenerating, setIsGenerating ] = useState( false );
+
+ const generateProductVariations = useCallback(
+ async ( attributes: EnhancedProductAttribute[] ) => {
+ setIsGenerating( true );
+
+ const updateProductAttributes = async () => {
+ const hasVariableAttribute = attributes.some(
+ ( attr ) => attr.variation
+ );
+ await saveEntityRecord< Promise< Product > >(
+ 'postType',
+ 'product',
+ {
+ id: productId,
+ type: hasVariableAttribute ? 'variable' : 'simple',
+ attributes,
+ }
+ );
+ };
+
+ return updateProductAttributes()
+ .then( () => {
+ return _generateProductVariations< { count: number } >( {
+ product_id: productId,
+ } );
+ } )
+ .then( ( data ) => {
+ if ( data.count > 0 ) {
+ invalidateResolutionForStoreSelector(
+ 'getProductVariations'
+ );
+ return invalidateResolutionForStoreSelector(
+ 'getProductVariationsTotalCount'
+ );
+ }
+ } )
+ .finally( () => {
+ setIsGenerating( false );
+ } );
+ },
+ []
+ );
+
+ return {
+ generateProductVariations,
+ isGenerating,
+ };
+}
diff --git a/packages/js/product-editor/src/utils/index.ts b/packages/js/product-editor/src/utils/index.ts
index a546a1a8653..bf3e695d1c6 100644
--- a/packages/js/product-editor/src/utils/index.ts
+++ b/packages/js/product-editor/src/utils/index.ts
@@ -22,7 +22,7 @@ import { isValidEmail } from './validate-email';
export * from './create-ordered-children';
export * from './sort-fills-by-order';
-export * from './init-blocks';
+export * from './init-block';
export * from './product-apifetch-middleware';
export * from './sift';
diff --git a/packages/js/product-editor/src/utils/init-block.ts b/packages/js/product-editor/src/utils/init-block.ts
index 31ab31bd3c5..06404008120 100644
--- a/packages/js/product-editor/src/utils/init-block.ts
+++ b/packages/js/product-editor/src/utils/init-block.ts
@@ -2,38 +2,30 @@
* External dependencies
*/
import {
+ Block,
BlockConfiguration,
- BlockEditProps,
registerBlockType,
} from '@wordpress/blocks';
-import { ComponentType } from 'react';
-type BlockRepresentation = {
- name: string;
- metadata: BlockConfiguration;
- settings: Partial< Omit< BlockConfiguration, 'edit' > > & {
- readonly edit?:
- | ComponentType<
- BlockEditProps< object > & {
- context?: Record< string, unknown >;
- }
- >
- | undefined;
- };
-};
+interface BlockRepresentation< T extends Record< string, object > > {
+ name?: string;
+ metadata: BlockConfiguration< T >;
+ settings: Partial< BlockConfiguration< T > >;
+}
/**
* Function to register an individual block.
*
- * @param {Object} block The block to be registered.
- *
- * @return {WPBlockType|void} The block, if it has been successfully registered;
- * otherwise `undefined`.
+ * @param block The block to be registered.
+ * @return The block, if it has been successfully registered; otherwise `undefined`.
*/
-export default function initBlock( block: BlockRepresentation ) {
+export function initBlock<
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ T extends Record< string, any > = Record< string, any >
+>( block: BlockRepresentation< T > ): Block< T > | undefined {
if ( ! block ) {
return;
}
const { metadata, settings, name } = block;
- return registerBlockType( { name, ...metadata }, settings );
+ return registerBlockType< T >( { name, ...metadata }, settings );
}
diff --git a/packages/js/product-editor/src/utils/init-blocks.ts b/packages/js/product-editor/src/utils/init-blocks.ts
deleted file mode 100644
index 06404008120..00000000000
--- a/packages/js/product-editor/src/utils/init-blocks.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * External dependencies
- */
-import {
- Block,
- BlockConfiguration,
- registerBlockType,
-} from '@wordpress/blocks';
-
-interface BlockRepresentation< T extends Record< string, object > > {
- name?: string;
- metadata: BlockConfiguration< T >;
- settings: Partial< BlockConfiguration< T > >;
-}
-
-/**
- * Function to register an individual block.
- *
- * @param block The block to be registered.
- * @return The block, if it has been successfully registered; otherwise `undefined`.
- */
-export function initBlock<
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- T extends Record< string, any > = Record< string, any >
->( block: BlockRepresentation< T > ): Block< T > | undefined {
- if ( ! block ) {
- return;
- }
- const { metadata, settings, name } = block;
- return registerBlockType< T >( { name, ...metadata }, settings );
-}
diff --git a/plugins/woocommerce-admin/client/products/fields/options/index.ts b/plugins/woocommerce-admin/client/products/fields/options/index.ts
deleted file mode 100644
index 5f30ef383a5..00000000000
--- a/plugins/woocommerce-admin/client/products/fields/options/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './options';
diff --git a/plugins/woocommerce-admin/client/products/fields/options/options.tsx b/plugins/woocommerce-admin/client/products/fields/options/options.tsx
deleted file mode 100644
index f4bd7ae1b98..00000000000
--- a/plugins/woocommerce-admin/client/products/fields/options/options.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * External dependencies
- */
-import { __ } from '@wordpress/i18n';
-import { Product, ProductAttribute } from '@woocommerce/data';
-import { recordEvent } from '@woocommerce/tracks';
-import { useFormContext } from '@woocommerce/components';
-import { AttributeControl } from '@woocommerce/product-editor/src/components/attribute-control';
-import { useProductAttributes } from '@woocommerce/product-editor/src/hooks/use-product-attributes';
-
-/**
- * Internal dependencies
- */
-import { useProductVariationsHelper } from '../../hooks/use-product-variations-helper';
-
-type OptionsProps = {
- value: ProductAttribute[];
- onChange: ( value: ProductAttribute[] ) => void;
- productId?: number;
-};
-
-export const Options: React.FC< OptionsProps > = ( {
- value,
- onChange,
- productId,
-} ) => {
- const { values } = useFormContext< Product >();
- const { generateProductVariations } = useProductVariationsHelper();
-
- const { attributes, handleChange } = useProductAttributes( {
- allAttributes: value,
- isVariationAttributes: true,
- onChange: ( newAttributes ) => {
- onChange( newAttributes );
- generateProductVariations( {
- ...values,
- attributes: newAttributes,
- } );
- },
- productId,
- } );
-
- return (
-
{
- recordEvent( 'product_add_options_modal_add_button_click' );
- } }
- onChange={ handleChange }
- onNewModalCancel={ () => {
- recordEvent( 'product_add_options_modal_cancel_button_click' );
- } }
- onNewModalOpen={ () => {
- if ( ! attributes.length ) {
- recordEvent( 'product_add_first_option_button_click' );
- return;
- }
- recordEvent( 'product_add_option_button' );
- } }
- uiStrings={ {
- emptyStateSubtitle: __( 'No options yet', 'woocommerce' ),
- newAttributeListItemLabel: __( 'Add option', 'woocommerce' ),
- newAttributeModalTitle: __( 'Add options', 'woocommerce' ),
- globalAttributeHelperMessage: __(
- `You can change the option's name in {{link}}Attributes{{/link}}.`,
- 'woocommerce'
- ),
- } }
- onRemove={ () =>
- recordEvent(
- 'product_remove_option_confirmation_confirm_click'
- )
- }
- onRemoveCancel={ () =>
- recordEvent( 'product_remove_option_confirmation_cancel_click' )
- }
- />
- );
-};
diff --git a/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx b/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx
index 179650d0b33..fa1c59c2d0f 100644
--- a/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx
+++ b/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx
@@ -20,7 +20,6 @@ import { PricingSectionFills } from './pricing-section';
import { InventorySectionFills } from './inventory-section';
import { AttributesSectionFills } from './attributes-section';
import { ImagesSectionFills } from './images-section';
-import { OptionsSection } from '../sections/options-section';
import { ProductVariationsSection } from '../sections/product-variations-section';
import {
TAB_GENERAL_ID,
@@ -113,7 +112,6 @@ const Tabs = () => {
tabProps={ tabPropData.options }
>
<>
-
>
diff --git a/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts b/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts
deleted file mode 100644
index f999a67be3e..00000000000
--- a/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * External dependencies
- */
-import { AUTO_DRAFT_NAME } from '@woocommerce/product-editor';
-import { useDispatch } from '@wordpress/data';
-import { useCallback, useState } from '@wordpress/element';
-import {
- Product,
- EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME,
- PRODUCTS_STORE_NAME,
-} from '@woocommerce/data';
-import { useFormContext } from '@woocommerce/components';
-
-export function useProductVariationsHelper() {
- const {
- generateProductVariations: _generateProductVariations,
- invalidateResolutionForStoreSelector,
- } = useDispatch( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME );
- const { createProduct, updateProduct } = useDispatch( PRODUCTS_STORE_NAME );
- const { resetForm } = useFormContext< Product >();
-
- const [ isGenerating, setIsGenerating ] = useState( false );
-
- const generateProductVariations = useCallback(
- async ( product: Partial< Product > ) => {
- setIsGenerating( true );
-
- const createOrUpdateProduct = product.id
- ? () =>
- updateProduct< Promise< Product > >(
- product.id,
- product
- )
- : () => {
- return createProduct< Promise< Product > >( {
- ...product,
- status: 'auto-draft',
- name: product.name || AUTO_DRAFT_NAME,
- } );
- };
-
- return createOrUpdateProduct()
- .then( ( createdOrUpdatedProduct ) => {
- if ( ! product.id ) {
- resetForm( {
- ...createdOrUpdatedProduct,
- name: product.name || '',
- } );
- }
- return _generateProductVariations( {
- product_id: createdOrUpdatedProduct.id,
- } );
- } )
- .then( () => {
- return invalidateResolutionForStoreSelector(
- 'getProductVariations'
- );
- } )
- .finally( () => {
- setIsGenerating( false );
- } );
- },
- []
- );
-
- return {
- generateProductVariations,
- isGenerating,
- };
-}
diff --git a/plugins/woocommerce-admin/client/products/sections/options-section.scss b/plugins/woocommerce-admin/client/products/sections/options-section.scss
deleted file mode 100644
index 8090c6112dd..00000000000
--- a/plugins/woocommerce-admin/client/products/sections/options-section.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.woocommerce-product-options-section.woocommerce-form-section {
- .woocommerce-form-section__content {
- padding: 0;
- border: 0;
- }
-}
diff --git a/plugins/woocommerce-admin/client/products/sections/options-section.tsx b/plugins/woocommerce-admin/client/products/sections/options-section.tsx
deleted file mode 100644
index 821ffe9e9ab..00000000000
--- a/plugins/woocommerce-admin/client/products/sections/options-section.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * External dependencies
- */
-import { __ } from '@wordpress/i18n';
-import { Link, useFormContext } from '@woocommerce/components';
-import { Product } from '@woocommerce/data';
-import { recordEvent } from '@woocommerce/tracks';
-
-/**
- * Internal dependencies
- */
-import './options-section.scss';
-import { ProductSectionLayout } from '../layout/product-section-layout';
-import { Options } from '../fields/options';
-
-export const OptionsSection: React.FC = () => {
- const {
- getInputProps,
- values: { id: productId },
- } = useFormContext< Product >();
-
- return (
-
-
- { __(
- 'Add and manage options, such as size and color, for customers to choose on the product page.',
- 'woocommerce'
- ) }
-
- {
- recordEvent( 'learn_more_about_options_help' );
- } }
- >
- { __( 'Learn more about options', 'woocommerce' ) }
-
- >
- }
- >
-
-
- );
-};
diff --git a/plugins/woocommerce/changelog/update-39453_simple_to_variable_product b/plugins/woocommerce/changelog/update-39453_simple_to_variable_product
new file mode 100644
index 00000000000..61811ccbfca
--- /dev/null
+++ b/plugins/woocommerce/changelog/update-39453_simple_to_variable_product
@@ -0,0 +1,4 @@
+Significance: minor
+Type: dev
+
+Remove unused variation option components
diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php
index c9852fe88b9..470054b98b7 100644
--- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php
+++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php
@@ -46,6 +46,7 @@ class Init {
add_action( 'admin_enqueue_scripts', array( $this, 'dequeue_conflicting_styles' ), 100 );
add_action( 'get_edit_post_link', array( $this, 'update_edit_product_link' ), 10, 2 );
}
+ add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_filter( 'woocommerce_register_post_type_product', array( $this, 'add_product_template' ) );
@@ -778,6 +779,21 @@ class Init {
return $args;
}
+ /**
+ * Adds fields so that we can store user preferences for the variations block.
+ *
+ * @param array $user_data_fields User data fields.
+ * @return array
+ */
+ public function add_user_data_fields( $user_data_fields ) {
+ return array_merge(
+ $user_data_fields,
+ array(
+ 'variable_product_block_tour_shown',
+ )
+ );
+ }
+
/**
* Sets the current screen to the block editor if a wc-admin page.
*/