Commit 8a72c04528c for woocommerce
commit 8a72c04528c5675a1333842d5ef933709fb4c90e
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Tue Sep 8 10:08:48 2026 +0200
Add support for typography and border radius styles to the Chips block (#68105)
* Add changelog
* Add support for typography and border radius styles to the Chips block
* Update PHPStan
* Add tests
* Make sure swatches are not squared with border radius set to 0
* Add padding support
* Remove redundants CSS properties
* Fix useMemo() dependencies
* Reduce diff
* Fix tests
* Remove random widths
* refactor: use style engine to parse block support
* fix: only generate style when chip is not color swatches
---------
Co-authored-by: Tung Du <dinhtungdu@gmail.com>
diff --git a/docs/block-development/reference/block-references.md b/docs/block-development/reference/block-references.md
index 2c2cfab7551..94524b66a08 100644
--- a/docs/block-development/reference/block-references.md
+++ b/docs/block-development/reference/block-references.md
@@ -1278,7 +1278,7 @@ Display filter options as chips.
- **Name:** woocommerce/product-filter-chips
- **Category:** woocommerce
- **Ancestor:** woocommerce/product-filter-attribute, woocommerce/product-filter-taxonomy, woocommerce/product-filter-status, woocommerce/add-to-cart-with-options-variation-selector-attribute
-- **Supports:** interactivity, woocommerce (innerBlockDisplayStyle)
+- **Supports:** interactivity, spacing (padding), typography (fontSize), woocommerce (innerBlockDisplayStyle)
- **Attributes:** chipBackground, chipBorder, chipText, customChipBackground, customChipBorder, customChipText, customSelectedChipBackground, customSelectedChipBorder, customSelectedChipText, selectedChipBackground, selectedChipBorder, selectedChipText
## Clear filters - woocommerce/product-filter-clear-button
diff --git a/plugins/woocommerce/changelog/add-filter-chips-styles b/plugins/woocommerce/changelog/add-filter-chips-styles
new file mode 100644
index 00000000000..f6d2a44f760
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-filter-chips-styles
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add support for typography, padding, and border radius styles to the Chips block
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/block.json b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/block.json
index c3a446d6b44..a67f6c0d6f5 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/block.json
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/block.json
@@ -17,6 +17,25 @@
"interactivity": true,
"woocommerce": {
"innerBlockDisplayStyle": true
+ },
+ "typography": {
+ "fontSize": true,
+ "__experimentalLetterSpacing": true,
+ "__experimentalTextTransform": true
+ },
+ "__experimentalBorder": {
+ "radius": true,
+ "__experimentalSkipSerialization": true
+ },
+ "spacing": {
+ "padding": true,
+ "__experimentalSkipSerialization": true
+ }
+ },
+ "selectors": {
+ "border": ".wp-block-woocommerce-product-filter-chips:not(.is-style-swatch) .wc-block-product-filter-chips__item",
+ "spacing": {
+ "padding": ".wp-block-woocommerce-product-filter-chips:not(.is-style-swatch) .wc-block-product-filter-chips__item"
}
},
"usesContext": [ "woocommerce/selectableItems", "woocommerce/attributeId" ],
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/edit.tsx
index f071dc82596..5a19a006e6f 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/edit.tsx
@@ -2,7 +2,6 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
-import { useMemo } from '@wordpress/element';
import { Disabled } from '@wordpress/components';
import clsx from 'clsx';
import { decodeHtmlEntities } from '@woocommerce/utils';
@@ -17,6 +16,12 @@ import {
// @ts-expect-error - no types.
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients,
+ // @ts-expect-error - no types.
+ // eslint-disable-next-line @wordpress/no-unsafe-wp-apis
+ __experimentalUseBorderProps as useBorderProps,
+ // @ts-expect-error - no types.
+ // eslint-disable-next-line @wordpress/no-unsafe-wp-apis
+ __experimentalGetSpacingClassesAndStyles as useSpacingProps,
} from '@wordpress/block-editor';
/**
@@ -30,6 +35,17 @@ import {
isVisualAttributeTermEmpty,
} from '../../../../base/utils/visual-attribute-terms';
+const LOADING_WIDTHS = [
+ '42%',
+ '67%',
+ '31%',
+ '55%',
+ '73%',
+ '28%',
+ '48%',
+ '61%',
+];
+
const Edit = ( props: EditProps ): JSX.Element => {
const colorGradientSettings = useMultipleOriginColorsAndGradients();
const {
@@ -68,6 +84,8 @@ const Edit = ( props: EditProps ): JSX.Element => {
{}
);
const colorVars = getColorVars( attributes );
+ const borderProps = useBorderProps( attributes );
+ const spacingProps = useSpacingProps( attributes );
const blockProps = useBlockProps( {
className: clsx( 'wc-block-product-filter-chips', {
@@ -88,21 +106,6 @@ const Edit = ( props: EditProps ): JSX.Element => {
},
} );
- const loadingState = useMemo( () => {
- return [ ...Array( 10 ) ].map( ( _, i ) => (
- <div
- className="wc-block-product-filter-chips__item"
- key={ i }
- style={ {
- /* stylelint-disable */
- width: Math.floor( Math.random() * ( 100 - 25 ) ) + '%',
- } }
- >
-
- </div>
- ) );
- }, [] );
-
if ( ! items ) {
return <></>;
}
@@ -110,6 +113,28 @@ const Edit = ( props: EditProps ): JSX.Element => {
const threshold = 15;
const isLongList = items.length > threshold;
+ const chipItemClassName = clsx(
+ 'wc-block-product-filter-chips__item',
+ ! hasVisualSwatches && borderProps.className,
+ ! hasVisualSwatches && spacingProps.className
+ );
+ const chipItemStyle = hasVisualSwatches
+ ? undefined
+ : { ...borderProps.style, ...spacingProps.style };
+ const loadingState = LOADING_WIDTHS.map( ( width, i ) => (
+ <div
+ className={ chipItemClassName }
+ key={ i }
+ style={ {
+ ...chipItemStyle,
+ /* stylelint-disable */
+ width,
+ } }
+ >
+
+ </div>
+ ) );
+
return (
<>
<div { ...blockProps }>
@@ -123,7 +148,8 @@ const Edit = ( props: EditProps ): JSX.Element => {
).map( ( item, index ) => (
<div
key={ index }
- className="wc-block-product-filter-chips__item"
+ className={ chipItemClassName }
+ style={ chipItemStyle }
aria-checked={ !! item.selected }
>
<span className="wc-block-product-filter-chips__label">
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss
index cdd0d1a403f..c85cd2578a3 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss
@@ -1,3 +1,7 @@
+:where(.wp-block-woocommerce-product-filter-chips) {
+ font-size: 0.8em;
+}
+
:where(.wc-block-product-filter-chips__fieldset) {
display: contents;
}
@@ -14,8 +18,10 @@
appearance: none;
background: transparent;
border-radius: 2px;
- font-size: 0.8em;
font-family: inherit;
+ font-size: inherit;
+ letter-spacing: inherit;
+ text-transform: inherit;
cursor: pointer;
color: inherit;
@@ -94,7 +100,6 @@
border: 0;
padding: 0;
font: inherit;
- font-size: 0.875em;
color: inherit;
text-decoration: underline;
cursor: pointer;
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 6df50b7b695..e515c8ab6cf 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -51309,18 +51309,6 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/ProductFilterChips.php
- -
- message: '#^Parameter \#1 \$text of function esc_attr expects string, string\|true given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/ProductFilterChips.php
-
- -
- message: '#^Parameter \#1 \$text of function esc_attr expects string, string\|true\|null given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/ProductFilterChips.php
-
-
message: '#^Access to property \$context on an unknown class Automattic\\WooCommerce\\Blocks\\BlockTypes\\WP_Block\.$#'
identifier: class.notFound
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterChips.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterChips.php
index c45119fa9b9..eff9bfa08cb 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterChips.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductFilterChips.php
@@ -52,15 +52,35 @@ final class ProductFilterChips extends AbstractBlock {
$items = is_array( $block_context['items'] ?? null ) ? $block_context['items'] : array();
$store_namespace = $block_context['storeNamespace'] ?? 'woocommerce/product-filters';
$display_limit = 'woocommerce/product-filters' === $store_namespace ? 15 : 30;
- $classes = '';
- $style = '';
- $tags = new \WP_HTML_Tag_Processor( $content );
+ $classes = '';
+ $style = '';
+ $tags = new \WP_HTML_Tag_Processor( $content );
if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filter-chips' ) ) ) {
- $classes = $tags->get_attribute( 'class' );
- $style = $tags->get_attribute( 'style' );
+ $saved_classes = $tags->get_attribute( 'class' );
+ $saved_style = $tags->get_attribute( 'style' );
+ $classes = is_string( $saved_classes ) ? $saved_classes : '';
+ $style = is_string( $saved_style ) ? $saved_style : '';
}
+ $has_visual_swatches = self::has_visual_swatches( $items );
+ $item_classes_and_styles = wp_parse_args(
+ $has_visual_swatches ? array() : wp_style_engine_get_styles(
+ array(
+ 'border' => array(
+ 'radius' => $attributes['style']['border']['radius'] ?? null,
+ ),
+ 'spacing' => array(
+ 'padding' => $attributes['style']['spacing']['padding'] ?? null,
+ ),
+ )
+ ),
+ array(
+ 'css' => '',
+ 'classnames' => '',
+ )
+ );
+
$wrapper_attributes = array(
'data-wp-interactive' => 'woocommerce/product-filter-chips',
'data-wp-init--colors' => 'callbacks.initColors',
@@ -94,12 +114,11 @@ final class ProductFilterChips extends AbstractBlock {
$visible_items = array_merge( $first_items, $overflow_selected_items );
$hidden_count = count( $items ) - count( $visible_items );
- $first_item = reset( $items );
- $show_counts = is_array( $first_item ) && array_key_exists( 'count', $first_item );
- $has_visual_swatches = self::has_visual_swatches( $items );
- $button_role = 'single' === $block_context['selectionMode'] ? 'radio' : 'checkbox';
+ $first_item = reset( $items );
+ $show_counts = is_array( $first_item ) && array_key_exists( 'count', $first_item );
+ $button_role = 'single' === $block_context['selectionMode'] ? 'radio' : 'checkbox';
- if ( $has_visual_swatches && is_string( $classes ) && ! str_contains( $classes, 'is-style-swatch' ) ) {
+ if ( $has_visual_swatches && ! str_contains( $classes, 'is-style-swatch' ) ) {
$classes .= ' is-style-swatch';
$wrapper_attributes['class'] = esc_attr( $classes );
}
@@ -116,7 +135,10 @@ final class ProductFilterChips extends AbstractBlock {
foreach ( $visible_items as $item ) :
?>
<button
- class="wc-block-product-filter-chips__item"
+ class="wc-block-product-filter-chips__item <?php echo esc_attr( $item_classes_and_styles['classnames'] ); ?>"
+ <?php if ( $item_classes_and_styles['css'] ) : ?>
+ style="<?php echo esc_attr( $item_classes_and_styles['css'] ); ?>"
+ <?php endif; ?>
type="button"
role="<?php echo esc_attr( $button_role ); ?>"
id="<?php echo esc_attr( $item['id'] ); ?>"
@@ -166,7 +188,10 @@ final class ProductFilterChips extends AbstractBlock {
data-wp-each-key="context.item.id"
>
<button
- class="wc-block-product-filter-chips__item"
+ class="wc-block-product-filter-chips__item <?php echo esc_attr( $item_classes_and_styles['classnames'] ); ?>"
+ <?php if ( $item_classes_and_styles['css'] ) : ?>
+ style="<?php echo esc_attr( $item_classes_and_styles['css'] ); ?>"
+ <?php endif; ?>
type="button"
role="<?php echo esc_attr( $button_role ); ?>"
data-wp-bind--id="context.item.id"
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductFilterChipsTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductFilterChipsTest.php
new file mode 100644
index 00000000000..196bf2ab516
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductFilterChipsTest.php
@@ -0,0 +1,130 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes;
+
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the Product Filter Chips block type.
+ */
+class ProductFilterChipsTest extends WC_Unit_Test_Case {
+
+ /**
+ * @testdox Typography is applied to the wrapper and border radius to chip items.
+ * @covers \Automattic\WooCommerce\Blocks\BlockTypes\ProductFilterChips::render
+ */
+ public function test_renders_typography_on_wrapper_and_border_radius_on_items(): void {
+ $markup = $this->render_chips(
+ array(
+ 'style' => array(
+ 'border' => array(
+ 'radius' => '12px',
+ ),
+ 'typography' => array(
+ 'textTransform' => 'uppercase',
+ ),
+ ),
+ ),
+ array(
+ array(
+ 'id' => 'item-red',
+ 'label' => 'Red',
+ 'value' => 'red',
+ 'selected' => false,
+ ),
+ )
+ );
+
+ $wrapper_style = $this->get_style( $markup, 'wc-block-product-filter-chips' );
+ $item_style = $this->get_style( $markup, 'wc-block-product-filter-chips__item' );
+
+ $this->assertStringContainsString( 'text-transform:uppercase', $wrapper_style, 'Wrapper should have text-transform: uppercase.' );
+ $this->assertStringContainsString( 'border-radius:12px', $item_style, 'Item should have border-radius: 12px.' );
+ $this->assertStringNotContainsString( 'border-radius', $wrapper_style, 'Wrapper should not have border-radius.' );
+ }
+
+ /**
+ * @testdox Padding and border radius are not applied to visual swatch chip items.
+ * @covers \Automattic\WooCommerce\Blocks\BlockTypes\ProductFilterChips::render
+ */
+ public function test_does_not_apply_padding_or_border_radius_to_swatch_items(): void {
+ $markup = $this->render_chips(
+ array(
+ 'style' => array(
+ 'border' => array(
+ 'radius' => '0px',
+ ),
+ 'spacing' => array(
+ 'padding' => array(
+ 'top' => '8px',
+ 'right' => '16px',
+ 'bottom' => '8px',
+ 'left' => '16px',
+ ),
+ ),
+ ),
+ ),
+ array(
+ array(
+ 'id' => 'item-red',
+ 'label' => 'Red',
+ 'value' => 'red',
+ 'selected' => false,
+ 'visual' => array(
+ 'type' => 'color',
+ 'color' => '#ff0000',
+ ),
+ ),
+ )
+ );
+
+ $item_style = $this->get_style( $markup, 'wc-block-product-filter-chips__item' );
+
+ $this->assertStringContainsString( 'is-style-swatch', $markup, 'Visual items should use the swatch style.' );
+ $this->assertStringNotContainsString( 'border-radius', $item_style, 'Swatch items should not get an inline border radius.' );
+ $this->assertStringNotContainsString( 'padding-top', $item_style, 'Swatch items should not get inline padding.' );
+ }
+
+ /**
+ * Render the Chips block with the given attributes.
+ *
+ * @param array $attributes Block attributes.
+ * @param array $items Selectable items.
+ * @return string Rendered markup.
+ */
+ private function render_chips( array $attributes, array $items ): string {
+ $block = new \WP_Block(
+ array(
+ 'blockName' => 'woocommerce/product-filter-chips',
+ 'attrs' => array_merge( $attributes, array( 'className' => 'wc-block-product-filter-chips' ) ),
+ 'innerContent' => array(),
+ ),
+ array(
+ 'woocommerce/selectableItems' => array(
+ 'items' => $items,
+ 'selectionMode' => 'multiple',
+ 'storeNamespace' => 'woocommerce/product-filters',
+ ),
+ )
+ );
+
+ return $block->render();
+ }
+
+ /**
+ * Get the style attribute of the first element with the given class.
+ *
+ * @param string $markup Rendered markup.
+ * @param string $class_name Class name to find.
+ * @return string
+ */
+ private function get_style( string $markup, string $class_name ): string {
+ $processor = new \WP_HTML_Tag_Processor( $markup );
+ $this->assertTrue( $processor->next_tag( array( 'class_name' => $class_name ) ), 'Should find the first element with the given class name.' );
+
+ $style = $processor->get_attribute( 'style' );
+
+ return is_string( $style ) ? $style : '';
+ }
+}