[Experimental] Product Filters Redesign > Overlay Navigation: Add Block to the Product Filters Block (#50186)

* Add variation to Product Filters Overlay Navigation

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

* Move Product Filters Overlay Navigation to correct position

* Hide block when it is outside the Product Filters template part

* Display Navigation block in the frontend

* Show the Product Filters Overlay Navigation on the frontend

* Add logic to hide Product Filters Overlay Navigation block on the frontend

* Hide block on the Overlay template part

* Fix eslint errors

* Update the block variation title

* Remove the `isActive` property from the block variations

* Use Product Filters block context

* Replace enum with const

* Remove unnecessary `StyleAttributesUtils`

* Rename context key

* Move BlockOverlayAttribute to the constants.ts file

* fix BlockOverlayAttribute import

* Fix import error

* Improve code for the shouldHideBlock method

* Remove unnecessary attributes property

* Fix error in ProductFiltersOverlay block

* Prevent block from being hidden on Product Filters template part

* Fix inspector controls when block is hidden

* Remove unnecessary import

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Alexandre Lara 2024-08-15 14:50:45 -03:00 committed by GitHub
parent acaa5ad7a7
commit 6185185589
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 452 additions and 123 deletions

View File

@ -0,0 +1,18 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { BlockVariation } from '@wordpress/blocks';
const variations: BlockVariation[] = [
{
name: 'product-filters-overlay-navigation-open-trigger',
title: __( 'Overlay Navigation (Experimental)', 'woocommerce' ),
attributes: {
triggerType: 'open-overlay',
},
isDefault: false,
},
];
export const blockVariations = variations;

View File

@ -5,7 +5,7 @@
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"textdomain": "woocommerce",
"ancestor": [ "woocommerce/product-filters-overlay" ],
"ancestor": [ "woocommerce/product-filters-overlay", "woocommerce/product-filters" ],
"attributes": {
"align": {
"type": "string",
@ -22,6 +22,10 @@
"iconSize": {
"type": "string"
},
"overlayMode": {
"type": "string",
"default": "never"
},
"style": {
"type": "object",
"default": {
@ -29,6 +33,10 @@
"blockGap": "1rem"
}
}
},
"triggerType": {
"type": "string",
"default": "close-overlay"
}
},
"supports": {
@ -87,6 +95,7 @@
}
}
},
"usesContext": [ "woocommerce/product-filters/overlay"],
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3
}

View File

@ -2,40 +2,169 @@
* External dependencies
*/
import { useSelect } from '@wordpress/data';
import {
useBlockProps,
useInnerBlocksProps,
InspectorControls,
} from '@wordpress/block-editor';
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
import { BlockEditProps, store as blocksStore } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import clsx from 'clsx';
import {
PanelBody,
RadioControl,
SelectControl,
RangeControl,
__experimentalToggleGroupControl as ToggleGroupControl,
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
} from '@wordpress/components';
import { Icon, close } from '@wordpress/icons';
/**
* Internal dependencies
*/
import type { BlockAttributes } from './types';
import type {
BlockAttributes,
BlockContext,
BlockVariationTriggerType,
} from './types';
import { default as productFiltersIcon } from '../product-filters/icon';
import { BlockOverlayAttribute as ProductFiltersBlockOverlayAttribute } from '../product-filters/constants';
import './editor.scss';
import { Inspector } from './inspector-controls';
export const Edit = ( {
attributes,
setAttributes,
}: BlockEditProps< BlockAttributes > ) => {
const { navigationStyle, buttonStyle, iconSize, style } = attributes;
const OverlayNavigationLabel = ( {
variation,
}: {
variation: BlockVariationTriggerType;
} ) => {
let label = __( 'Close', 'woocommerce' );
if ( variation === 'open-overlay' ) {
label = __( 'Filters', 'woocommerce' );
}
return <span>{ label }</span>;
};
const OverlayNavigationIcon = ( {
variation,
iconSize,
style,
}: {
variation: BlockVariationTriggerType;
iconSize: number | undefined;
style: BlockAttributes[ 'style' ];
} ) => {
let icon = close;
if ( variation === 'open-overlay' ) {
icon = productFiltersIcon();
}
return (
<Icon
fill="currentColor"
icon={ icon }
style={ {
width: iconSize || style?.typography?.fontSize || '16px',
height: iconSize || style?.typography?.fontSize || '16px',
} }
/>
);
};
const OverlayNavigationContent = ( {
variation,
iconSize,
style,
navigationStyle,
}: {
variation: BlockVariationTriggerType;
iconSize: BlockAttributes[ 'iconSize' ];
style: BlockAttributes[ 'style' ];
navigationStyle: BlockAttributes[ 'navigationStyle' ];
} ) => {
const overlayNavigationLabel = (
<OverlayNavigationLabel variation={ variation } />
);
const overlayNavigationIcon = (
<OverlayNavigationIcon
variation={ variation }
iconSize={ iconSize }
style={ style }
/>
);
if ( navigationStyle === 'label-and-icon' ) {
if ( variation === 'open-overlay' ) {
return (
<>
{ overlayNavigationIcon }
{ overlayNavigationLabel }
</>
);
} else if ( variation === 'close-overlay' ) {
return (
<>
{ overlayNavigationLabel }
{ overlayNavigationIcon }
</>
);
}
} else if ( navigationStyle === 'label-only' ) {
return overlayNavigationLabel;
} else if ( navigationStyle === 'icon-only' ) {
return overlayNavigationIcon;
}
return null;
};
type BlockProps = BlockEditProps< BlockAttributes > & { context: BlockContext };
export const Edit = ( { attributes, setAttributes, context }: BlockProps ) => {
const { navigationStyle, buttonStyle, iconSize, style, triggerType } =
attributes;
const { 'woocommerce/product-filters/overlay': productFiltersOverlayMode } =
context;
const blockProps = useBlockProps( {
className: clsx( 'wc-block-product-filters-overlay-navigation', {
'wp-block-button__link wp-element-button': buttonStyle !== 'link',
} ),
} );
const {
isWithinProductFiltersOverlayTemplatePart,
}: {
isWithinProductFiltersOverlayTemplatePart: boolean;
} = useSelect( ( select ) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const { getCurrentPostId, getCurrentPostType } =
select( 'core/editor' );
const currentPostId = getCurrentPostId< string >();
const currentPostIdParts = currentPostId?.split( '//' );
const currentPostType = getCurrentPostType< string >();
let isProductFiltersOverlayTemplatePart = false;
if (
currentPostType === 'wp_template_part' &&
currentPostIdParts?.length > 1
) {
const [ , postId ] = currentPostIdParts;
isProductFiltersOverlayTemplatePart =
postId === 'product-filters-overlay';
}
return {
isWithinProductFiltersOverlayTemplatePart:
isProductFiltersOverlayTemplatePart,
};
} );
const shouldHideBlock = () => {
if ( triggerType === 'open-overlay' ) {
if (
productFiltersOverlayMode ===
ProductFiltersBlockOverlayAttribute.NEVER
) {
return true;
}
if ( isWithinProductFiltersOverlayTemplatePart ) {
return true;
}
}
return false;
};
// We need useInnerBlocksProps because Gutenberg only applies layout classes
// to parent block. We don't have any inner blocks but we want to use the
// layout controls.
@ -60,6 +189,16 @@ export const Edit = ( {
}
);
if ( shouldHideBlock() ) {
return (
<Inspector
attributes={ attributes }
setAttributes={ setAttributes }
buttonStyles={ buttonStyles }
/>
);
}
return (
<nav
className={ clsx(
@ -71,104 +210,18 @@ export const Edit = ( {
) }
>
<div { ...innerBlocksProps }>
{ navigationStyle !== 'icon-only' && (
<span>{ __( 'Close', 'woocommerce' ) }</span>
) }
{ navigationStyle !== 'label-only' && (
<Icon
fill="currentColor"
icon={ close }
style={ {
width:
iconSize ||
style?.typography?.fontSize ||
'16px',
height:
iconSize ||
style?.typography?.fontSize ||
'16px',
} }
/>
) }
<OverlayNavigationContent
variation={ triggerType }
iconSize={ iconSize }
navigationStyle={ navigationStyle }
style={ style }
/>
</div>
<InspectorControls group="styles">
<PanelBody title={ __( 'Style', 'woocommerce' ) }>
<RadioControl
selected={ navigationStyle }
options={ [
{
label: __( 'Label and icon', 'woocommerce' ),
value: 'label-and-icon',
},
{
label: __( 'Label only', 'woocommerce' ),
value: 'label-only',
},
{
label: __( 'Icon only', 'woocommerce' ),
value: 'icon-only',
},
] }
onChange={ (
value: BlockAttributes[ 'navigationStyle' ]
) =>
setAttributes( {
navigationStyle: value,
} )
}
/>
{ buttonStyles.length <= 3 && (
<ToggleGroupControl
label={ __( 'Button', 'woocommerce' ) }
value={ buttonStyle }
isBlock
onChange={ (
value: BlockAttributes[ 'buttonStyle' ]
) =>
setAttributes( {
buttonStyle: value,
} )
}
>
{ buttonStyles.map( ( option ) => (
<ToggleGroupControlOption
key={ option.value }
label={ option.label }
value={ option.value }
/>
) ) }
</ToggleGroupControl>
) }
{ buttonStyles.length > 3 && (
<SelectControl
label={ __( 'Button', 'woocommerce' ) }
value={ buttonStyle }
options={ buttonStyles }
onChange={ (
value: BlockAttributes[ 'buttonStyle' ]
) =>
setAttributes( {
buttonStyle: value,
} )
}
/>
) }
{ navigationStyle !== 'label-only' && (
<RangeControl
className="wc-block-product-filters-overlay-navigation__icon-size-control"
label={ __( 'Icon Size', 'woocommerce' ) }
value={ iconSize }
onChange={ ( newSize: number ) => {
setAttributes( { iconSize: newSize } );
} }
min={ 0 }
max={ 300 }
/>
) }
</PanelBody>
</InspectorControls>
<Inspector
attributes={ attributes }
setAttributes={ setAttributes }
buttonStyles={ buttonStyles }
/>
</nav>
);
};

View File

@ -12,6 +12,7 @@ import { isExperimentalBlocksEnabled } from '@woocommerce/block-settings';
import metadata from './block.json';
import { Edit } from './edit';
import { Save } from './save';
import { blockVariations } from './block-variations';
import './style.scss';
if ( isExperimentalBlocksEnabled() ) {
@ -19,5 +20,6 @@ if ( isExperimentalBlocksEnabled() ) {
edit: Edit,
save: Save,
icon: <Icon icon={ closeSquareShadow } />,
variations: blockVariations,
} );
}

View File

@ -0,0 +1,118 @@
/**
* External dependencies
*/
import { InspectorControls } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import {
PanelBody,
RadioControl,
SelectControl,
RangeControl,
__experimentalToggleGroupControl as ToggleGroupControl,
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
} from '@wordpress/components';
/**
* Internal dependencies
*/
import type { BlockAttributes } from './types';
interface ButtonStyle {
value: string;
label: string;
}
interface InspectorProps {
attributes: BlockEditProps< BlockAttributes >[ 'attributes' ];
setAttributes: BlockEditProps< BlockAttributes >[ 'setAttributes' ];
buttonStyles: ButtonStyle[];
}
export const Inspector = ( {
attributes,
setAttributes,
buttonStyles,
}: InspectorProps ) => {
const { navigationStyle, buttonStyle, iconSize } = attributes;
return (
<InspectorControls group="styles">
<PanelBody title={ __( 'Style', 'woocommerce' ) }>
<RadioControl
selected={ navigationStyle }
options={ [
{
label: __( 'Label and icon', 'woocommerce' ),
value: 'label-and-icon',
},
{
label: __( 'Label only', 'woocommerce' ),
value: 'label-only',
},
{
label: __( 'Icon only', 'woocommerce' ),
value: 'icon-only',
},
] }
onChange={ (
value: BlockAttributes[ 'navigationStyle' ]
) =>
setAttributes( {
navigationStyle: value,
} )
}
/>
{ buttonStyles.length <= 3 && (
<ToggleGroupControl
label={ __( 'Button', 'woocommerce' ) }
value={ buttonStyle }
isBlock
onChange={ (
value: BlockAttributes[ 'buttonStyle' ]
) =>
setAttributes( {
buttonStyle: value,
} )
}
>
{ buttonStyles.map( ( option ) => (
<ToggleGroupControlOption
key={ option.value }
label={ option.label }
value={ option.value }
/>
) ) }
</ToggleGroupControl>
) }
{ buttonStyles.length > 3 && (
<SelectControl
label={ __( 'Button', 'woocommerce' ) }
value={ buttonStyle }
options={ buttonStyles }
onChange={ (
value: BlockAttributes[ 'buttonStyle' ]
) =>
setAttributes( {
buttonStyle: value,
} )
}
/>
) }
{ navigationStyle !== 'label-only' && (
<RangeControl
className="wc-block-product-filters-overlay-navigation__icon-size-control"
label={ __( 'Icon Size', 'woocommerce' ) }
value={ iconSize }
onChange={ ( newSize: number ) => {
setAttributes( { iconSize: newSize } );
} }
min={ 0 }
max={ 300 }
/>
) }
</PanelBody>
</InspectorControls>
);
};

View File

@ -3,10 +3,18 @@
}
.wc-block-product-filters-overlay-navigation {
display: flex;
flex-direction: row;
cursor: pointer;
&.alignright {
margin-left: auto;
}
&.alignleft {
margin-left: unset;
}
&.aligncenter {
margin-left: auto;
margin-right: auto;

View File

@ -1,3 +1,8 @@
/**
* Internal dependencies
*/
import { BlockOverlayAttributeOptions as ProductFiltersBlockOverlayAttributeOptions } from '../product-filters/types';
type BorderRadius = {
bottomLeft: string;
bottomRight: string;
@ -9,10 +14,19 @@ type BorderSide = {
width: string;
};
export interface BlockContext {
// eslint-disable-next-line @typescript-eslint/naming-convention
'woocommerce/product-filters/overlay': ProductFiltersBlockOverlayAttributeOptions;
}
export type BlockVariationTriggerType = 'open-overlay' | 'close-overlay';
export type BlockAttributes = {
navigationStyle: 'label-and-icon' | 'label-only' | 'icon-only';
buttonStyle: string;
iconSize?: number;
overlayMode: ProductFiltersBlockOverlayAttributeOptions;
triggerType: BlockVariationTriggerType;
style: {
border?: {
radius?: string | BorderRadius;

View File

@ -44,7 +44,9 @@
},
"textdomain": "woocommerce",
"usesContext": [ "postId" ],
"providesContext": {},
"providesContext": {
"woocommerce/product-filters/overlay": "overlay"
},
"attributes": {
"overlay": {
"type": "string",

View File

@ -0,0 +1,5 @@
export const BlockOverlayAttribute = {
NEVER: 'never',
MOBILE: 'mobile',
ALWAYS: 'always',
} as const;

View File

@ -30,7 +30,8 @@ import {
* Internal dependencies
*/
import './editor.scss';
import type { BlockAttributes } from './types';
import { type BlockAttributes } from './types';
import { BlockOverlayAttribute } from './constants';
const defaultAttribute = getSetting< AttributeSetting >(
'defaultProductFilterAttribute'
@ -131,15 +132,15 @@ export const Edit = ( {
} }
>
<ToggleGroupControlOption
value={ 'never' }
value={ BlockOverlayAttribute.NEVER }
label={ __( 'Never', 'woocommerce' ) }
/>
<ToggleGroupControlOption
value={ 'mobile' }
value={ BlockOverlayAttribute.MOBILE }
label={ __( 'Mobile', 'woocommerce' ) }
/>
<ToggleGroupControlOption
value={ 'always' }
value={ BlockOverlayAttribute.ALWAYS }
label={ __( 'Always', 'woocommerce' ) }
/>
</ToggleGroupControl>

View File

@ -1,7 +1,15 @@
/**
* Internal dependencies
*/
import { BlockOverlayAttribute } from './constants';
export type BlockOverlayAttributeOptions =
( typeof BlockOverlayAttribute )[ keyof typeof BlockOverlayAttribute ];
export interface BlockAttributes {
productId?: string;
setAttributes: ( attributes: ProductFiltersBlockAttributes ) => void;
overlay: 'never' | 'mobile' | 'always';
overlay: BlockOverlayAttributeOptions;
overlayIcon:
| 'filter-icon-1'
| 'filter-icon-2'

View File

@ -0,0 +1,4 @@
Significance: patch
Type: tweak
Comment: Add the Product Filters Overlay Navigation block to the Product Filters block.

View File

@ -11,4 +11,87 @@ class ProductFiltersOverlayNavigation extends AbstractBlock {
* @var string
*/
protected $block_name = 'product-filters-overlay-navigation';
/**
* Register the context
*
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'woocommerce/product-filters/overlay' ];
}
/**
* Get the frontend script handle for this block type.
*
* @see $this->register_block_type()
* @param string $key Data to get, or default to everything.
* @return array|string|null
*/
protected function get_block_type_script( $key = null ) {
return null;
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
$wrapper_attributes = get_block_wrapper_attributes(
array(
'class' => 'wc-block-product-filters-overlay-navigation',
)
);
$overlay_mode = $block->context['woocommerce/product-filters/overlay'];
if ( 'never' === $overlay_mode || ( ! wp_is_mobile() && 'mobile' === $overlay_mode ) ) {
return null;
}
$html_content = strtr(
'<div {{wrapper_attributes}}>
{{primary_content}}
{{secondary_content}}
</div>',
array(
'{{wrapper_attributes}}' => $wrapper_attributes,
'{{primary_content}}' => 'open-overlay' === $attributes['triggerType'] ? $this->render_icon( $attributes ) : $this->render_label( $attributes ),
'{{secondary_content}}' => 'open-overlay' === $attributes['triggerType'] ? $this->render_label( $attributes ) : $this->render_icon( $attributes ),
)
);
return $html_content;
}
/**
* Gets the icon to render depending on the triggerType attribute.
*
* @param array $attributes Block attributes.
*
* @return string Label to render on the block
*/
private function render_icon( $attributes ) {
if ( 'open-overlay' === $attributes['triggerType'] ) {
return '<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" style="width: 16px; height: 16px;"><path d="M10 17.5H14V16H10V17.5ZM6 6V7.5H18V6H6ZM8 12.5H16V11H8V12.5Z" fill="currentColor"></path></svg>';
}
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor" aria-hidden="true" focusable="false" style="width: 16px; height: 16px;"><path d="M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z"></path></svg>';
}
/**
* Gets the label to render depending on the triggerType.
*
* @param array $attributes Block attributes.
*
* @return string Label to render on the block
*/
private function render_label( $attributes ) {
return sprintf(
'<span>%s</span>',
'open-overlay' === $attributes['triggerType'] ? __( 'Filters', 'woocommerce' ) : __( 'Close', 'woocommerce' )
);
}
}

View File

@ -1,4 +1,8 @@
<!-- wp:woocommerce/product-filters -->
<!-- wp:woocommerce/product-filters-overlay-navigation {"align":"left","triggerType":"open-overlay","lock":{"move":true,"remove":true}} -->
<div class="wp-block-woocommerce-product-filters-overlay-navigation alignleft wc-block-product-filters-overlay-navigation"></div>
<!-- /wp:woocommerce/product-filters-overlay-navigation -->
<div class="wp-block-woocommerce-product-filters wc-block-product-filters"><!-- wp:heading {"level":3,"style":{"typography":{"fontSize":"24px"}}} -->
<h3 class="wp-block-heading" style="font-size:24px">Filters</h3>
<!-- /wp:heading -->