[Experimental] Add styling controls (#52598)

* Add alignment controls

* Add changefile(s) from automation for the following project(s): woocommerce-blocks

* Fix justication and orientation layouts

* Move styling settings to inner blocks

* Add e2e tests

* Fix chips not displaying correctly on first load

* Fix security error

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Roy Ho 2024-11-11 13:44:03 -08:00 committed by GitHub
parent d43b3af43f
commit dd38a6ed2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 285 additions and 48 deletions

View File

@ -14,7 +14,29 @@
"woocommerce/product-filters"
],
"supports": {
"interactivity": true
"interactivity": true,
"__experimentalBorder": {
"color": true,
"radius": true,
"style": true,
"width": true,
"__experimentalDefaultControls": {
"color": false,
"radius": false,
"style": false,
"width": false
}
},
"spacing": {
"margin": true,
"padding": true,
"blockGap": false,
"__experimentalDefaultControls": {
"margin": false,
"padding": false,
"blockGap": false
}
}
},
"usesContext": [
"filterParams"

View File

@ -18,7 +18,8 @@ import { EditProps } from './types';
import { filtersPreview } from './constants';
const Edit = ( props: EditProps ) => {
const { clearButton } = props.attributes;
const { attributes } = props;
const { clearButton } = attributes;
const { children, ...innerBlocksProps } = useInnerBlocksProps(
useBlockProps(),

View File

@ -13,7 +13,17 @@
"ancestor": [
"woocommerce/product-filter-active"
],
"supports": {},
"supports": {
"layout": {
"allowSwitching": false,
"allowInheriting": false,
"allowJustification": false,
"allowVerticalAlignment": false,
"default": {
"type": "flex"
}
}
},
"usesContext": [
"queryId",
"filterData"

View File

@ -3,11 +3,15 @@
*/
import { __, sprintf } from '@wordpress/i18n';
import clsx from 'clsx';
import { Icon, closeSmall } from '@wordpress/icons';
import { Icon, closeSmall, arrowRight, arrowDown } from '@wordpress/icons';
import { Label } from '@woocommerce/blocks-components';
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
import { getBlockSupport } from '@wordpress/blocks';
import {
InspectorControls,
useBlockProps,
useInnerBlocksProps,
BlockControls,
withColors,
// @ts-expect-error - no types.
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
@ -26,6 +30,7 @@ import { getColorClasses, getColorVars } from './utils';
const Edit = ( props: EditProps ): JSX.Element => {
const colorGradientSettings = useMultipleOriginColorsAndGradients();
const {
name,
context,
clientId,
attributes,
@ -37,11 +42,16 @@ const Edit = ( props: EditProps ): JSX.Element => {
chipBorder,
setChipBorder,
} = props;
const { customChipText, customChipBackground, customChipBorder } =
const { customChipText, customChipBackground, customChipBorder, layout } =
attributes;
const { filterData } = context;
const { items } = filterData;
// Extract attributes from block layout
const layoutBlockSupport = getBlockSupport( name, 'layout' );
const defaultBlockLayout = layoutBlockSupport?.default;
const usedLayout = layout || defaultBlockLayout || {};
const blockProps = useBlockProps( {
className: clsx( 'wc-block-product-filter-removable-chips', {
...getColorClasses( attributes ),
@ -49,6 +59,7 @@ const Edit = ( props: EditProps ): JSX.Element => {
style: getColorVars( attributes ),
} );
const innerBlocksProps = useInnerBlocksProps( blockProps, {} );
const removeText = ( label: string ): string => {
return sprintf(
/* translators: %s attribute value used in the filter. For example: yellow, green, small, large. */
@ -58,33 +69,64 @@ const Edit = ( props: EditProps ): JSX.Element => {
};
return (
<>
<div { ...blockProps }>
<ul className="wc-block-product-filter-removable-chips__items">
{ items?.map( ( item, index ) => (
<li
key={ index }
className="wc-block-product-filter-removable-chips__item"
>
<span className="wc-block-product-filter-removable-chips__label">
{ item.type + ': ' + item.label }
</span>
<button className="wc-block-product-filter-removable-chips__remove">
<Icon
className="wc-block-product-filter-removable-chips__remove-icon"
icon={ closeSmall }
size={ 25 }
/>
<Label
screenReaderLabel={ removeText(
item.type + ': ' + item.label
) }
/>
</button>
</li>
) ) }
</ul>
</div>
<div { ...innerBlocksProps }>
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon={ arrowRight }
label={ __( 'Horizontal', 'woocommerce' ) }
onClick={ () =>
setAttributes( {
layout: {
...usedLayout,
orientation: 'horizontal',
},
} )
}
isPressed={
usedLayout.orientation === 'horizontal' ||
! usedLayout.orientation
}
/>
<ToolbarButton
icon={ arrowDown }
label={ __( 'Vertical', 'woocommerce' ) }
onClick={ () =>
setAttributes( {
layout: {
...usedLayout,
orientation: 'vertical',
},
} )
}
isPressed={ usedLayout.orientation === 'vertical' }
/>
</ToolbarGroup>
</BlockControls>
<ul className="wc-block-product-filter-removable-chips__items">
{ items?.map( ( item, index ) => (
<li
key={ index }
className="wc-block-product-filter-removable-chips__item"
>
<span className="wc-block-product-filter-removable-chips__label">
{ item.type + ': ' + item.label }
</span>
<button className="wc-block-product-filter-removable-chips__remove">
<Icon
className="wc-block-product-filter-removable-chips__remove-icon"
icon={ closeSmall }
size={ 25 }
/>
<Label
screenReaderLabel={ removeText(
item.type + ': ' + item.label
) }
/>
</button>
</li>
) ) }
</ul>
<InspectorControls group="color">
{ colorGradientSettings.hasColorsOrGradients && (
<ColorGradientSettingsDropdown
@ -147,7 +189,7 @@ const Edit = ( props: EditProps ): JSX.Element => {
/>
) }
</InspectorControls>
</>
</div>
);
};

View File

@ -1,5 +1,29 @@
.is-vertical {
.wc-block-product-filter-removable-chips__items {
flex-direction: column;
}
}
.is-content-justification-center {
.wc-block-product-filter-removable-chips__items {
justify-content: center;
}
}
.is-content-justification-right {
.wc-block-product-filter-removable-chips__items {
justify-content: flex-end;
}
}
.is-content-justification-space-between {
.wc-block-product-filter-removable-chips__items {
justify-content: space-between;
}
}
.wc-block-product-filter-removable-chips__items {
display: flex;
display: inline-flex;
gap: 4px;
flex-wrap: wrap;
list-style: none;
@ -16,13 +40,14 @@
font-size: 0.875em;
display: flex;
align-items: center;
justify-content: space-between;
.wc-block-product-filter-removable-chips__remove {
background: none;
border: none;
cursor: pointer;
padding: 0;
margin: 0 0 0 5px;
margin: 0;
display: flex;
align-items: center;
fill: var(--wc-product-filter-removable-chips-text, currentColor);

View File

@ -23,6 +23,9 @@ export type BlockAttributes = {
customChipBackground?: string;
chipBorder?: string;
customChipBorder?: string;
layout: {
orientation: string;
};
};
export type EditProps = BlockEditProps< BlockAttributes > & {
@ -34,4 +37,5 @@ export type EditProps = BlockEditProps< BlockAttributes > & {
setChipBackground: ( value: string ) => void;
chipBorder: Color;
setChipBorder: ( value: string ) => void;
name: string;
};

View File

@ -0,0 +1,127 @@
/**
* External dependencies
*/
import { test as base, expect } from '@woocommerce/e2e-utils';
/**
* Internal dependencies
*/
import { ProductFiltersPage } from './product-filters.page';
const blockData = {
name: 'woocommerce/product-filter-active',
selectors: {
frontend: {},
editor: {
settings: {},
label: 'Block: Active (Experimental)',
innerBlocks: {
chips: {
label: 'Block: Chips',
},
},
},
},
slug: 'archive-product',
};
const test = base.extend< { pageObject: ProductFiltersPage } >( {
pageObject: async ( { page, editor, frontendUtils }, use ) => {
const pageObject = new ProductFiltersPage( {
page,
editor,
frontendUtils,
} );
await use( pageObject );
},
} );
test.describe( `${ blockData.name }`, () => {
test.beforeEach( async ( { admin, requestUtils } ) => {
await requestUtils.setFeatureFlag( 'experimental-blocks', true );
await admin.visitSiteEditor( {
postId: `woocommerce/woocommerce//${ blockData.slug }`,
postType: 'wp_template',
canvas: 'edit',
} );
} );
test( 'should display the correct inspector layout controls', async ( {
editor,
pageObject,
} ) => {
await pageObject.addProductFiltersBlock( { cleanContent: true } );
const activeBlock = editor.canvas.getByLabel(
blockData.selectors.editor.label
);
await expect( activeBlock ).toBeVisible();
await activeBlock.click();
const chipsBlock = editor.canvas.getByLabel(
blockData.selectors.editor.innerBlocks.chips.label
);
await expect( chipsBlock ).toBeVisible();
await chipsBlock.click();
await editor.openDocumentSettingsSidebar();
await expect( editor.page.getByText( 'Justification' ) ).toBeVisible();
await expect( editor.page.getByText( 'Orientation' ) ).toBeVisible();
} );
test( 'should add correct layout CSS class when modifying layout settings', async ( {
editor,
pageObject,
} ) => {
await pageObject.addProductFiltersBlock( { cleanContent: true } );
const activeBlock = editor.canvas.getByLabel(
blockData.selectors.editor.label
);
await expect( activeBlock ).toBeVisible();
await activeBlock.click();
const chipsBlock = editor.canvas.getByLabel(
blockData.selectors.editor.innerBlocks.chips.label
);
await expect( chipsBlock ).toBeVisible();
await chipsBlock.click();
await editor.openDocumentSettingsSidebar();
await editor.page.getByLabel( 'Space between items' ).click();
await expect( chipsBlock ).toHaveClass(
/is-content-justification-space-between/
);
await editor.page.getByLabel( 'Justify items right' ).click();
await expect( chipsBlock ).toHaveClass(
/is-content-justification-right/
);
await editor.page.getByLabel( 'Justify items center' ).click();
await expect( chipsBlock ).toHaveClass(
/is-content-justification-center/
);
await editor.page.getByLabel( 'Justify items left' ).click();
await expect( chipsBlock ).toHaveClass(
/is-content-justification-left/
);
await editor.page.getByRole( 'button', { name: 'Horizontal' } ).click();
await expect( chipsBlock ).toHaveClass( /is-horizontal/ );
await editor.page.getByRole( 'button', { name: 'Vertical' } ).click();
await expect( chipsBlock ).toHaveClass( /is-vertical/ );
} );
} );

View File

@ -0,0 +1,4 @@
Significance: patch
Type: tweak
Comment: Adds additional styling settings to active filter block

View File

@ -24,13 +24,13 @@ final class ProductFilterRemovableChips extends AbstractBlock {
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( empty( $block->context['filterData'] ) || empty( $block->context['filterData']['items'] ) ) {
return '';
$filters = array();
if ( ! empty( $block->context['filterData'] ) && ! empty( $block->context['filterData']['items'] ) ) {
$filters = $block->context['filterData']['items'];
}
$style = '';
$context = $block->context['filterData'];
$filters = $context['items'] ?? array();
$style = '';
$tags = new \WP_HTML_Tag_Processor( $content );
if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filter-removable-chips' ) ) ) {
@ -38,19 +38,21 @@ final class ProductFilterRemovableChips extends AbstractBlock {
$style = $tags->get_attribute( 'style' );
}
$wrapper_attributes = get_block_wrapper_attributes(
array(
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-wc-key' => wp_unique_prefixed_id( $this->get_full_block_name() ),
'class' => esc_attr( $classes ),
'style' => esc_attr( $style ),
)
$wrapper_attributes = array(
'data-wc-interactive' => wp_json_encode( array( 'namespace' => $this->get_full_block_name() ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
'data-wc-key' => wp_unique_prefixed_id( $this->get_full_block_name() ),
'class' => esc_attr( $classes ),
'style' => esc_attr( $style ),
);
if ( empty( $filters ) ) {
$wrapper_attributes['hidden'] = true;
}
ob_start();
?>
<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<div <?php echo get_block_wrapper_attributes( $wrapper_attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<?php if ( ! empty( $filters ) ) : ?>
<ul class="wc-block-product-filter-removable-chips__items">
<?php foreach ( $filters as $filter ) : ?>