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

Commit

Permalink
Add a stock filter block powered by the interactivity API (#11663)
Browse files Browse the repository at this point in the history
  • Loading branch information
samueljseay authored Nov 15, 2023
1 parent bdae8e9 commit be20b37
Show file tree
Hide file tree
Showing 18 changed files with 1,018 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "woocommerce/collection-stock-filter",
"version": "1.0.0",
"title": "Stock Filter",
"description": "Enable customers to filter the product collection by stock status.",
"category": "woocommerce",
"keywords": [ "WooCommerce", "filter", "stock" ],
"supports": {
"interactivity": true,
"html": false,
"multiple": false
},
"attributes": {
"className": {
"type": "string",
"default": ""
},
"showCounts": {
"type": "boolean",
"default": false
},
"displayStyle": {
"type": "string",
"default": "list"
},
"selectType": {
"type": "string",
"default": "multiple"
},
"isPreview": {
"type": "boolean",
"default": false
},
"queryParam": {
"type": "object",
"default": {
"calculate_stock_status_counts": "true"
}
}
},
"usesContext": [ "collectionData" ],
"ancestor": [ "woocommerce/collection-filters" ],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2,
"$schema": "https://schemas.wp.org/trunk/block.json"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* External dependencies
*/
import { InspectorControls } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
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 { EditProps } from '../types';

export const Inspector = ( { attributes, setAttributes }: EditProps ) => {
const { showCounts, selectType, displayStyle } = 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>
<ToggleGroupControl
label={ __(
'Display Style',
'woo-gutenberg-products-block'
) }
value={ displayStyle }
onChange={ ( value ) =>
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>
</InspectorControls>
);
};
131 changes: 131 additions & 0 deletions assets/js/blocks/collection-filters/inner-blocks/stock-filter/edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* External dependencies
*/
import { useMemo } from '@wordpress/element';
import classnames from 'classnames';
import { useBlockProps } from '@wordpress/block-editor';
import { Disabled } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { Icon, chevronDown } from '@wordpress/icons';
import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';
import { CheckboxList } from '@woocommerce/blocks-components';
import Label from '@woocommerce/base-components/filter-element-label';
import FormTokenField from '@woocommerce/base-components/form-token-field';
import type { BlockEditProps } from '@wordpress/blocks';
import { getSetting } from '@woocommerce/settings';
import {
useCollectionData,
useQueryStateByContext,
} from '@woocommerce/base-context/hooks';

/**
* Internal dependencies
*/
import { BlockProps } from './types';
import { Inspector } from './components/inspector';

type CollectionData = {
// attribute_counts: null | unknown;
// price_range: null | unknown;
// rating_counts: null | unknown;
stock_status_counts: StockStatusCount[];
};

type StockStatusCount = {
status: string;
count: number;
};

const Edit = ( props: BlockEditProps< BlockProps > & { context: Context } ) => {
const blockProps = useBlockProps( {
className: classnames(
'wc-block-stock-filter',
props.attributes.className
),
} );

const { showCounts, displayStyle } = props.attributes;
const stockStatusOptions: Record< string, string > = getSetting(
'stockStatusOptions',
{}
);

const [ queryState ] = useQueryStateByContext();

const { results: filteredCounts } = useCollectionData( {
queryStock: true,
queryState,
isEditor: true,
} );

const listOptions = useMemo( () => {
return Object.entries( stockStatusOptions ).map( ( [ key, value ] ) => {
const count =
// @ts-expect-error - there is a fault with useCollectionData types, it can be non-array.
( filteredCounts as CollectionData )?.stock_status_counts?.find(
( item: StockStatusCount ) => item.status === key
)?.count;

return {
value: key,
label: (
<Label
name={ value }
count={ showCounts && count ? Number( count ) : null }
/>
),
};
} );
}, [ stockStatusOptions, filteredCounts, showCounts ] );

return (
<>
{
<div { ...blockProps }>
<Inspector { ...props } />
<Disabled>
<div
className={ classnames(
'wc-block-stock-filter',
`style-${ displayStyle }`,
{
'is-loading': false,
}
) }
>
{ displayStyle === 'dropdown' ? (
<>
<FormTokenField
className={ classnames( {
'single-selection': true,
'is-loading': false,
} ) }
suggestions={ [] }
placeholder={ __(
'Select stock status',
'woo-gutenberg-products-block'
) }
onChange={ () => null }
value={ [] }
/>
<Icon icon={ chevronDown } size={ 30 } />
</>
) : (
<CheckboxList
className={ 'wc-block-stock-filter-list' }
options={ listOptions }
checked={ [] }
onChange={ () => null }
isLoading={ false }
isDisabled={ true }
/>
) }
</div>
</Disabled>
</div>
}
</>
);
};

export default Edit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* External dependencies
*/
import {
store as interactivityStore,
navigate,
} from '@woocommerce/interactivity';
import { DropdownContext } from '@woocommerce/interactivity-components/dropdown';
import { HTMLElementEvent } from '@woocommerce/types';

const getUrl = ( activeFilters: string ) => {
const url = new URL( window.location.href );
const { searchParams } = url;

if ( activeFilters !== '' ) {
searchParams.set( 'filter_stock_status', activeFilters );
} else {
searchParams.delete( 'filter_stock_status' );
}

return url.href;
};

type StockFilterState = {
filters: {
stockStatus: string;
activeFilters: string;
showDropdown: boolean;
};
};

type ActionProps = {
state: StockFilterState;
event: HTMLElementEvent< HTMLInputElement >;
};

interactivityStore( {
state: {
filters: {
stockStatus: '',
},
},
actions: {
filters: {
navigate: ( { context }: { context: DropdownContext } ) => {
if ( context.woocommerceDropdown.selectedItem.value ) {
navigate(
getUrl( context.woocommerceDropdown.selectedItem.value )
);
}
},
updateProducts: ( { event }: ActionProps ) => {
// get the active filters from the url:
const url = new URL( window.location.href );
const currentFilters =
url.searchParams.get( 'filter_stock_status' ) || '';

// split out the active filters into an array.
const filtersArr =
currentFilters === '' ? [] : currentFilters.split( ',' );

// if checked and not already in activeFilters, add to activeFilters
// if not checked and in activeFilters, remove from activeFilters.
if ( event.target.checked ) {
if ( ! currentFilters.includes( event.target.value ) ) {
filtersArr.push( event.target.value );
}
} else {
const index = filtersArr.indexOf( event.target.value );
if ( index > -1 ) {
filtersArr.splice( index, 1 );
}
}

navigate( getUrl( filtersArr.join( ',' ) ) );
},
},
},
} );
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import { Icon, box } from '@wordpress/icons';
import { isExperimentalBuild } from '@woocommerce/block-settings';

/**
* Internal dependencies
*/
import './style.scss';
import edit from './edit';
import metadata from './block.json';

if ( isExperimentalBuild() ) {
registerBlockType( metadata, {
icon: {
src: (
<Icon
icon={ box }
className="wc-block-editor-components-block-icon"
/>
),
},
edit,
} );
}
Loading

0 comments on commit be20b37

Please sign in to comment.