[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:
parent
d43b3af43f
commit
dd38a6ed2d
|
@ -14,7 +14,29 @@
|
||||||
"woocommerce/product-filters"
|
"woocommerce/product-filters"
|
||||||
],
|
],
|
||||||
"supports": {
|
"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": [
|
"usesContext": [
|
||||||
"filterParams"
|
"filterParams"
|
||||||
|
|
|
@ -18,7 +18,8 @@ import { EditProps } from './types';
|
||||||
import { filtersPreview } from './constants';
|
import { filtersPreview } from './constants';
|
||||||
|
|
||||||
const Edit = ( props: EditProps ) => {
|
const Edit = ( props: EditProps ) => {
|
||||||
const { clearButton } = props.attributes;
|
const { attributes } = props;
|
||||||
|
const { clearButton } = attributes;
|
||||||
|
|
||||||
const { children, ...innerBlocksProps } = useInnerBlocksProps(
|
const { children, ...innerBlocksProps } = useInnerBlocksProps(
|
||||||
useBlockProps(),
|
useBlockProps(),
|
||||||
|
|
|
@ -13,7 +13,17 @@
|
||||||
"ancestor": [
|
"ancestor": [
|
||||||
"woocommerce/product-filter-active"
|
"woocommerce/product-filter-active"
|
||||||
],
|
],
|
||||||
"supports": {},
|
"supports": {
|
||||||
|
"layout": {
|
||||||
|
"allowSwitching": false,
|
||||||
|
"allowInheriting": false,
|
||||||
|
"allowJustification": false,
|
||||||
|
"allowVerticalAlignment": false,
|
||||||
|
"default": {
|
||||||
|
"type": "flex"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"usesContext": [
|
"usesContext": [
|
||||||
"queryId",
|
"queryId",
|
||||||
"filterData"
|
"filterData"
|
||||||
|
|
|
@ -3,11 +3,15 @@
|
||||||
*/
|
*/
|
||||||
import { __, sprintf } from '@wordpress/i18n';
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
import clsx from 'clsx';
|
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 { Label } from '@woocommerce/blocks-components';
|
||||||
|
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
|
||||||
|
import { getBlockSupport } from '@wordpress/blocks';
|
||||||
import {
|
import {
|
||||||
InspectorControls,
|
InspectorControls,
|
||||||
useBlockProps,
|
useBlockProps,
|
||||||
|
useInnerBlocksProps,
|
||||||
|
BlockControls,
|
||||||
withColors,
|
withColors,
|
||||||
// @ts-expect-error - no types.
|
// @ts-expect-error - no types.
|
||||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
// 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 Edit = ( props: EditProps ): JSX.Element => {
|
||||||
const colorGradientSettings = useMultipleOriginColorsAndGradients();
|
const colorGradientSettings = useMultipleOriginColorsAndGradients();
|
||||||
const {
|
const {
|
||||||
|
name,
|
||||||
context,
|
context,
|
||||||
clientId,
|
clientId,
|
||||||
attributes,
|
attributes,
|
||||||
|
@ -37,11 +42,16 @@ const Edit = ( props: EditProps ): JSX.Element => {
|
||||||
chipBorder,
|
chipBorder,
|
||||||
setChipBorder,
|
setChipBorder,
|
||||||
} = props;
|
} = props;
|
||||||
const { customChipText, customChipBackground, customChipBorder } =
|
const { customChipText, customChipBackground, customChipBorder, layout } =
|
||||||
attributes;
|
attributes;
|
||||||
const { filterData } = context;
|
const { filterData } = context;
|
||||||
const { items } = filterData;
|
const { items } = filterData;
|
||||||
|
|
||||||
|
// Extract attributes from block layout
|
||||||
|
const layoutBlockSupport = getBlockSupport( name, 'layout' );
|
||||||
|
const defaultBlockLayout = layoutBlockSupport?.default;
|
||||||
|
const usedLayout = layout || defaultBlockLayout || {};
|
||||||
|
|
||||||
const blockProps = useBlockProps( {
|
const blockProps = useBlockProps( {
|
||||||
className: clsx( 'wc-block-product-filter-removable-chips', {
|
className: clsx( 'wc-block-product-filter-removable-chips', {
|
||||||
...getColorClasses( attributes ),
|
...getColorClasses( attributes ),
|
||||||
|
@ -49,6 +59,7 @@ const Edit = ( props: EditProps ): JSX.Element => {
|
||||||
style: getColorVars( attributes ),
|
style: getColorVars( attributes ),
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
const innerBlocksProps = useInnerBlocksProps( blockProps, {} );
|
||||||
const removeText = ( label: string ): string => {
|
const removeText = ( label: string ): string => {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
/* translators: %s attribute value used in the filter. For example: yellow, green, small, large. */
|
/* translators: %s attribute value used in the filter. For example: yellow, green, small, large. */
|
||||||
|
@ -58,8 +69,40 @@ const Edit = ( props: EditProps ): JSX.Element => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div { ...innerBlocksProps }>
|
||||||
<div { ...blockProps }>
|
<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">
|
<ul className="wc-block-product-filter-removable-chips__items">
|
||||||
{ items?.map( ( item, index ) => (
|
{ items?.map( ( item, index ) => (
|
||||||
<li
|
<li
|
||||||
|
@ -84,7 +127,6 @@ const Edit = ( props: EditProps ): JSX.Element => {
|
||||||
</li>
|
</li>
|
||||||
) ) }
|
) ) }
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
<InspectorControls group="color">
|
<InspectorControls group="color">
|
||||||
{ colorGradientSettings.hasColorsOrGradients && (
|
{ colorGradientSettings.hasColorsOrGradients && (
|
||||||
<ColorGradientSettingsDropdown
|
<ColorGradientSettingsDropdown
|
||||||
|
@ -147,7 +189,7 @@ const Edit = ( props: EditProps ): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
</InspectorControls>
|
</InspectorControls>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
.wc-block-product-filter-removable-chips__items {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
@ -16,13 +40,14 @@
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.wc-block-product-filter-removable-chips__remove {
|
.wc-block-product-filter-removable-chips__remove {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0 0 5px;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
fill: var(--wc-product-filter-removable-chips-text, currentColor);
|
fill: var(--wc-product-filter-removable-chips-text, currentColor);
|
||||||
|
|
|
@ -23,6 +23,9 @@ export type BlockAttributes = {
|
||||||
customChipBackground?: string;
|
customChipBackground?: string;
|
||||||
chipBorder?: string;
|
chipBorder?: string;
|
||||||
customChipBorder?: string;
|
customChipBorder?: string;
|
||||||
|
layout: {
|
||||||
|
orientation: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EditProps = BlockEditProps< BlockAttributes > & {
|
export type EditProps = BlockEditProps< BlockAttributes > & {
|
||||||
|
@ -34,4 +37,5 @@ export type EditProps = BlockEditProps< BlockAttributes > & {
|
||||||
setChipBackground: ( value: string ) => void;
|
setChipBackground: ( value: string ) => void;
|
||||||
chipBorder: Color;
|
chipBorder: Color;
|
||||||
setChipBorder: ( value: string ) => void;
|
setChipBorder: ( value: string ) => void;
|
||||||
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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/ );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: tweak
|
||||||
|
Comment: Adds additional styling settings to active filter block
|
||||||
|
|
|
@ -24,13 +24,13 @@ final class ProductFilterRemovableChips extends AbstractBlock {
|
||||||
* @return string Rendered block type output.
|
* @return string Rendered block type output.
|
||||||
*/
|
*/
|
||||||
protected function render( $attributes, $content, $block ) {
|
protected function render( $attributes, $content, $block ) {
|
||||||
if ( empty( $block->context['filterData'] ) || empty( $block->context['filterData']['items'] ) ) {
|
$filters = array();
|
||||||
return '';
|
|
||||||
|
if ( ! empty( $block->context['filterData'] ) && ! empty( $block->context['filterData']['items'] ) ) {
|
||||||
|
$filters = $block->context['filterData']['items'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$style = '';
|
$style = '';
|
||||||
$context = $block->context['filterData'];
|
|
||||||
$filters = $context['items'] ?? array();
|
|
||||||
|
|
||||||
$tags = new \WP_HTML_Tag_Processor( $content );
|
$tags = new \WP_HTML_Tag_Processor( $content );
|
||||||
if ( $tags->next_tag( array( 'class_name' => 'wc-block-product-filter-removable-chips' ) ) ) {
|
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' );
|
$style = $tags->get_attribute( 'style' );
|
||||||
}
|
}
|
||||||
|
|
||||||
$wrapper_attributes = get_block_wrapper_attributes(
|
$wrapper_attributes = array(
|
||||||
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-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() ),
|
'data-wc-key' => wp_unique_prefixed_id( $this->get_full_block_name() ),
|
||||||
'class' => esc_attr( $classes ),
|
'class' => esc_attr( $classes ),
|
||||||
'style' => esc_attr( $style ),
|
'style' => esc_attr( $style ),
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ( empty( $filters ) ) {
|
||||||
|
$wrapper_attributes['hidden'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
ob_start();
|
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 ) ) : ?>
|
<?php if ( ! empty( $filters ) ) : ?>
|
||||||
<ul class="wc-block-product-filter-removable-chips__items">
|
<ul class="wc-block-product-filter-removable-chips__items">
|
||||||
<?php foreach ( $filters as $filter ) : ?>
|
<?php foreach ( $filters as $filter ) : ?>
|
||||||
|
|
Loading…
Reference in New Issue