Add 'Go' button to Attribute Filter (https://github.com/woocommerce/woocommerce-blocks/pull/1332)
* Add 'Go' button to Attribute Filter * Code cleanup * Fixes * Avoid onSubmit being called in the editor * Update attributes help texts * Fix wrong input check box width
This commit is contained in:
parent
58d4d73b51
commit
0fa4dd05a5
|
@ -18,7 +18,11 @@ const Label = ( {
|
|||
} ) => {
|
||||
let Wrapper;
|
||||
|
||||
if ( ! label && screenReaderLabel ) {
|
||||
const hasLabel = typeof label !== 'undefined' && label !== null;
|
||||
const hasScreenReaderLabel =
|
||||
typeof screenReaderLabel !== 'undefined' && screenReaderLabel !== null;
|
||||
|
||||
if ( ! hasLabel && hasScreenReaderLabel ) {
|
||||
Wrapper = wrapperElement || 'span';
|
||||
wrapperProps = {
|
||||
...wrapperProps,
|
||||
|
@ -33,7 +37,7 @@ const Label = ( {
|
|||
|
||||
Wrapper = wrapperElement || Fragment;
|
||||
|
||||
if ( label && screenReaderLabel && label !== screenReaderLabel ) {
|
||||
if ( hasLabel && hasScreenReaderLabel && label !== screenReaderLabel ) {
|
||||
return (
|
||||
<Wrapper { ...wrapperProps }>
|
||||
<span aria-hidden="true">{ label }</span>
|
||||
|
|
|
@ -19,7 +19,7 @@ import classnames from 'classnames';
|
|||
import './style.scss';
|
||||
import { constrainRangeSliderValues } from './utils';
|
||||
import { formatPrice } from '../../utils/price';
|
||||
import SubmitButton from './submit-button';
|
||||
import SubmitButton from '../submit-button';
|
||||
import PriceLabel from './price-label';
|
||||
import PriceInput from './price-input';
|
||||
|
||||
|
@ -307,6 +307,7 @@ const PriceSlider = ( {
|
|||
) }
|
||||
{ showFilterButton && (
|
||||
<SubmitButton
|
||||
className="wc-block-price-filter__button"
|
||||
disabled={ isLoading || ! hasValidConstraints }
|
||||
onClick={ onSubmit }
|
||||
/>
|
||||
|
|
|
@ -96,11 +96,6 @@
|
|||
margin-left: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.wc-block-price-filter__button {
|
||||
margin-left: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wc-block-price-filter__range-input {
|
||||
|
|
|
@ -3,16 +3,22 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const SubmitButton = ( { disabled, onClick } ) => {
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
const SubmitButton = ( { className, disabled, onClick } ) => {
|
||||
return (
|
||||
<button
|
||||
type="submit"
|
||||
className="wc-block-price-filter__button wc-block-form-button"
|
||||
className={ classNames( 'wc-block-submit-button', className ) }
|
||||
disabled={ disabled }
|
||||
onClick={ onClick }
|
||||
>
|
||||
{ // translators: Submit button text for the price filter.
|
||||
{ // translators: Submit button text for filters.
|
||||
__( 'Go', 'woo-gutenberg-products-block' ) }
|
||||
</button>
|
||||
);
|
|
@ -0,0 +1,5 @@
|
|||
.wc-block-submit-button {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
white-space: nowrap;
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { speak } from '@wordpress/a11y';
|
||||
import {
|
||||
useCollection,
|
||||
useQueryStateByKey,
|
||||
useQueryStateByContext,
|
||||
useCollectionData,
|
||||
useShallowEqual,
|
||||
} from '@woocommerce/base-hooks';
|
||||
import {
|
||||
useCallback,
|
||||
|
@ -18,14 +19,17 @@ import {
|
|||
} from '@wordpress/element';
|
||||
import CheckboxList from '@woocommerce/base-components/checkbox-list';
|
||||
import DropdownSelector from '@woocommerce/base-components/dropdown-selector';
|
||||
import Label from '@woocommerce/base-components/label';
|
||||
import SubmitButton from '@woocommerce/base-components/submit-button';
|
||||
import isShallowEqual from '@wordpress/is-shallow-equal';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import { getAttributeFromID } from '../../utils/attributes';
|
||||
import { updateAttributeFilter } from '../../utils/attributes-query';
|
||||
import Label from './label';
|
||||
import { previewAttributeObject, previewOptions } from './preview';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Component displaying an attribute filter.
|
||||
|
@ -34,68 +38,15 @@ const AttributeFilterBlock = ( {
|
|||
attributes: blockAttributes,
|
||||
isEditor = false,
|
||||
} ) => {
|
||||
/**
|
||||
* Get the label for an attribute term filter.
|
||||
*/
|
||||
const getLabel = useCallback(
|
||||
( name, count ) => {
|
||||
return (
|
||||
<Fragment>
|
||||
{ name }
|
||||
{ blockAttributes.showCounts && count !== null && (
|
||||
<Label
|
||||
label={ count }
|
||||
screenReaderLabel={ sprintf(
|
||||
// translators: %s number of products.
|
||||
_n(
|
||||
'%s product',
|
||||
'%s products',
|
||||
count,
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
count
|
||||
) }
|
||||
wrapperElement="span"
|
||||
wrapperProps={ {
|
||||
className:
|
||||
'wc-block-attribute-filter-list-count',
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
},
|
||||
[ blockAttributes ]
|
||||
);
|
||||
|
||||
const attributeObject =
|
||||
blockAttributes.isPreview && ! blockAttributes.attributeId
|
||||
? {
|
||||
id: 0,
|
||||
name: 'preview',
|
||||
taxonomy: 'preview',
|
||||
label: 'Preview',
|
||||
}
|
||||
? previewAttributeObject
|
||||
: getAttributeFromID( blockAttributes.attributeId );
|
||||
|
||||
const [ checked, setChecked ] = useState( [] );
|
||||
const [ displayedOptions, setDisplayedOptions ] = useState(
|
||||
blockAttributes.isPreview && ! blockAttributes.attributeId
|
||||
? [
|
||||
{
|
||||
value: 'preview-1',
|
||||
name: 'Blue',
|
||||
label: getLabel( 'Blue', 3 ),
|
||||
},
|
||||
{
|
||||
value: 'preview-2',
|
||||
name: 'Green',
|
||||
label: getLabel( 'Green', 3 ),
|
||||
},
|
||||
{
|
||||
value: 'preview-3',
|
||||
name: 'Red',
|
||||
label: getLabel( 'Red', 2 ),
|
||||
},
|
||||
]
|
||||
? previewOptions
|
||||
: []
|
||||
);
|
||||
|
||||
|
@ -105,15 +56,6 @@ const AttributeFilterBlock = ( {
|
|||
setProductAttributesQuery,
|
||||
] = useQueryStateByKey( 'attributes', [] );
|
||||
|
||||
const checked = useMemo( () => {
|
||||
return productAttributesQuery
|
||||
.filter(
|
||||
( attribute ) =>
|
||||
attribute.attribute === attributeObject.taxonomy
|
||||
)
|
||||
.flatMap( ( attribute ) => attribute.slug );
|
||||
}, [ productAttributesQuery, attributeObject ] );
|
||||
|
||||
const {
|
||||
results: attributeTerms,
|
||||
isLoading: attributeTermsLoading,
|
||||
|
@ -157,39 +99,89 @@ const AttributeFilterBlock = ( {
|
|||
* Compare intersection of all terms and filtered counts to get a list of options to display.
|
||||
*/
|
||||
useEffect( () => {
|
||||
/**
|
||||
* Checks if a term slug is in the query state.
|
||||
*/
|
||||
const isTermInQueryState = ( termSlug ) => {
|
||||
if ( ! queryState || ! queryState.attributes ) {
|
||||
return false;
|
||||
}
|
||||
return queryState.attributes.some(
|
||||
( { attribute, slug = [] } ) =>
|
||||
attribute === attributeObject.taxonomy &&
|
||||
slug.includes( termSlug )
|
||||
);
|
||||
};
|
||||
|
||||
if ( attributeTermsLoading || filteredCountsLoading ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newOptions = [];
|
||||
const newOptions = attributeTerms
|
||||
.map( ( term ) => {
|
||||
const filteredTerm = getFilteredTerm( term.id );
|
||||
|
||||
attributeTerms.forEach( ( term ) => {
|
||||
const filteredTerm = getFilteredTerm( term.id );
|
||||
const isChecked = checked.includes( term.slug );
|
||||
const count = filteredTerm ? filteredTerm.count : null;
|
||||
// If there is no match this term doesn't match the current product collection - only render if checked.
|
||||
if (
|
||||
! filteredTerm &&
|
||||
! checked.includes( term.slug ) &&
|
||||
! isTermInQueryState( term.slug )
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If there is no match this term doesn't match the current product collection - only render if checked.
|
||||
if ( ! filteredTerm && ! isChecked ) {
|
||||
return;
|
||||
}
|
||||
const count = filteredTerm ? filteredTerm.count : 0;
|
||||
|
||||
newOptions.push( {
|
||||
value: term.slug,
|
||||
name: term.name,
|
||||
label: getLabel( term.name, count ),
|
||||
} );
|
||||
} );
|
||||
return {
|
||||
value: term.slug,
|
||||
name: term.name,
|
||||
label: (
|
||||
<Label
|
||||
name={ term.name }
|
||||
count={ blockAttributes.showCounts ? count : null }
|
||||
/>
|
||||
),
|
||||
};
|
||||
} )
|
||||
.filter( Boolean );
|
||||
|
||||
setDisplayedOptions( newOptions );
|
||||
}, [
|
||||
attributeObject.taxonomy,
|
||||
attributeTerms,
|
||||
attributeTermsLoading,
|
||||
blockAttributes.showCounts,
|
||||
filteredCountsLoading,
|
||||
getFilteredTerm,
|
||||
getLabel,
|
||||
checked,
|
||||
queryState.attributes,
|
||||
] );
|
||||
|
||||
// Track checked STATE changes - if state changes, update the query.
|
||||
useEffect(
|
||||
() => {
|
||||
if ( ! blockAttributes.showFilterButton ) {
|
||||
onSubmit();
|
||||
}
|
||||
},
|
||||
// There is no need to add blockAttributes.showFilterButton as a dependency.
|
||||
// It will only change in the editor and there we don't need to call onSubmit in any case.
|
||||
[ checked, onSubmit ]
|
||||
);
|
||||
|
||||
const curentCheckedQuery = useShallowEqual( checkedQuery );
|
||||
|
||||
// Track ATTRIBUTES QUERY changes so the block reflects current filters.
|
||||
useEffect(
|
||||
() => {
|
||||
if ( ! isShallowEqual( checked, checkedQuery ) ) {
|
||||
setChecked( checkedQuery );
|
||||
}
|
||||
},
|
||||
// We only want to apply this effect when the query changes, so we are intentionally leaving `checked` out of the dependencies.
|
||||
[ curentCheckedQuery ]
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns an array of term objects that have been chosen via the checkboxes.
|
||||
*/
|
||||
|
@ -205,6 +197,28 @@ const AttributeFilterBlock = ( {
|
|||
[ attributeTerms ]
|
||||
);
|
||||
|
||||
const onSubmit = () => {
|
||||
if ( isEditor ) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateAttributeFilter(
|
||||
productAttributesQuery,
|
||||
setProductAttributesQuery,
|
||||
attributeObject,
|
||||
getSelectedTerms( checked ),
|
||||
blockAttributes.queryType === 'or' ? 'in' : 'and'
|
||||
);
|
||||
};
|
||||
|
||||
const checkedQuery = useMemo( () => {
|
||||
return productAttributesQuery
|
||||
.filter(
|
||||
( { attribute } ) => attribute === attributeObject.taxonomy
|
||||
)
|
||||
.flatMap( ( { slug } ) => slug );
|
||||
}, [ productAttributesQuery, attributeObject.taxonomy ] );
|
||||
|
||||
const multiple =
|
||||
blockAttributes.displayStyle !== 'dropdown' ||
|
||||
blockAttributes.queryType === 'or';
|
||||
|
@ -289,17 +303,9 @@ const AttributeFilterBlock = ( {
|
|||
}
|
||||
}
|
||||
|
||||
const newSelectedTerms = getSelectedTerms( newChecked );
|
||||
|
||||
updateAttributeFilter(
|
||||
productAttributesQuery,
|
||||
setProductAttributesQuery,
|
||||
attributeObject,
|
||||
newSelectedTerms,
|
||||
blockAttributes.queryType === 'or' ? 'in' : 'and'
|
||||
);
|
||||
setChecked( newChecked );
|
||||
},
|
||||
[ displayedOptions, multiple ]
|
||||
[ checked, displayedOptions, multiple ]
|
||||
);
|
||||
|
||||
if ( displayedOptions.length === 0 && ! attributeTermsLoading ) {
|
||||
|
@ -337,6 +343,13 @@ const AttributeFilterBlock = ( {
|
|||
isDisabled={ isDisabled }
|
||||
/>
|
||||
) }
|
||||
{ blockAttributes.showFilterButton && (
|
||||
<SubmitButton
|
||||
className="wc-block-attribute-filter__button"
|
||||
disabled={ isLoading || isDisabled }
|
||||
onClick={ onSubmit }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -39,6 +39,7 @@ const Edit = ( { attributes, setAttributes, debouncedSpeak } ) => {
|
|||
isPreview,
|
||||
queryType,
|
||||
showCounts,
|
||||
showFilterButton,
|
||||
} = attributes;
|
||||
|
||||
const [ isEditing, setIsEditing ] = useState(
|
||||
|
@ -180,6 +181,29 @@ const Edit = ( { attributes, setAttributes, debouncedSpeak } ) => {
|
|||
} )
|
||||
}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Filter button',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
help={
|
||||
showFilterButton
|
||||
? __(
|
||||
'Products will only update when the button is pressed.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
: __(
|
||||
'Products will update as options are selected.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
}
|
||||
checked={ showFilterButton }
|
||||
onChange={ ( value ) =>
|
||||
setAttributes( {
|
||||
showFilterButton: value,
|
||||
} )
|
||||
}
|
||||
/>
|
||||
</PanelBody>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
|
|
|
@ -18,6 +18,7 @@ const getProps = ( el ) => {
|
|||
heading: el.dataset.heading,
|
||||
headingLevel: el.dataset.headingLevel || 3,
|
||||
displayStyle: el.dataset.displayStyle,
|
||||
showFilterButton: el.dataset.showFilterButton === 'true',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -57,6 +57,10 @@ registerBlockType( 'woocommerce/attribute-filter', {
|
|||
type: 'string',
|
||||
default: 'list',
|
||||
},
|
||||
showFilterButton: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Are we previewing?
|
||||
*/
|
||||
|
@ -78,6 +82,7 @@ registerBlockType( 'woocommerce/attribute-filter', {
|
|||
heading,
|
||||
headingLevel,
|
||||
displayStyle,
|
||||
showFilterButton,
|
||||
} = attributes;
|
||||
const data = {
|
||||
'data-attribute-id': attributeId,
|
||||
|
@ -89,6 +94,9 @@ registerBlockType( 'woocommerce/attribute-filter', {
|
|||
if ( displayStyle !== 'list' ) {
|
||||
data[ 'data-display-style' ] = displayStyle;
|
||||
}
|
||||
if ( showFilterButton ) {
|
||||
data[ 'data-show-filter-button' ] = showFilterButton;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={ classNames( 'is-loading', className ) }
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { _n, sprintf } from '@wordpress/i18n';
|
||||
import { Fragment } from '@wordpress/element';
|
||||
import Label from '@woocommerce/base-components/label';
|
||||
|
||||
/**
|
||||
* The label for an attribute term filter.
|
||||
*/
|
||||
const AttributeFilterLabel = ( { name, count } ) => {
|
||||
return (
|
||||
<Fragment>
|
||||
{ name }
|
||||
{ Number.isFinite( count ) && (
|
||||
<Label
|
||||
label={ count }
|
||||
screenReaderLabel={ sprintf(
|
||||
// translators: %s number of products.
|
||||
_n(
|
||||
'%s product',
|
||||
'%s products',
|
||||
count,
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
count
|
||||
) }
|
||||
wrapperElement="span"
|
||||
wrapperProps={ {
|
||||
className: 'wc-block-attribute-filter-list-count',
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttributeFilterLabel;
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Label from './label';
|
||||
|
||||
export const previewOptions = [
|
||||
{
|
||||
value: 'preview-1',
|
||||
name: 'Blue',
|
||||
label: <Label name="Blue" count={ 3 } />,
|
||||
},
|
||||
{
|
||||
value: 'preview-2',
|
||||
name: 'Green',
|
||||
label: <Label name="Green" count={ 3 } />,
|
||||
},
|
||||
{
|
||||
value: 'preview-3',
|
||||
name: 'Red',
|
||||
label: <Label name="Red" count={ 2 } />,
|
||||
},
|
||||
];
|
||||
|
||||
export const previewAttributeObject = {
|
||||
id: 0,
|
||||
name: 'preview',
|
||||
taxonomy: 'preview',
|
||||
label: 'Preview',
|
||||
};
|
|
@ -19,6 +19,7 @@
|
|||
label,
|
||||
input {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,4 +32,8 @@
|
|||
.wc-block-dropdown-selector .wc-block-dropdown-selector__list .wc-block-attribute-filter-list-count {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.wc-block-attribute-filter__button {
|
||||
margin-top: $gap-smaller;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,11 +78,11 @@ export default function( { attributes, setAttributes } ) {
|
|||
help={
|
||||
showFilterButton
|
||||
? __(
|
||||
'Results will only update when the button is pressed.',
|
||||
'Products will only update when the button is pressed.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
: __(
|
||||
'Results will update when the slider is moved.',
|
||||
'Products will update when the slider is moved.',
|
||||
'woo-gutenberg-products-block'
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue