Commit d323a2e98f1 for woocommerce

commit d323a2e98f1d32dc9a97814f34f431126034bb49
Author: Tung Du <dinhtungdu@gmail.com>
Date:   Tue Jun 16 11:10:49 2026 +0700

    Product Filter: add option disabling filter drawer (#65671)

    * feat: add option disabling filter drawer

    * chore: changelog

    * fix: only render the content when button is disable

    * fix: simplify product filters desktop styles

    * fix: remove obsolete drawer-disabled styles

    * fix: set product filters button types

    * fix: render disabled product filters inline in editor

diff --git a/plugins/woocommerce/changelog/add-product-filters-disable-responsive b/plugins/woocommerce/changelog/add-product-filters-disable-responsive
new file mode 100644
index 00000000000..cca5437ae71
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-product-filters-disable-responsive
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add option disabling filter drawer on smaller screens.
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/block.json b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/block.json
index 212e91f61d1..58285ee6921 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/block.json
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/block.json
@@ -39,6 +39,10 @@
 		"isPreview": {
 			"type": "boolean",
 			"default": false
+		},
+		"showFilterDrawer": {
+			"type": "boolean",
+			"default": true
 		}
 	},
 	"example": {
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/edit.tsx
index 0fe27fc2d5f..464a9b695dc 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/edit.tsx
@@ -1,8 +1,13 @@
 /**
  * External dependencies
  */
-import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
+import {
+	InnerBlocks,
+	InspectorControls,
+	useBlockProps,
+} from '@wordpress/block-editor';
 import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
+import { PanelBody, ToggleControl } from '@wordpress/components';
 import { __ } from '@wordpress/i18n';
 import { Icon, close } from '@wordpress/icons';
 import { useState } from '@wordpress/element';
@@ -39,8 +44,9 @@ const TEMPLATE: InnerBlockTemplate[] = [
 ];

 export const Edit = ( props: BlockEditProps< BlockAttributes > ) => {
-	const { attributes } = props;
+	const { attributes, setAttributes } = props;
 	const { isPreview } = attributes;
+	const showFilterDrawer = attributes.showFilterDrawer !== false;
 	const [ isOpen, setIsOpen ] = useState( false );

 	const globalColors = getSetting< { background?: string; text?: string } >(
@@ -59,6 +65,7 @@ export const Edit = ( props: BlockEditProps< BlockAttributes > ) => {
 	const blockProps = useBlockProps( {
 		className: clsx( 'wc-block-product-filters', {
 			'is-overlay-opened': isOpen,
+			'is-filter-drawer-disabled': ! showFilterDrawer,
 		} ),
 		style: {
 			'--wc-product-filters-background-color':
@@ -71,60 +78,99 @@ export const Edit = ( props: BlockEditProps< BlockAttributes > ) => {
 		},
 	} );

-	return (
-		<div { ...blockProps }>
-			{ isPreview ? (
-				<div className="wc-block-product-filters__overlay-content">
-					<InnerBlocks templateLock={ false } template={ TEMPLATE } />
-				</div>
-			) : (
-				<>
-					<button
-						className="wc-block-product-filters__open-overlay"
-						onClick={ () => setIsOpen( ! isOpen ) }
-					>
-						<Icon icon={ filterThreeLines } />
-						<span>{ __( 'Filter products', 'woocommerce' ) }</span>
-					</button>
+	let filtersContent: JSX.Element;
+
+	if ( isPreview ) {
+		filtersContent = (
+			<div className="wc-block-product-filters__overlay-content">
+				<InnerBlocks templateLock={ false } template={ TEMPLATE } />
+			</div>
+		);
+	} else if ( showFilterDrawer ) {
+		filtersContent = (
+			<>
+				<button
+					className="wc-block-product-filters__open-overlay"
+					onClick={ () => setIsOpen( ! isOpen ) }
+				>
+					<Icon icon={ filterThreeLines } />
+					<span>{ __( 'Filter products', 'woocommerce' ) }</span>
+				</button>

-					<div className="wc-block-product-filters__overlay">
-						<div className="wc-block-product-filters__overlay-wrapper">
-							<div
-								className="wc-block-product-filters__overlay-dialog"
-								role="dialog"
-							>
-								<header className="wc-block-product-filters__overlay-header">
-									<button
-										className="wc-block-product-filters__close-overlay"
-										onClick={ () => setIsOpen( ! isOpen ) }
-									>
-										<span>
-											{ __( 'Close', 'woocommerce' ) }
-										</span>
-										<Icon icon={ close } />
-									</button>
-								</header>
-								<div className="wc-block-product-filters__overlay-content">
-									<InnerBlocks
-										templateLock={ false }
-										template={ TEMPLATE }
-									/>
-								</div>
-								<footer className="wc-block-product-filters__overlay-footer">
-									<button
-										className="wc-block-product-filters__apply wp-block-button__link wp-element-button"
-										onClick={ () => setIsOpen( ! isOpen ) }
-									>
-										<span>
-											{ __( 'Apply', 'woocommerce' ) }
-										</span>
-									</button>
-								</footer>
+				<div className="wc-block-product-filters__overlay">
+					<div className="wc-block-product-filters__overlay-wrapper">
+						<div
+							className="wc-block-product-filters__overlay-dialog"
+							role="dialog"
+						>
+							<header className="wc-block-product-filters__overlay-header">
+								<button
+									className="wc-block-product-filters__close-overlay"
+									onClick={ () => setIsOpen( ! isOpen ) }
+								>
+									<span>
+										{ __( 'Close', 'woocommerce' ) }
+									</span>
+									<Icon icon={ close } />
+								</button>
+							</header>
+							<div className="wc-block-product-filters__overlay-content">
+								<InnerBlocks
+									templateLock={ false }
+									template={ TEMPLATE }
+								/>
 							</div>
+							<footer className="wc-block-product-filters__overlay-footer">
+								<button
+									className="wc-block-product-filters__apply wp-block-button__link wp-element-button"
+									onClick={ () => setIsOpen( ! isOpen ) }
+								>
+									<span>
+										{ __( 'Apply', 'woocommerce' ) }
+									</span>
+								</button>
+							</footer>
 						</div>
 					</div>
-				</>
-			) }
-		</div>
+				</div>
+			</>
+		);
+	} else {
+		filtersContent = (
+			<div className="wc-block-product-filters__content">
+				<InnerBlocks templateLock={ false } template={ TEMPLATE } />
+			</div>
+		);
+	}
+
+	return (
+		<>
+			<InspectorControls>
+				<PanelBody title={ __( 'Settings', 'woocommerce' ) }>
+					<ToggleControl
+						label={ __(
+							'Use drawer on small screens',
+							'woocommerce'
+						) }
+						help={
+							showFilterDrawer
+								? __(
+										'Shoppers tap the button to open filters.',
+										'woocommerce'
+								  )
+								: __(
+										'Filters are shown directly on the page.',
+										'woocommerce'
+								  )
+						}
+						checked={ showFilterDrawer }
+						onChange={ ( value ) =>
+							setAttributes( { showFilterDrawer: value } )
+						}
+					/>
+				</PanelBody>
+			</InspectorControls>
+			<div { ...blockProps }>{ filtersContent }</div>
+		</>
 	);
 };
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/save.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/save.tsx
index 604c0d6d84a..b00280b8b14 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/save.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/save.tsx
@@ -8,10 +8,17 @@ import clsx from 'clsx';
  * Internal dependencies
  */
 import './editor.scss';
+import { type BlockAttributes } from './types';

-export const Save = (): JSX.Element => {
+export const Save = ( {
+	attributes,
+}: {
+	attributes: BlockAttributes;
+} ): JSX.Element => {
 	const blockProps = useBlockProps.save( {
-		className: clsx( 'wc-block-product-filters' ),
+		className: clsx( 'wc-block-product-filters', {
+			'is-filter-drawer-disabled': attributes.showFilterDrawer === false,
+		} ),
 	} );
 	const innerBlocksProps = useInnerBlocksProps.save( blockProps );
 	return <div { ...innerBlocksProps } />;
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/style.scss
index 11c8cc4c6c7..5da8faffae7 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/style.scss
@@ -69,10 +69,9 @@
 		flex-flow: row-reverse;
 	}

+	.wc-block-product-filters__content,
 	.wc-block-product-filters__overlay-content {
 		display: flex;
-		padding: 0 var(--wp--preset--spacing--40);
-		overflow-y: scroll;
 		flex-grow: 1;
 		flex-direction: column;
 		gap: var(
@@ -85,6 +84,11 @@
 		}
 	}

+	.wc-block-product-filters__overlay-content {
+		padding: 0 var(--wp--preset--spacing--40);
+		overflow-y: scroll;
+	}
+
 	.wc-block-product-filters__overlay-footer {
 		padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
 		box-shadow: 0 -4px 8px 0 #0000001a;
@@ -107,10 +111,11 @@
 		}
 	}

+	&.is-filter-drawer-disabled {
+		display: flex;
+	}
+
 	@include breakpoint(">600px") {
-		// If we add "Always show" option in the future, we can support that behavior
-		// by adding a class to the wrapper and refer it here, like this:
-		// &:not(.always-show) {
 		&,
 		&.is-overlay-opened {
 			display: flex;
@@ -155,6 +160,7 @@
 	}

 	@include breakpoint("<600px") {
+		.wc-block-product-filters__content,
 		.wc-block-product-filters__overlay-content {
 			.wp-block-group {
 				display: block;
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
index 4fcdb870970..1dbd7bd1d6d 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
@@ -54,6 +54,7 @@ export type ProductFiltersContext = {
 export type BlockAttributes = {
 	productId?: string;
 	isPreview: boolean;
+	showFilterDrawer?: boolean;
 };

 export type EditProps = BlockEditProps< BlockAttributes >;
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilters.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilters.php
index 3f2ce89ac69..b7bc9eb90b9 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilters.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilters.php
@@ -111,18 +111,27 @@ class ProductFilters extends AbstractBlock {
 			'forcePageReload' => isset( $block->context['forcePageReload'] ) ? (bool) $block->context['forcePageReload'] : null,
 		);

+		$show_filter_drawer = ! isset( $attributes['showFilterDrawer'] ) || false !== $attributes['showFilterDrawer'];
+		$wrapper_classes    = array( 'wc-block-product-filters' );
+		if ( ! $show_filter_drawer ) {
+			$wrapper_classes[] = 'is-filter-drawer-disabled';
+		}
+
 		$wrapper_attributes = array(
-			'class'                            => 'wc-block-product-filters',
-			'data-wp-interactive'              => $this->get_full_block_name(),
-			'data-wp-init--colors'             => 'callbacks.initColors',
-			'data-wp-watch--scrolling'         => 'callbacks.scrollLimit',
-			'data-wp-watch--active-filters'    => 'callbacks.syncActiveFiltersWithServer',
-			'data-wp-on--keyup'                => 'actions.closeOverlayOnEscape',
-			'data-wp-context'                  => (string) wp_json_encode( $interactivity_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
-			'data-wp-class--is-overlay-opened' => 'context.isOverlayOpened',
-			'style'                            => $this->get_css_variables( $attributes ),
+			'class'                         => implode( ' ', $wrapper_classes ),
+			'data-wp-interactive'           => $this->get_full_block_name(),
+			'data-wp-init--colors'          => 'callbacks.initColors',
+			'data-wp-watch--active-filters' => 'callbacks.syncActiveFiltersWithServer',
+			'data-wp-context'               => (string) wp_json_encode( $interactivity_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
+			'style'                         => $this->get_css_variables( $attributes ),
 		);

+		if ( $show_filter_drawer ) {
+			$wrapper_attributes['data-wp-watch--scrolling']         = 'callbacks.scrollLimit';
+			$wrapper_attributes['data-wp-on--keyup']                = 'actions.closeOverlayOnEscape';
+			$wrapper_attributes['data-wp-class--is-overlay-opened'] = 'context.isOverlayOpened';
+		}
+
 		// TODO: Remove this conditional once the fix is released in WP. https://github.com/woocommerce/gutenberg/pull/4.
 		if ( ! isset( $block->context['productCollectionLocation'] ) ) {
 			$wrapper_attributes['data-wp-router-region'] = $this->generate_navigation_id( $block );
@@ -131,46 +140,55 @@ class ProductFilters extends AbstractBlock {
 		ob_start();
 		?>
 		<div <?php echo get_block_wrapper_attributes( $wrapper_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
-			<button
-				class="wc-block-product-filters__open-overlay"
-				data-wp-on--click="actions.openOverlay"
-			>
-				<?php echo $this->get_svg_icon( 'filter-icon-2' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
-				<span><?php echo esc_html__( 'Filter products', 'woocommerce' ); ?></span>
-			</button>
-			<div class="wc-block-product-filters__overlay">
-				<div class="wc-block-product-filters__overlay-wrapper">
-					<div
-						class="wc-block-product-filters__overlay-dialog"
-						role="dialog"
-						aria-label="<?php echo esc_html__( 'Product Filters', 'woocommerce' ); ?>"
-					>
-						<header class="wc-block-product-filters__overlay-header">
-							<button
-								class="wc-block-product-filters__close-overlay"
-								data-wp-on--click="actions.closeOverlay"
-							>
-								<span><?php echo esc_html__( 'Close', 'woocommerce' ); ?></span>
-								<?php echo $this->get_svg_icon( 'close' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
-							</button>
-						</header>
-						<div class="wc-block-product-filters__overlay-content">
-							<?php echo $inner_blocks; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
-						</div>
-						<footer
-							class="wc-block-product-filters__overlay-footer"
+			<?php if ( $show_filter_drawer ) : ?>
+				<button
+					type="button"
+					class="wc-block-product-filters__open-overlay"
+					data-wp-on--click="actions.openOverlay"
+				>
+					<?php echo $this->get_svg_icon( 'filter-icon-2' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+					<span><?php echo esc_html__( 'Filter products', 'woocommerce' ); ?></span>
+				</button>
+				<div class="wc-block-product-filters__overlay">
+					<div class="wc-block-product-filters__overlay-wrapper">
+						<div
+							class="wc-block-product-filters__overlay-dialog"
+							role="dialog"
+							aria-label="<?php echo esc_html__( 'Product Filters', 'woocommerce' ); ?>"
 						>
-							<button
-								class="wc-block-product-filters__apply wp-element-button"
-								data-wp-interactive="<?php echo esc_attr( $this->get_full_block_name() ); ?>"
-								data-wp-on--click="actions.closeOverlay"
+							<header class="wc-block-product-filters__overlay-header">
+								<button
+									type="button"
+									class="wc-block-product-filters__close-overlay"
+									data-wp-on--click="actions.closeOverlay"
+								>
+									<span><?php echo esc_html__( 'Close', 'woocommerce' ); ?></span>
+									<?php echo $this->get_svg_icon( 'close' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+								</button>
+							</header>
+							<div class="wc-block-product-filters__overlay-content">
+								<?php echo $inner_blocks; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+							</div>
+							<footer
+								class="wc-block-product-filters__overlay-footer"
 							>
-								<span><?php echo esc_html__( 'Apply', 'woocommerce' ); ?></span>
-							</button>
-						</footer>
+								<button
+									type="button"
+									class="wc-block-product-filters__apply wp-element-button"
+									data-wp-interactive="<?php echo esc_attr( $this->get_full_block_name() ); ?>"
+									data-wp-on--click="actions.closeOverlay"
+								>
+									<span><?php echo esc_html__( 'Apply', 'woocommerce' ); ?></span>
+								</button>
+							</footer>
+						</div>
 					</div>
 				</div>
-			</div>
+			<?php else : ?>
+				<div class="wc-block-product-filters__content">
+					<?php echo $inner_blocks; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+				</div>
+			<?php endif; ?>
 		</div>
 		<?php
 		return ob_get_clean();
@@ -234,7 +252,14 @@ class ProductFilters extends AbstractBlock {
 	private function generate_navigation_id( $block ) {
 		return sprintf(
 			'wc-product-filters-%s',
-			md5( wp_json_encode( $block->parsed_block['innerBlocks'] ) )
+			md5(
+				wp_json_encode(
+					array(
+						'attrs'       => $block->parsed_block['attrs'] ?? array(),
+						'innerBlocks' => $block->parsed_block['innerBlocks'] ?? array(),
+					)
+				)
+			)
 		);
 	}