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(),
+ )
+ )
+ )
);
}