Commit ba8854342d for woocommerce

commit ba8854342d6a1c37b3d9d4099fb137600096930f
Author: Amit Raj <77401999+amitraj2203@users.noreply.github.com>
Date:   Thu Feb 5 21:28:42 2026 +0530

    Refactor Product Specifications Block to use ToolsPanel (#62903)

    * Refactor product specifications block to use ToolsPanel

    * Add changelog file

    * Remove redundant resetAllFilter from all three ToolsPanelItem components

    * Refactor Product Specifications block tests to ensure ToolsPanel initialization and section visibility toggling

diff --git a/plugins/woocommerce/changelog/59464-use-toolspanel-in-product-specifications-block b/plugins/woocommerce/changelog/59464-use-toolspanel-in-product-specifications-block
new file mode 100644
index 0000000000..ad7ff3a151
--- /dev/null
+++ b/plugins/woocommerce/changelog/59464-use-toolspanel-in-product-specifications-block
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Removes PanelBody and uses ToolsPanel in Product Specifications block
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-specifications/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-specifications/edit.tsx
index fa6a6564e6..0e14dad5e8 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-specifications/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-specifications/edit.tsx
@@ -6,13 +6,25 @@ import { __ } from '@wordpress/i18n';
 import { useQueryLoopProductContextValidation } from '@woocommerce/base-hooks';
 import { useSelect } from '@wordpress/data';
 import { optionsStore, Product, productsStore } from '@woocommerce/data';
-import { PanelBody, ToggleControl } from '@wordpress/components';
+import {
+	ToggleControl,
+	// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
+	__experimentalToolsPanel as ToolsPanel,
+	// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
+	__experimentalToolsPanelItem as ToolsPanelItem,
+} from '@wordpress/components';

 /**
  * Internal dependencies
  */
 import { ProductSpecificationsEditProps } from './types';

+const DEFAULT_ATTRIBUTES = {
+	showWeight: true,
+	showDimensions: true,
+	showAttributes: true,
+};
+
 const getFormattedDimensions = (
 	dimensions: Product[ 'dimensions' ],
 	dimensionUnit: string
@@ -180,33 +192,79 @@ const Edit = ( {
 	return (
 		<>
 			<InspectorControls>
-				<PanelBody title={ __( 'Display Settings', 'woocommerce' ) }>
-					<ToggleControl
+				<ToolsPanel
+					label={ __( 'Display Settings', 'woocommerce' ) }
+					resetAll={ () => {
+						setAttributes( DEFAULT_ATTRIBUTES );
+					} }
+				>
+					<ToolsPanelItem
 						label={ __( 'Show Weight', 'woocommerce' ) }
-						checked={ showWeight }
-						onChange={ () =>
-							setAttributes( { showWeight: ! showWeight } )
+						hasValue={ () =>
+							showWeight !== DEFAULT_ATTRIBUTES.showWeight
 						}
-					/>
-					<ToggleControl
+						onDeselect={ () =>
+							setAttributes( {
+								showWeight: DEFAULT_ATTRIBUTES.showWeight,
+							} )
+						}
+						isShownByDefault
+					>
+						<ToggleControl
+							label={ __( 'Show Weight', 'woocommerce' ) }
+							checked={ showWeight }
+							onChange={ () =>
+								setAttributes( { showWeight: ! showWeight } )
+							}
+						/>
+					</ToolsPanelItem>
+					<ToolsPanelItem
 						label={ __( 'Show Dimensions', 'woocommerce' ) }
-						checked={ showDimensions }
-						onChange={ () =>
+						hasValue={ () =>
+							showDimensions !== DEFAULT_ATTRIBUTES.showDimensions
+						}
+						onDeselect={ () =>
 							setAttributes( {
-								showDimensions: ! showDimensions,
+								showDimensions:
+									DEFAULT_ATTRIBUTES.showDimensions,
 							} )
 						}
-					/>
-					<ToggleControl
+						isShownByDefault
+					>
+						<ToggleControl
+							label={ __( 'Show Dimensions', 'woocommerce' ) }
+							checked={ showDimensions }
+							onChange={ () =>
+								setAttributes( {
+									showDimensions: ! showDimensions,
+								} )
+							}
+						/>
+					</ToolsPanelItem>
+					<ToolsPanelItem
 						label={ __( 'Show Attributes', 'woocommerce' ) }
-						checked={ showAttributes }
-						onChange={ () =>
+						hasValue={ () =>
+							showAttributes !== DEFAULT_ATTRIBUTES.showAttributes
+						}
+						onDeselect={ () =>
 							setAttributes( {
-								showAttributes: ! showAttributes,
+								showAttributes:
+									DEFAULT_ATTRIBUTES.showAttributes,
 							} )
 						}
-					/>
-				</PanelBody>
+						isShownByDefault
+					>
+						<ToggleControl
+							label={ __( 'Show Attributes', 'woocommerce' ) }
+							checked={ showAttributes }
+							onChange={ () =>
+								setAttributes( {
+									showAttributes: ! showAttributes,
+								} )
+							}
+						/>
+					</ToolsPanelItem>
+				</ToolsPanel>
 			</InspectorControls>
 			<figure { ...blockProps }>
 				<table>
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-specifications/test/block.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-specifications/test/block.ts
index 12f5f6d357..6f8d535654 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-specifications/test/block.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-specifications/test/block.ts
@@ -43,6 +43,13 @@ describe( 'Product Specifications block', () => {
 			if ( displaySettings.getAttribute( 'aria-expanded' ) !== 'true' ) {
 				fireEvent.click( displaySettings );
 			}
+
+			// Wait for ToolsPanel to fully initialize and settle.
+			await waitFor( () => {
+				expect(
+					screen.getByRole( 'checkbox', { name: /Show Weight/i } )
+				).toBeVisible();
+			} );
 		} );

 		test( 'should show all sections by default', () => {
@@ -70,58 +77,70 @@ describe( 'Product Specifications block', () => {
 			).toBeChecked();
 		} );

-		test( 'should hide weight section when toggled off', () => {
+		test( 'should hide weight section when toggled off', async () => {
 			const block = within(
 				screen.getByLabelText( /Block: Product Specifications/i )
 			);

-			fireEvent.click(
-				screen.getByRole( 'checkbox', { name: /Show Weight/i } )
-			);
+			const weightCheckbox = screen.getByRole( 'checkbox', {
+				name: /Show Weight/i,
+			} );
+
+			fireEvent.click( weightCheckbox );
+
+			await waitFor( () => {
+				expect(
+					block.queryByText( /Weight/i )
+				).not.toBeInTheDocument();
+			} );

-			expect( block.queryByText( /Weight/i ) ).not.toBeInTheDocument();
 			expect( block.getByText( /Dimensions/i ) ).toBeInTheDocument();
 			expect( block.getByText( /Test Attribute/i ) ).toBeInTheDocument();
 		} );

-		test( 'should hide dimensions section when toggled off', () => {
+		test( 'should hide dimensions section when toggled off', async () => {
 			const block = within(
 				screen.getByLabelText( /Block: Product Specifications/i )
 			);

-			fireEvent.click(
-				screen.getByRole( 'checkbox', {
-					name: /Show Dimensions/i,
-				} )
-			);
+			const dimensionsCheckbox = screen.getByRole( 'checkbox', {
+				name: /Show Dimensions/i,
+			} );

-			expect( block.getByText( /Weight/i ) ).toBeInTheDocument();
+			fireEvent.click( dimensionsCheckbox );

-			expect(
-				block.queryByText( /Dimensions/i )
-			).not.toBeInTheDocument();
+			await waitFor( () => {
+				expect(
+					block.queryByText( /Dimensions/i )
+				).not.toBeInTheDocument();
+			} );
+
+			expect( block.getByText( /Weight/i ) ).toBeInTheDocument();
 			expect( block.getByText( /Test Attribute/i ) ).toBeInTheDocument();
 		} );

-		test( 'should hide attributes section when toggled off', () => {
+		test( 'should hide attributes section when toggled off', async () => {
 			const block = within(
 				screen.getByLabelText( /Block: Product Specifications/i )
 			);

-			fireEvent.click(
-				screen.getByRole( 'checkbox', {
-					name: /Show Attributes/i,
-				} )
-			);
+			const attributesCheckbox = screen.getByRole( 'checkbox', {
+				name: /Show Attributes/i,
+			} );
+
+			fireEvent.click( attributesCheckbox );
+
+			await waitFor( () => {
+				expect(
+					block.queryByText( /Test Attribute/i )
+				).not.toBeInTheDocument();
+			} );

 			expect( block.getByText( /Weight/i ) ).toBeInTheDocument();
 			expect( block.getByText( /Dimensions/i ) ).toBeInTheDocument();
-			expect(
-				block.queryByText( /Test Attribute/i )
-			).not.toBeInTheDocument();
 		} );

-		test( 'should restore visibility when sections are toggled back on', () => {
+		test( 'should restore visibility when sections are toggled back on', async () => {
 			const block = within(
 				screen.getByLabelText( /Block: Product Specifications/i )
 			);
@@ -137,6 +156,19 @@ describe( 'Product Specifications block', () => {
 				screen.getByRole( 'checkbox', { name: /Show Attributes/i } )
 			);

+			// Wait for all items to be hidden.
+			await waitFor( () => {
+				expect(
+					block.queryByText( /Weight/i )
+				).not.toBeInTheDocument();
+				expect(
+					block.queryByText( /Dimensions/i )
+				).not.toBeInTheDocument();
+				expect(
+					block.queryByText( /Test Attribute/i )
+				).not.toBeInTheDocument();
+			} );
+
 			// Then show them all again
 			fireEvent.click(
 				screen.getByRole( 'checkbox', { name: /Show Weight/i } )
@@ -148,9 +180,14 @@ describe( 'Product Specifications block', () => {
 				screen.getByRole( 'checkbox', { name: /Show Attributes/i } )
 			);

-			expect( block.getByText( /Weight/i ) ).toBeInTheDocument();
-			expect( block.getByText( /Dimensions/i ) ).toBeInTheDocument();
-			expect( block.getByText( /Test Attribute/i ) ).toBeInTheDocument();
+			// Wait for all items to be shown.
+			await waitFor( () => {
+				expect( block.getByText( /Weight/i ) ).toBeInTheDocument();
+				expect( block.getByText( /Dimensions/i ) ).toBeInTheDocument();
+				expect(
+					block.getByText( /Test Attribute/i )
+				).toBeInTheDocument();
+			} );
 		} );
 	} );
 } );