* 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:
Albert Juhé Lluveras 2019-12-06 16:36:55 +01:00 committed by GitHub
parent 58d4d73b51
commit 0fa4dd05a5
13 changed files with 237 additions and 108 deletions

View File

@ -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>

View File

@ -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 }
/>

View File

@ -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 {

View File

@ -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>
);

View File

@ -0,0 +1,5 @@
.wc-block-submit-button {
display: block;
margin-left: auto;
white-space: nowrap;
}

View File

@ -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>
);

View File

@ -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={ __(

View File

@ -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',
},
};
};

View File

@ -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 ) }

View File

@ -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;

View File

@ -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',
};

View File

@ -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;
}
}

View File

@ -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'
)
}