Commit 782059ce41 for woocommerce

commit 782059ce41997a152637524b1a9e8752fdc2d292
Author: Amit Raj <77401999+amitraj2203@users.noreply.github.com>
Date:   Thu Jan 15 13:58:53 2026 +0530

    Refactor Product Elements Blocks to use ToolsPanel (#62712)

    * Refactor WidthPanel to use ToolsPanel in Button block

    * Refactor Edit component to use ToolsPanel in Image block

    * Refactor TitleEdit component to use ToolsPanel in Title block

    * Add changelog file

    * Set default linkTarget to '_self' in TitleEdit component

    * Fix lint error

diff --git a/plugins/woocommerce/changelog/59464-use-toolspanel-in-product-elements-blocks b/plugins/woocommerce/changelog/59464-use-toolspanel-in-product-elements-blocks
new file mode 100644
index 0000000000..79a38acfca
--- /dev/null
+++ b/plugins/woocommerce/changelog/59464-use-toolspanel-in-product-elements-blocks
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Removes PanelBody and uses ToolsPanel for Button, Image and Title blocks
diff --git a/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/button/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/button/edit.tsx
index 2e403d7562..6d1e8ccb30 100644
--- a/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/button/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/button/edit.tsx
@@ -2,12 +2,6 @@
  * External dependencies
  */
 import clsx from 'clsx';
-import {
-	Disabled,
-	Button,
-	ButtonGroup,
-	PanelBody,
-} from '@wordpress/components';
 import { __ } from '@wordpress/i18n';
 import {
 	AlignmentToolbar,
@@ -19,6 +13,15 @@ import type { BlockEditProps } from '@wordpress/blocks';
 import { useEffect } from '@wordpress/element';
 import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';
 import { useProduct } from '@woocommerce/entities';
+import {
+	Disabled,
+	Button,
+	ButtonGroup,
+	// 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
@@ -26,6 +29,10 @@ import { useProduct } from '@woocommerce/entities';
 import Block from './block';
 import { BlockAttributes } from './types';

+const DEFAULT_ATTRIBUTES = {
+	width: undefined,
+};
+
 function WidthPanel( {
 	selectedWidth,
 	setAttributes,
@@ -42,26 +49,40 @@ function WidthPanel( {
 	}

 	return (
-		<PanelBody title={ __( 'Width settings', 'woocommerce' ) }>
-			<ButtonGroup aria-label={ __( 'Button width', 'woocommerce' ) }>
-				{ [ 25, 50, 75, 100 ].map( ( widthValue ) => {
-					return (
-						<Button
-							key={ widthValue }
-							isSmall
-							variant={
-								widthValue === selectedWidth
-									? 'primary'
-									: undefined
-							}
-							onClick={ () => handleChange( widthValue ) }
-						>
-							{ widthValue }%
-						</Button>
-					);
-				} ) }
-			</ButtonGroup>
-		</PanelBody>
+		<ToolsPanel
+			label={ __( 'Width settings', 'woocommerce' ) }
+			resetAll={ () =>
+				setAttributes( { width: DEFAULT_ATTRIBUTES.width } )
+			}
+		>
+			<ToolsPanelItem
+				label={ __( 'Button width', 'woocommerce' ) }
+				hasValue={ () => selectedWidth !== DEFAULT_ATTRIBUTES.width }
+				onDeselect={ () =>
+					setAttributes( { width: DEFAULT_ATTRIBUTES.width } )
+				}
+				isShownByDefault
+			>
+				<ButtonGroup aria-label={ __( 'Button width', 'woocommerce' ) }>
+					{ [ 25, 50, 75, 100 ].map( ( widthValue ) => {
+						return (
+							<Button
+								key={ widthValue }
+								isSmall
+								variant={
+									widthValue === selectedWidth
+										? 'primary'
+										: undefined
+								}
+								onClick={ () => handleChange( widthValue ) }
+							>
+								{ widthValue }%
+							</Button>
+						);
+					} ) }
+				</ButtonGroup>
+			</ToolsPanelItem>
+		</ToolsPanel>
 	);
 }

diff --git a/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/image/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/image/edit.tsx
index fc20e50d8b..b042678c9c 100644
--- a/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/image/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/image/edit.tsx
@@ -20,7 +20,6 @@ import { isBoolean } from '@woocommerce/types';
 import type { BlockEditProps } from '@wordpress/blocks';
 import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';
 import {
-	PanelBody,
 	ToggleControl,
 	// eslint-disable-next-line @typescript-eslint/ban-ts-comment
 	// @ts-ignore - Ignoring because `__experimentalToggleGroupControl` is not yet in the type definitions.
@@ -30,6 +29,10 @@ import {
 	// @ts-ignore - Ignoring because `__experimentalToggleGroupControl` is not yet in the type definitions.
 	// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
 	__experimentalToggleGroupControlOption as ToggleGroupControlOption,
+	// 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';

 /**
@@ -49,6 +52,11 @@ const TEMPLATE = [
 	],
 ];

+const DEFAULT_ATTRIBUTES = {
+	showProductLink: true,
+	imageSizing: ImageSizing.SINGLE,
+};
+
 const Edit = ( {
 	attributes,
 	setAttributes,
@@ -137,63 +145,103 @@ const Edit = ( {
 						height={ height }
 						setAttributes={ setAttributes }
 					/>
-					<PanelBody title={ __( 'Content', 'woocommerce' ) }>
-						<ToggleControl
+					<ToolsPanel
+						label={ __( 'Content', 'woocommerce' ) }
+						resetAll={ () =>
+							setAttributes( {
+								showProductLink:
+									DEFAULT_ATTRIBUTES.showProductLink,
+								imageSizing: DEFAULT_ATTRIBUTES.imageSizing,
+							} )
+						}
+					>
+						<ToolsPanelItem
 							label={ __(
 								'Link to Product Page',
 								'woocommerce'
 							) }
-							help={ __(
-								'Links the image to the single product listing.',
-								'woocommerce'
-							) }
-							checked={ showProductLink }
-							onChange={ () =>
+							hasValue={ () =>
+								showProductLink !==
+								DEFAULT_ATTRIBUTES.showProductLink
+							}
+							onDeselect={ () =>
 								setAttributes( {
-									showProductLink: ! showProductLink,
+									showProductLink:
+										DEFAULT_ATTRIBUTES.showProductLink,
 								} )
 							}
-						/>
-						<ToggleGroupControl
+							isShownByDefault
+						>
+							<ToggleControl
+								label={ __(
+									'Link to Product Page',
+									'woocommerce'
+								) }
+								help={ __(
+									'Links the image to the single product listing.',
+									'woocommerce'
+								) }
+								checked={ showProductLink }
+								onChange={ () =>
+									setAttributes( {
+										showProductLink: ! showProductLink,
+									} )
+								}
+							/>
+						</ToolsPanelItem>
+						<ToolsPanelItem
 							label={ __( 'Resolution', 'woocommerce' ) }
-							isBlock
-							help={
-								! isBlockTheme
-									? createInterpolateElement(
-											__(
-												'Product image cropping can be modified in the <a>Customizer</a>.',
-												'woocommerce'
-											),
-											{
-												a: (
-													// eslint-disable-next-line jsx-a11y/anchor-has-content
-													<a
-														href={ `${ getAdminLink(
-															'customize.php'
-														) }?autofocus[panel]=woocommerce&autofocus[section]=woocommerce_product_images` }
-														target="_blank"
-														rel="noopener noreferrer"
-													/>
-												),
-											}
-									  )
-									: null
+							hasValue={ () =>
+								imageSizing !== DEFAULT_ATTRIBUTES.imageSizing
 							}
-							value={ imageSizing }
-							onChange={ ( value: ImageSizing ) =>
-								setAttributes( { imageSizing: value } )
+							onDeselect={ () =>
+								setAttributes( {
+									imageSizing: DEFAULT_ATTRIBUTES.imageSizing,
+								} )
 							}
+							isShownByDefault
 						>
-							<ToggleGroupControlOption
-								value={ ImageSizing.SINGLE }
-								label={ __( 'Full Size', 'woocommerce' ) }
-							/>
-							<ToggleGroupControlOption
-								value={ ImageSizing.THUMBNAIL }
-								label={ __( 'Thumbnail', 'woocommerce' ) }
-							/>
-						</ToggleGroupControl>
-					</PanelBody>
+							<ToggleGroupControl
+								label={ __( 'Resolution', 'woocommerce' ) }
+								isBlock
+								help={
+									! isBlockTheme
+										? createInterpolateElement(
+												__(
+													'Product image cropping can be modified in the <a>Customizer</a>.',
+													'woocommerce'
+												),
+												{
+													a: (
+														// eslint-disable-next-line jsx-a11y/anchor-has-content
+														<a
+															href={ `${ getAdminLink(
+																'customize.php'
+															) }?autofocus[panel]=woocommerce&autofocus[section]=woocommerce_product_images` }
+															target="_blank"
+															rel="noopener noreferrer"
+														/>
+													),
+												}
+										  )
+										: null
+								}
+								value={ imageSizing }
+								onChange={ ( value: ImageSizing ) =>
+									setAttributes( { imageSizing: value } )
+								}
+							>
+								<ToggleGroupControlOption
+									value={ ImageSizing.SINGLE }
+									label={ __( 'Full Size', 'woocommerce' ) }
+								/>
+								<ToggleGroupControlOption
+									value={ ImageSizing.THUMBNAIL }
+									label={ __( 'Thumbnail', 'woocommerce' ) }
+								/>
+							</ToggleGroupControl>
+						</ToolsPanelItem>
+					</ToolsPanel>
 				</InspectorControls>
 			) }
 			<Block
diff --git a/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/title/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/title/edit.tsx
index 3753a3cee6..a7d87d3d56 100644
--- a/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/title/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/title/edit.tsx
@@ -2,7 +2,6 @@
  * External dependencies
  */
 import { __ } from '@wordpress/i18n';
-import { Disabled, PanelBody, ToggleControl } from '@wordpress/components';
 import {
 	InspectorControls,
 	BlockControls,
@@ -10,6 +9,14 @@ import {
 	useBlockProps,
 } from '@wordpress/block-editor';
 import HeadingToolbar from '@woocommerce/editor-components/heading-toolbar';
+import {
+	Disabled,
+	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
@@ -23,6 +30,11 @@ interface Props {
 	setAttributes: ( attributes: Record< string, unknown > ) => void;
 }

+const DEFAULT_ATTRIBUTES = {
+	showProductLink: true,
+	linkTarget: '_self',
+};
+
 const TitleEdit = ( { attributes, setAttributes }: Props ): JSX.Element => {
 	const blockProps = useBlockProps();
 	const { headingLevel, showProductLink, align, linkTarget } = attributes;
@@ -46,18 +58,52 @@ const TitleEdit = ( { attributes, setAttributes }: Props ): JSX.Element => {
 				/>
 			</BlockControls>
 			<InspectorControls>
-				<PanelBody title={ __( 'Link settings', 'woocommerce' ) }>
-					<ToggleControl
+				<ToolsPanel
+					label={ __( 'Link settings', 'woocommerce' ) }
+					resetAll={ () =>
+						setAttributes( {
+							showProductLink: DEFAULT_ATTRIBUTES.showProductLink,
+							linkTarget: DEFAULT_ATTRIBUTES.linkTarget,
+						} )
+					}
+				>
+					<ToolsPanelItem
 						label={ __( 'Make title a link', 'woocommerce' ) }
-						checked={ showProductLink }
-						onChange={ () =>
+						hasValue={ () =>
+							showProductLink !==
+							DEFAULT_ATTRIBUTES.showProductLink
+						}
+						onDeselect={ () =>
 							setAttributes( {
-								showProductLink: ! showProductLink,
+								showProductLink:
+									DEFAULT_ATTRIBUTES.showProductLink,
 							} )
 						}
-					/>
+						isShownByDefault
+					>
+						<ToggleControl
+							label={ __( 'Make title a link', 'woocommerce' ) }
+							checked={ showProductLink }
+							onChange={ () =>
+								setAttributes( {
+									showProductLink: ! showProductLink,
+								} )
+							}
+						/>
+					</ToolsPanelItem>
 					{ showProductLink && (
-						<>
+						<ToolsPanelItem
+							label={ __( 'Open in new tab', 'woocommerce' ) }
+							hasValue={ () =>
+								linkTarget !== DEFAULT_ATTRIBUTES.linkTarget
+							}
+							onDeselect={ () =>
+								setAttributes( {
+									linkTarget: DEFAULT_ATTRIBUTES.linkTarget,
+								} )
+							}
+							isShownByDefault
+						>
 							<ToggleControl
 								label={ __( 'Open in new tab', 'woocommerce' ) }
 								onChange={ ( value ) =>
@@ -67,9 +113,9 @@ const TitleEdit = ( { attributes, setAttributes }: Props ): JSX.Element => {
 								}
 								checked={ linkTarget === '_blank' }
 							/>
-						</>
+						</ToolsPanelItem>
 					) }
-				</PanelBody>
+				</ToolsPanel>
 			</InspectorControls>
 			<Disabled>
 				<Block { ...attributes } />