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

Add dropdown version of Filter by Stock Status #7831

Merged
merged 22 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
425d391
Extend Filter by Stock Editor options with dropdown and single/multip…
kmanijak Dec 2, 2022
6339b5e
Add dropdown implementation for Filter by Stock Status
kmanijak Dec 2, 2022
1fa92ce
Merge branch 'trunk' into add/filter-by-stock-dropdown
kmanijak Dec 6, 2022
bc82358
Adjust font-sizes to the rest of the filters
kmanijak Dec 6, 2022
45b7406
Fix logical bug in onChange handler of Filter by Stock
kmanijak Dec 6, 2022
b00a16e
Add tests to Filter by Stock: dropdown and list variants
kmanijak Dec 6, 2022
006cf70
Add E2E test to Filter by Stock checking if display style can be toggled
kmanijak Dec 6, 2022
2836188
When typing in Filter by Stock dropdown, handle the space so it highl…
kmanijak Dec 6, 2022
c97b6e7
Revert Editor Settings label to align to Filter by Rating and Attribute
kmanijak Dec 7, 2022
a8efbbc
Typescript errors fixes and adopting previous Filter by Stock tests t…
kmanijak Dec 7, 2022
b4ad0a5
Change the name of the filter blocks in the test files
kmanijak Dec 8, 2022
7256bab
Remove unnecessary waiting step in E2E test for Filter by Stock
kmanijak Dec 8, 2022
1c02855
Improve the STOCK_STATUS_OPTIONS type handling
kmanijak Dec 8, 2022
bfec264
Rename vague variable to
kmanijak Dec 9, 2022
03ef806
Remove unnecessary type
kmanijak Dec 9, 2022
5ca1ccf
Remove unnecessary style
kmanijak Dec 9, 2022
edfcd98
Extract onDropdownChange function instead of inline arrow function
kmanijak Dec 9, 2022
c6fc632
Merge branch 'trunk' into add/filter-by-stock-dropdown
kmanijak Dec 13, 2022
fac7ef1
Fix overlaping dropdown content with the wrapper when Filter by Stock…
kmanijak Dec 13, 2022
102edbb
Merge branch 'trunk' into add/filter-by-stock-dropdown
kmanijak Dec 13, 2022
dca4315
Merge branch 'trunk' into add/filter-by-stock-dropdown
kmanijak Dec 14, 2022
1205976
Merge branch 'trunk' into add/filter-by-stock-dropdown
kmanijak Dec 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/js/blocks/attribute-filter/test/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jest.mock( '@woocommerce/base-context/hooks', () => ( {
...jest.requireActual( '@woocommerce/base-context/hooks' ),
} ) );

const setWindowUrl = ( { url }: SetWindowUrlParams ) => {
const setWindowUrl = ( { url }: { url: string } ) => {
global.window = Object.create( window );
Object.defineProperty( window, 'location', {
value: {
Expand Down
8 changes: 8 additions & 0 deletions assets/js/blocks/stock-filter/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
"type": "boolean",
"default": false
},
"displayStyle": {
"type": "string",
"default": "list"
},
"selectType": {
"type": "string",
"default": "multiple"
},
"isPreview": {
"type": "boolean",
"default": false
Expand Down
164 changes: 140 additions & 24 deletions assets/js/blocks/stock-filter/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
*/
import { __, sprintf } from '@wordpress/i18n';
import { speak } from '@wordpress/a11y';
import { usePrevious, useShallowEqual } from '@woocommerce/base-hooks';
import { Icon, chevronDown } from '@wordpress/icons';
import {
usePrevious,
useShallowEqual,
useBorderProps,
} from '@woocommerce/base-hooks';
import {
useQueryStateByKey,
useQueryStateByContext,
Expand All @@ -22,20 +27,22 @@ import FilterSubmitButton from '@woocommerce/base-components/filter-submit-butto
import FilterResetButton from '@woocommerce/base-components/filter-reset-button';
import FilterTitlePlaceholder from '@woocommerce/base-components/filter-placeholder';
import Label from '@woocommerce/base-components/filter-element-label';
import FormTokenField from '@woocommerce/base-components/form-token-field';
import isShallowEqual from '@wordpress/is-shallow-equal';
import { decodeEntities } from '@wordpress/html-entities';
import { isBoolean, objectHasProp } from '@woocommerce/types';
import { addQueryArgs, removeQueryArgs } from '@wordpress/url';
import { changeUrl, PREFIX_QUERY_ARG_FILTER_TYPE } from '@woocommerce/utils';
import { difference } from 'lodash';
import classnames from 'classnames';

/**
* Internal dependencies
*/
import { previewOptions } from './preview';
import './style.scss';
import { getActiveFilters } from './utils';
import { Attributes, DisplayOption } from './types';
import { formatSlug, getActiveFilters, generateUniqueId } from './utils';
import { Attributes, DisplayOption, Current } from './types';
import { useSetWraperVisibility } from '../filter-wrapper/context';

export const QUERY_PARAM_KEY = PREFIX_QUERY_ARG_FILTER_TYPE + 'stock_status';
Expand Down Expand Up @@ -74,7 +81,7 @@ const StockStatusFilterBlock = ( {
? []
: getSettingWithCoercion( 'product_ids', [], Array.isArray );

const STOCK_STATUS_OPTIONS = useRef(
const STOCK_STATUS_OPTIONS: { current: Current } = useRef(
getSetting( 'hideOutOfStockItems', false )
? otherStockStatusOptions
: { outofstock, ...otherStockStatusOptions }
Expand Down Expand Up @@ -127,6 +134,15 @@ const StockStatusFilterBlock = ( {
[ filteredCounts ]
);

/*
FormTokenField forces the dropdown to reopen on reset, so we create a unique ID to use as the components key.
This will force the component to remount on reset when we change this value.
More info: https://github.com/woocommerce/woocommerce-blocks/pull/6920#issuecomment-1222402482
*/
const [ remountKey, setRemountKey ] = useState( generateUniqueId() );

const borderProps = useBorderProps( blockAttributes );

/**
* Compare intersection of all stock statuses and filtered counts to get a list of options to display.
*/
Expand Down Expand Up @@ -173,11 +189,15 @@ const StockStatusFilterBlock = ( {
count={ blockAttributes.showCounts ? count : null }
/>
),
textLabel: blockAttributes.showCounts
? `${ decodeEntities( status.name ) } (${ count })`
: decodeEntities( status.name ),
};
} )
.filter( ( option ): option is DisplayOption => !! option );

setDisplayedOptions( newOptions );
setRemountKey( generateUniqueId() );
}, [
blockAttributes.showCounts,
blockAttributes.isPreview,
Expand Down Expand Up @@ -221,6 +241,8 @@ const StockStatusFilterBlock = ( {
changeUrl( newUrl );
};

const allowsMultipleOptions = blockAttributes.selectType !== 'single';

const onSubmit = useCallback(
( checkedOptions ) => {
if ( isEditor ) {
Expand Down Expand Up @@ -327,23 +349,59 @@ const StockStatusFilterBlock = ( {

const previouslyChecked = checked.includes( checkedValue );

const newChecked = checked.filter(
( value ) => value !== checkedValue
);
if ( ! allowsMultipleOptions ) {
const newChecked = previouslyChecked ? [] : [ checkedValue ];
announceFilterChange(
previouslyChecked
? { filterRemoved: checkedValue }
: { filterAdded: checkedValue }
);
setChecked( newChecked );
return;
}

if ( previouslyChecked ) {
const newChecked = checked.filter(
( value ) => value !== checkedValue
);

if ( ! previouslyChecked ) {
newChecked.push( checkedValue );
newChecked.sort();
announceFilterChange( { filterAdded: checkedValue } );
} else {
announceFilterChange( { filterRemoved: checkedValue } );
setChecked( newChecked );
return;
}

const newChecked = [ ...checked, checkedValue ].sort();
announceFilterChange( { filterAdded: checkedValue } );
setChecked( newChecked );
},
[ checked, displayedOptions ]
[ checked, allowsMultipleOptions, displayedOptions ]
);

const onDropdownChange = ( tokens: string[] ) => {
if ( ! allowsMultipleOptions && tokens.length > 1 ) {
tokens = tokens.slice( -1 );
}

tokens = tokens.map( ( token ) => {
const displayOption = displayedOptions.find(
( option ) => option.value === token
);

return displayOption ? displayOption.value : token;
} );

const added = difference( tokens, checked );

if ( added.length === 1 ) {
return onChange( added[ 0 ] );
}

const removed = difference( checked, tokens );
if ( removed.length === 1 ) {
onChange( removed[ 0 ] );
}
};

if ( ! filteredCountsLoading && displayedOptions.length === 0 ) {
setWrapperVisibility( false );
return null;
Expand Down Expand Up @@ -385,18 +443,76 @@ const StockStatusFilterBlock = ( {
<>
{ ! isEditor && blockAttributes.heading && filterHeading }
<div
className={ classnames( 'wc-block-stock-filter', {
'is-loading': isLoading,
} ) }
className={ classnames(
'wc-block-stock-filter',
`style-${ blockAttributes.displayStyle }`,
{
'is-loading': isLoading,
}
) }
>
<CheckboxList
className={ 'wc-block-stock-filter-list' }
options={ displayedOptions }
checked={ checked }
onChange={ onChange }
isLoading={ isLoading }
isDisabled={ isDisabled }
/>
{ blockAttributes.displayStyle === 'dropdown' ? (
<>
<FormTokenField
key={ remountKey }
className={ classnames( borderProps.className, {
'single-selection': ! allowsMultipleOptions,
'is-loading': isLoading,
} ) }
style={ { ...borderProps.style } }
suggestions={ displayedOptions
.filter(
( option ) =>
! checked.includes( option.value )
)
.map( ( option ) => option.value ) }
disabled={ isLoading }
placeholder={ __(
'Select stock status',
'woo-gutenberg-products-block'
) }
onChange={ onDropdownChange }
value={ checked }
displayTransform={ ( value: string ) => {
kmanijak marked this conversation as resolved.
Show resolved Hide resolved
const result = displayedOptions.find(
( option ) => option.value === value
);
return result ? result.textLabel : value;
} }
saveTransform={ formatSlug }
messages={ {
kmanijak marked this conversation as resolved.
Show resolved Hide resolved
added: __(
'Stock filter added.',
'woo-gutenberg-products-block'
),
removed: __(
'Stock filter removed.',
'woo-gutenberg-products-block'
),
remove: __(
'Remove stock filter.',
'woo-gutenberg-products-block'
),
__experimentalInvalid: __(
'Invalid stock filter.',
'woo-gutenberg-products-block'
),
} }
/>
{ allowsMultipleOptions && (
<Icon icon={ chevronDown } size={ 30 } />
) }
</>
) : (
<CheckboxList
className={ 'wc-block-stock-filter-list' }
options={ displayedOptions }
checked={ checked }
onChange={ onChange }
isLoading={ isLoading }
isDisabled={ isDisabled }
/>
) }
</div>
{
<div className="wc-block-stock-filter__actions">
Expand Down
84 changes: 75 additions & 9 deletions assets/js/blocks/stock-filter/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
import { __ } from '@wordpress/i18n';
import classnames from 'classnames';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import BlockTitle from '@woocommerce/editor-components/block-title';
import type { BlockEditProps } from '@wordpress/blocks';
import {
Disabled,
PanelBody,
ToggleControl,
withSpokenMessages,
// 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';
import BlockTitle from '@woocommerce/editor-components/block-title';
import type { BlockEditProps } from '@wordpress/blocks';

/**
* Internal dependencies
Expand All @@ -26,8 +30,15 @@ const Edit = ( {
attributes,
setAttributes,
}: BlockEditProps< Attributes > ) => {
const { className, heading, headingLevel, showCounts, showFilterButton } =
attributes;
const {
className,
heading,
headingLevel,
showCounts,
showFilterButton,
selectType,
displayStyle,
} = attributes;

const blockProps = useBlockProps( {
className: classnames( 'wc-block-stock-filter', className ),
Expand All @@ -37,7 +48,10 @@ const Edit = ( {
return (
<InspectorControls key="inspector">
<PanelBody
title={ __( 'Content', 'woo-gutenberg-products-block' ) }
title={ __(
'Display Settings',
'woo-gutenberg-products-block'
) }
>
<ToggleControl
label={ __(
Expand All @@ -51,10 +65,62 @@ const Edit = ( {
} )
}
/>
</PanelBody>
<PanelBody
title={ __( 'Settings', 'woo-gutenberg-products-block' ) }
>
<ToggleGroupControl
label={ __(
'Allow selecting multiple options?',
'woo-gutenberg-products-block'
) }
value={ selectType || 'multiple' }
onChange={ ( value: string ) =>
kmanijak marked this conversation as resolved.
Show resolved Hide resolved
setAttributes( {
selectType: value,
} )
}
kmanijak marked this conversation as resolved.
Show resolved Hide resolved
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'
) }
/>
kmanijak marked this conversation as resolved.
Show resolved Hide resolved
</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>
<ToggleControl
label={ __(
"Show 'Apply filters' button",
Expand Down
Loading