Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
MVP: Attribute filter block powered interactivity API (#11749)
Browse files Browse the repository at this point in the history
* Update Interactivity API JS files

* Disable TS checks in the Interactivity API for now

* Add new SSR files

* Replace wp_ prefixes with wc_ ones

* Replace wp- prefix with wc-

* Replace guternberg_ prefix with woocommerce_

* Remove file comments from Gutenberg

* Rename files with `wp` prefix

* Fix code to load Interactivity API php files

* Remove TODO comments

* Replace @WordPress with @woocommerce

* Update Webpack configuration

* Fix directive prefix

* Remove interactivity folder from tsconfig exclude

* Add client-side navigation meta tag code

* Remove unneeded blocks.php file

* Fix store tag id

* Register Interactivity API runtime script

* Fix Interactivity API runtime registering

* Add Simple Price Filter block

* Remove all files related to directive processing in PHP

* Use values directly for SimplePriceFilter SSR

* Reset pages to 0 when changing filter

Note: we also need to do this with `/page/x`

* wip

* phpcs

* register price filter as inner block

* try: render block using save

* add types

* use min range var instead of 0

* inject dynamic data

* query price data in editor

* better injecting interactivity data

* remove rounding

* Product Collection Data endpoint doesn't care about current query so we remove the context for now

* extract data injecting as a method, possbily a trait in the future

* add sidebar setting

* duplicating the markup in php render callback for safety

* remove directive from edit component

* show prices without decimal

* use final class

* use sample collection data response

* prepare for multiple styles support

* use collection data from context

* cleanup props and props passing

* pass only necessary states

* retire heredoc in favor of late escaping

* reorganize style

* inherit style from current price filter react component, pre extract the component for multiple display style support

* keep minPrice smaller than max

* remove unnecessary active handler logic

* update folder structure

* avoid whitespace change

* clean up

* title

* move inspector to component folder, ready to be extracted to inner block

* block icon

* block name

* name

* use inner block for view

* inner block view switcher

* try: process data in Collection Filtes block only

* wip: query collection data from collection filters block only

* provide all context from collection filters block

* simplify context passing

* feat: use default attribute to define filter type of view block

* rename

* remove price block

* rename price slider to price, default price filter should be a slider

* type cleanup

* fix ancestor block name

* only passing the collection data down

* wip

* recusive

* editor preview

* refactor: data fetching, context passing, and code organization

* initial attribute filter block

* feat: attribute selector

* inspector control

* wip

* break the edit into smaller components

* wip: editor component

* extract checkbox list

* wip editor preview

* show count checkbox list

* fix param aggregation for attribute filter blocks

* post merge fix

* fix param aggregation for attribute filters

* fix: set correct selected attribute

* WIP checkbox list

* WIP checkbox list

* avoid action name conflicts

* Checkbox list front end

* phpcs

* update context on input, navigate on change

* fix: attribute selection

* dropdown

* remove isDeepEqual

* add: warning when attribute has no products or no attribute is selected

* update type

* update type

---------

Co-authored-by: David Arenas <[email protected]>
Co-authored-by: Luis Herranz <[email protected]>
  • Loading branch information
3 people authored Nov 21, 2023
1 parent 1da1fef commit 7f2f23c
Show file tree
Hide file tree
Showing 19 changed files with 1,111 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"name": "woocommerce/collection-attribute-filter",
"version": "1.0.0",
"title": "Collection Attribute Filter",
"description": "Enable customers to filter the product grid by selecting one or more attributes, such as color.",
"category": "woocommerce",
"keywords": [
"WooCommerce"
],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2,
"ancestor": [
"woocommerce/collection-filters"
],
"supports": {
"interactivity": true
},
"usesContext": [
"collectionData"
],
"attributes": {
"queryParam": {
"type": "object",
"default": {}
},
"attributeId": {
"type": "number",
"default": 0
},
"showCounts": {
"type": "boolean",
"default": false
},
"queryType": {
"type": "string",
"default": "or"
},
"displayStyle": {
"type": "string",
"default": "list"
},
"selectType": {
"type": "string",
"default": "multiple"
},
"isPreview": {
"type": "boolean",
"default": false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* External dependencies
*/
import FilterElementLabel from '@woocommerce/base-components/filter-element-label';
import { CheckboxList } from '@woocommerce/blocks-components';
import { AttributeTerm } from '@woocommerce/types';

type Props = {
attributeTerms: AttributeTerm[];
showCounts?: boolean;
};
export const AttributeCheckboxList = ( {
attributeTerms,
showCounts,
}: Props ) => (
<CheckboxList
className="attribute-checkbox-list"
onChange={ () => null }
options={ attributeTerms.map( ( term ) => ( {
label: (
<FilterElementLabel
name={ term.name }
count={ showCounts ? term.count : null }
/>
),
value: term.slug,
} ) ) }
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* External dependencies
*/
import FormTokenField from '@woocommerce/base-components/form-token-field';
import { __, sprintf } from '@wordpress/i18n';
import { Icon, chevronDown } from '@wordpress/icons';

type Props = {
label: string;
};
export const AttributeDropdown = ( { label }: Props ) => (
<div className="attribute-dropdown">
<FormTokenField
suggestions={ [] }
placeholder={ sprintf(
/* translators: %s attribute name. */
__( 'Select %s', 'woo-gutenberg-products-block' ),
label
) }
onChange={ () => null }
value={ [] }
/>
<Icon icon={ chevronDown } size={ 30 } />
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* External dependencies
*/
import { sort } from 'fast-sort';
import { __, sprintf, _n } from '@wordpress/i18n';
import { SearchListControl } from '@woocommerce/editor-components/search-list-control';
import { getSetting } from '@woocommerce/settings';
import { SearchListItem } from '@woocommerce/editor-components/search-list-control/types';
import { AttributeSetting } from '@woocommerce/types';

const ATTRIBUTES = getSetting< AttributeSetting[] >( 'attributes', [] );

type AttributeSelectControlsProps = {
isCompact: boolean;
setAttributeId: ( id: number ) => void;
attributeId: number;
};

export const AttributeSelectControls = ( {
isCompact,
setAttributeId,
attributeId,
}: AttributeSelectControlsProps ) => {
const messages = {
clear: __( 'Clear selected attribute', 'woo-gutenberg-products-block' ),
list: __( 'Product Attributes', 'woo-gutenberg-products-block' ),
noItems: __(
"Your store doesn't have any product attributes.",
'woo-gutenberg-products-block'
),
search: __(
'Search for a product attribute:',
'woo-gutenberg-products-block'
),
selected: ( n: number ) =>
sprintf(
/* translators: %d is the number of attributes selected. */
_n(
'%d attribute selected',
'%d attributes selected',
n,
'woo-gutenberg-products-block'
),
n
),
updated: __(
'Product attribute search results updated.',
'woo-gutenberg-products-block'
),
};

const list = sort(
ATTRIBUTES.map( ( item ) => {
return {
id: parseInt( item.attribute_id, 10 ),
name: item.attribute_label,
};
} )
).asc( 'name' );

const onChange = ( selected: SearchListItem[] ) => {
if ( ! selected || ! selected.length ) {
return;
}

const selectedId = selected[ 0 ].id;
const productAttribute = ATTRIBUTES.find(
( attribute ) => attribute.attribute_id === selectedId.toString()
);

if ( ! productAttribute || attributeId === selectedId ) {
return;
}

setAttributeId( selectedId as number );
};

return (
<SearchListControl
className="woocommerce-product-attributes"
list={ list }
selected={ list.filter( ( { id } ) => id === attributeId ) }
onChange={ onChange }
messages={ messages }
isSingle
isCompact={ isCompact }
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { InspectorControls } from '@wordpress/block-editor';
import {
PanelBody,
ToggleControl,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControl as ToggleGroupControl,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
} from '@wordpress/components';

/**
* Internal dependencies
*/
import { AttributeSelectControls } from './attribute-select-controls';
import { EditProps } from '../types';

export const Inspector = ( { attributes, setAttributes }: EditProps ) => {
const { attributeId, showCounts, queryType, displayStyle, selectType } =
attributes;
return (
<InspectorControls key="inspector">
<PanelBody
title={ __(
'Display Settings',
'woo-gutenberg-products-block'
) }
>
<ToggleControl
label={ __(
'Display product count',
'woo-gutenberg-products-block'
) }
checked={ showCounts }
onChange={ () =>
setAttributes( {
showCounts: ! showCounts,
} )
}
/>
<ToggleGroupControl
label={ __(
'Allow selecting multiple options?',
'woo-gutenberg-products-block'
) }
value={ selectType || 'multiple' }
onChange={ ( value: string ) =>
setAttributes( {
selectType: value,
} )
}
className="wc-block-attribute-filter__multiple-toggle"
>
<ToggleGroupControlOption
value="multiple"
label={ __(
'Multiple',
'woo-gutenberg-products-block'
) }
/>
<ToggleGroupControlOption
value="single"
label={ __( 'Single', 'woo-gutenberg-products-block' ) }
/>
</ToggleGroupControl>
{ selectType === 'multiple' && (
<ToggleGroupControl
label={ __(
'Filter Conditions',
'woo-gutenberg-products-block'
) }
help={
queryType === 'and'
? __(
'Choose to return filter results for all of the attributes selected.',
'woo-gutenberg-products-block'
)
: __(
'Choose to return filter results for any of the attributes selected.',
'woo-gutenberg-products-block'
)
}
value={ queryType }
onChange={ ( value: string ) =>
setAttributes( {
queryType: value,
} )
}
className="wc-block-attribute-filter__conditions-toggle"
>
<ToggleGroupControlOption
value="and"
label={ __(
'All',
'woo-gutenberg-products-block'
) }
/>
<ToggleGroupControlOption
value="or"
label={ __(
'Any',
'woo-gutenberg-products-block'
) }
/>
</ToggleGroupControl>
) }
<ToggleGroupControl
label={ __(
'Display Style',
'woo-gutenberg-products-block'
) }
value={ displayStyle }
onChange={ ( value: string ) =>
setAttributes( {
displayStyle: value,
} )
}
className="wc-block-attribute-filter__display-toggle"
>
<ToggleGroupControlOption
value="list"
label={ __( 'List', 'woo-gutenberg-products-block' ) }
/>
<ToggleGroupControlOption
value="dropdown"
label={ __(
'Dropdown',
'woo-gutenberg-products-block'
) }
/>
</ToggleGroupControl>
</PanelBody>
<PanelBody
title={ __(
'Content Settings',
'woo-gutenberg-products-block'
) }
initialOpen={ false }
>
<AttributeSelectControls
isCompact={ true }
attributeId={ attributeId }
setAttributes={ setAttributes }
/>
</PanelBody>
</InspectorControls>
);
};
Loading

0 comments on commit 7f2f23c

Please sign in to comment.