Add dropdown display style to Attribute Filter block (https://github.com/woocommerce/woocommerce-blocks/pull/1255)
* Add dropdown display style to Attribute Filter block * Unify filter blocks margin * Show attribute label inside dropdown input * Minor CSS reorganization * Refactor code to smaller files * Preserve input values on blur * Only save data-display-style if it's different than 'list' * Remove inputRef prop in DropdownSelectorInputWrapper * Accessibility: fix missing label * Prevent input field being unselected when removing an item with the backspace * Remove isLoading styles and don't set isDisabled when it's not actually disabled * Accessibility: increase color contrast * Add package-lock.json * Prevent input field being unfocused when removing an item with its chip card * Don't show menu when input is unfocused
This commit is contained in:
parent
1e92555c8c
commit
f7c807a0c6
|
@ -97,7 +97,7 @@ const CheckboxList = ( {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{ options.map( ( option, index ) => (
|
{ options.map( ( option, index ) => (
|
||||||
<Fragment key={ option.key }>
|
<Fragment key={ option.value }>
|
||||||
<li
|
<li
|
||||||
{ ...shouldTruncateOptions &&
|
{ ...shouldTruncateOptions &&
|
||||||
! showExpanded &&
|
! showExpanded &&
|
||||||
|
@ -105,13 +105,15 @@ const CheckboxList = ( {
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={ option.key }
|
id={ option.value }
|
||||||
value={ option.key }
|
value={ option.value }
|
||||||
onChange={ onChange }
|
onChange={ ( event ) => {
|
||||||
checked={ checked.includes( option.key ) }
|
onChange( event.target.value );
|
||||||
|
} }
|
||||||
|
checked={ checked.includes( option.value ) }
|
||||||
disabled={ isDisabled }
|
disabled={ isDisabled }
|
||||||
/>
|
/>
|
||||||
<label htmlFor={ option.key }>
|
<label htmlFor={ option.value }>
|
||||||
{ option.label }
|
{ option.label }
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
|
@ -152,8 +154,8 @@ CheckboxList.propTypes = {
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
options: PropTypes.arrayOf(
|
options: PropTypes.arrayOf(
|
||||||
PropTypes.shape( {
|
PropTypes.shape( {
|
||||||
key: PropTypes.string.isRequired,
|
|
||||||
label: PropTypes.node.isRequired,
|
label: PropTypes.node.isRequired,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
} )
|
} )
|
||||||
),
|
),
|
||||||
checked: PropTypes.array,
|
checked: PropTypes.array,
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useRef } from '@wordpress/element';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Downshift from 'downshift';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import DropdownSelectorInput from './input';
|
||||||
|
import DropdownSelectorInputWrapper from './input-wrapper';
|
||||||
|
import DropdownSelectorMenu from './menu';
|
||||||
|
import DropdownSelectorSelectedChip from './selected-chip';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State reducer for the downshift component.
|
||||||
|
* See: https://github.com/downshift-js/downshift#statereducer
|
||||||
|
*/
|
||||||
|
const stateReducer = ( state, changes ) => {
|
||||||
|
switch ( changes.type ) {
|
||||||
|
case Downshift.stateChangeTypes.keyDownEnter:
|
||||||
|
case Downshift.stateChangeTypes.clickItem:
|
||||||
|
return {
|
||||||
|
...changes,
|
||||||
|
highlightedIndex: state.highlightedIndex,
|
||||||
|
isOpen: true,
|
||||||
|
inputValue: '',
|
||||||
|
};
|
||||||
|
case Downshift.stateChangeTypes.blurInput:
|
||||||
|
case Downshift.stateChangeTypes.mouseUp:
|
||||||
|
return {
|
||||||
|
...changes,
|
||||||
|
inputValue: state.inputValue,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component used to show an input box with a dropdown with suggestions.
|
||||||
|
*/
|
||||||
|
const DropdownSelector = ( {
|
||||||
|
attributeLabel = '',
|
||||||
|
className,
|
||||||
|
checked = [],
|
||||||
|
inputLabel = '',
|
||||||
|
isDisabled = false,
|
||||||
|
isLoading = false,
|
||||||
|
onChange = () => {},
|
||||||
|
options = [],
|
||||||
|
} ) => {
|
||||||
|
const inputRef = useRef( null );
|
||||||
|
|
||||||
|
const classes = classNames( className, 'wc-block-dropdown-selector', {
|
||||||
|
'is-disabled': isDisabled,
|
||||||
|
'is-loading': isLoading,
|
||||||
|
} );
|
||||||
|
|
||||||
|
const focusInput = ( isOpen ) => {
|
||||||
|
if ( ! isOpen ) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Downshift
|
||||||
|
onChange={ onChange }
|
||||||
|
selectedItem={ null }
|
||||||
|
stateReducer={ stateReducer }
|
||||||
|
>
|
||||||
|
{ ( {
|
||||||
|
getInputProps,
|
||||||
|
getItemProps,
|
||||||
|
getLabelProps,
|
||||||
|
getMenuProps,
|
||||||
|
highlightedIndex,
|
||||||
|
inputValue,
|
||||||
|
isOpen,
|
||||||
|
openMenu,
|
||||||
|
} ) => (
|
||||||
|
<div className={ classes }>
|
||||||
|
{ /* eslint-disable-next-line jsx-a11y/label-has-for */ }
|
||||||
|
<label
|
||||||
|
{ ...getLabelProps( {
|
||||||
|
className: 'screen-reader-text',
|
||||||
|
} ) }
|
||||||
|
>
|
||||||
|
{ inputLabel }
|
||||||
|
</label>
|
||||||
|
<DropdownSelectorInputWrapper
|
||||||
|
isOpen={ isOpen }
|
||||||
|
onClick={ () => focusInput( isOpen ) }
|
||||||
|
>
|
||||||
|
{ checked.map( ( value ) => {
|
||||||
|
const option = options.find(
|
||||||
|
( o ) => o.value === value
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<DropdownSelectorSelectedChip
|
||||||
|
key={ value }
|
||||||
|
onRemoveItem={ ( val ) => {
|
||||||
|
onChange( val );
|
||||||
|
focusInput( isOpen );
|
||||||
|
} }
|
||||||
|
option={ option }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} ) }
|
||||||
|
<DropdownSelectorInput
|
||||||
|
attributeLabel={ attributeLabel }
|
||||||
|
checked={ checked }
|
||||||
|
getInputProps={ getInputProps }
|
||||||
|
inputRef={ inputRef }
|
||||||
|
isDisabled={ isDisabled }
|
||||||
|
onFocus={ openMenu }
|
||||||
|
onRemoveItem={ ( val ) => {
|
||||||
|
onChange( val );
|
||||||
|
focusInput( isOpen );
|
||||||
|
} }
|
||||||
|
value={ inputValue }
|
||||||
|
/>
|
||||||
|
</DropdownSelectorInputWrapper>
|
||||||
|
{ isOpen && ! isDisabled && (
|
||||||
|
<DropdownSelectorMenu
|
||||||
|
checked={ checked }
|
||||||
|
getItemProps={ getItemProps }
|
||||||
|
getMenuProps={ getMenuProps }
|
||||||
|
highlightedIndex={ highlightedIndex }
|
||||||
|
options={ options.filter(
|
||||||
|
( option ) =>
|
||||||
|
! inputValue ||
|
||||||
|
option.value.startsWith( inputValue )
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
</Downshift>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DropdownSelector.propTypes = {
|
||||||
|
attributeLabel: PropTypes.string,
|
||||||
|
checked: PropTypes.array,
|
||||||
|
className: PropTypes.string,
|
||||||
|
inputLabel: PropTypes.string,
|
||||||
|
isDisabled: PropTypes.bool,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
|
limit: PropTypes.number,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
options: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape( {
|
||||||
|
label: PropTypes.node.isRequired,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
} )
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownSelector;
|
|
@ -0,0 +1,13 @@
|
||||||
|
const DropdownSelectorInputWrapper = ( { children, onClick } ) => {
|
||||||
|
return (
|
||||||
|
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
|
||||||
|
<div
|
||||||
|
className="wc-block-dropdown-selector__input-wrapper"
|
||||||
|
onClick={ onClick }
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownSelectorInputWrapper;
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
const DropdownSelectorInput = ( {
|
||||||
|
attributeLabel,
|
||||||
|
checked,
|
||||||
|
getInputProps,
|
||||||
|
inputRef,
|
||||||
|
isDisabled,
|
||||||
|
onFocus,
|
||||||
|
onRemoveItem,
|
||||||
|
value,
|
||||||
|
} ) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
{ ...getInputProps( {
|
||||||
|
ref: inputRef,
|
||||||
|
className: 'wc-block-dropdown-selector__input',
|
||||||
|
disabled: isDisabled,
|
||||||
|
onFocus,
|
||||||
|
onKeyDown( e ) {
|
||||||
|
if (
|
||||||
|
e.key === 'Backspace' &&
|
||||||
|
! value &&
|
||||||
|
checked.length > 0
|
||||||
|
) {
|
||||||
|
onRemoveItem( checked[ checked.length - 1 ] );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
placeholder:
|
||||||
|
checked.length === 0
|
||||||
|
? sprintf(
|
||||||
|
// Translators: %s attribute name.
|
||||||
|
__( 'Any %s', 'woo-gutenberg-products-block' ),
|
||||||
|
attributeLabel
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
} ) }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownSelectorInput;
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const DropdownSelectorMenu = ( {
|
||||||
|
checked,
|
||||||
|
getItemProps,
|
||||||
|
getMenuProps,
|
||||||
|
highlightedIndex,
|
||||||
|
options,
|
||||||
|
} ) => {
|
||||||
|
return (
|
||||||
|
<ul
|
||||||
|
{ ...getMenuProps( {
|
||||||
|
className: 'wc-block-dropdown-selector__list',
|
||||||
|
} ) }
|
||||||
|
>
|
||||||
|
{ options.map( ( option, index ) => {
|
||||||
|
const selected = checked.includes( option.value );
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line react/jsx-key
|
||||||
|
<li
|
||||||
|
{ ...getItemProps( {
|
||||||
|
key: option.value,
|
||||||
|
className: classNames(
|
||||||
|
'wc-block-dropdown-selector__list-item',
|
||||||
|
{
|
||||||
|
'is-selected': selected,
|
||||||
|
'is-highlighted':
|
||||||
|
highlightedIndex === index,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
index,
|
||||||
|
item: option.value,
|
||||||
|
'aria-label': selected
|
||||||
|
? sprintf(
|
||||||
|
__(
|
||||||
|
'Remove %s filter',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
),
|
||||||
|
option.name
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
} ) }
|
||||||
|
>
|
||||||
|
{ option.label }
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
} ) }
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownSelectorMenu;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __, sprintf } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
const DropdownSelectorSelectedChip = ( { onRemoveItem, option } ) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="wc-block-dropdown-selector__selected-chip"
|
||||||
|
onClick={ ( e ) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onRemoveItem( option.value );
|
||||||
|
} }
|
||||||
|
onKeyDown={ ( e ) => {
|
||||||
|
if ( e.key === 'Backspace' || e.key === 'Delete' ) {
|
||||||
|
onRemoveItem( option.value );
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
aria-label={ sprintf(
|
||||||
|
__( 'Remove %s filter', 'woo-gutenberg-products-block' ),
|
||||||
|
option.name
|
||||||
|
) }
|
||||||
|
>
|
||||||
|
{ option.label }
|
||||||
|
<span className="wc-block-dropdown-selector__selected-chip__remove">
|
||||||
|
𝘅
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownSelectorSelectedChip;
|
|
@ -0,0 +1,105 @@
|
||||||
|
.wc-block-dropdown-selector {
|
||||||
|
max-width: 300px;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-dropdown-selector__input-wrapper {
|
||||||
|
align-items: baseline;
|
||||||
|
border: 1px solid #9f9f9f;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: text;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 2px;
|
||||||
|
|
||||||
|
.is-disabled & {
|
||||||
|
background-color: $core-grey-light-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-dropdown-selector__placeholder {
|
||||||
|
font-size: 0.8em;
|
||||||
|
height: 1.8em;
|
||||||
|
margin: 0 $gap-smallest;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-dropdown-selector__input {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.8em;
|
||||||
|
height: 1.8em;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 1.5px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-dropdown-selector__selected-chip {
|
||||||
|
background-color: $core-grey-light-600;
|
||||||
|
border: 1px solid #9f9f9f;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: $core-grey-dark-600;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: inherit;
|
||||||
|
height: 1.8em;
|
||||||
|
margin: 1.5px;
|
||||||
|
padding: 0 0 0 0.3em;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
background-color: $core-grey-light-600;
|
||||||
|
border: 1px solid #9f9f9f;
|
||||||
|
color: $core-grey-dark-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-dropdown-selector__selected-chip__remove {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-dropdown-selector__list {
|
||||||
|
list-style: none;
|
||||||
|
margin: -1px 0 0;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 100%;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&:not(:empty) {
|
||||||
|
border: 1px solid #9f9f9f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-dropdown-selector__list-item {
|
||||||
|
background-color: #fff;
|
||||||
|
color: $core-grey-dark-600;
|
||||||
|
padding: 0 $gap-smallest;
|
||||||
|
|
||||||
|
&.is-selected {
|
||||||
|
background-color: $core-grey-light-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&.is-highlighted,
|
||||||
|
&:active {
|
||||||
|
background-color: #00669e;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,8 +48,8 @@ const Label = ( {
|
||||||
};
|
};
|
||||||
|
|
||||||
Label.propTypes = {
|
Label.propTypes = {
|
||||||
label: PropTypes.string,
|
label: PropTypes.node,
|
||||||
screenReaderLabel: PropTypes.string,
|
screenReaderLabel: PropTypes.node,
|
||||||
wrapperElement: PropTypes.elementType,
|
wrapperElement: PropTypes.elementType,
|
||||||
wrapperProps: PropTypes.object,
|
wrapperProps: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,6 +47,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-block-price-filter {
|
.wc-block-price-filter {
|
||||||
|
margin-bottom: $gap-large;
|
||||||
|
|
||||||
.wc-block-price-filter__range-input-wrapper {
|
.wc-block-price-filter__range-input-wrapper {
|
||||||
@include reset;
|
@include reset;
|
||||||
height: 9px;
|
height: 9px;
|
||||||
|
@ -73,7 +75,6 @@
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0 0 20px;
|
|
||||||
|
|
||||||
.wc-block-price-filter__amount {
|
.wc-block-price-filter__amount {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.wc-block-active-filters {
|
.wc-block-active-filters {
|
||||||
margin: 0 0 $gap;
|
margin-bottom: $gap-large;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.wc-block-active-filters__clear-all {
|
.wc-block-active-filters__clear-all {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
|
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||||
|
import { speak } from '@wordpress/a11y';
|
||||||
import {
|
import {
|
||||||
useCollection,
|
useCollection,
|
||||||
useQueryStateByKey,
|
useQueryStateByKey,
|
||||||
|
@ -15,6 +17,8 @@ import {
|
||||||
useMemo,
|
useMemo,
|
||||||
} from '@wordpress/element';
|
} from '@wordpress/element';
|
||||||
import CheckboxList from '@woocommerce/base-components/checkbox-list';
|
import CheckboxList from '@woocommerce/base-components/checkbox-list';
|
||||||
|
import DropdownSelector from '@woocommerce/base-components/dropdown-selector';
|
||||||
|
import Label from '@woocommerce/base-components/label';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -39,9 +43,24 @@ const AttributeFilterBlock = ( {
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{ name }
|
{ name }
|
||||||
{ blockAttributes.showCounts && count !== null && (
|
{ blockAttributes.showCounts && count !== null && (
|
||||||
<span className="wc-block-attribute-filter-list-count">
|
<Label
|
||||||
{ count }
|
label={ count }
|
||||||
</span>
|
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>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
@ -62,15 +81,18 @@ const AttributeFilterBlock = ( {
|
||||||
blockAttributes.isPreview && ! blockAttributes.attributeId
|
blockAttributes.isPreview && ! blockAttributes.attributeId
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: 'preview-1',
|
value: 'preview-1',
|
||||||
|
name: 'Blue',
|
||||||
label: getLabel( 'Blue', 3 ),
|
label: getLabel( 'Blue', 3 ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'preview-2',
|
value: 'preview-2',
|
||||||
|
name: 'Green',
|
||||||
label: getLabel( 'Green', 3 ),
|
label: getLabel( 'Green', 3 ),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'preview-3',
|
value: 'preview-3',
|
||||||
|
name: 'Red',
|
||||||
label: getLabel( 'Red', 2 ),
|
label: getLabel( 'Red', 2 ),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -149,7 +171,8 @@ const AttributeFilterBlock = ( {
|
||||||
}
|
}
|
||||||
|
|
||||||
newOptions.push( {
|
newOptions.push( {
|
||||||
key: term.slug,
|
value: term.slug,
|
||||||
|
name: term.name,
|
||||||
label: getLabel( term.name, count ),
|
label: getLabel( term.name, count ),
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
@ -183,16 +206,37 @@ const AttributeFilterBlock = ( {
|
||||||
* When a checkbox in the list changes, update state.
|
* When a checkbox in the list changes, update state.
|
||||||
*/
|
*/
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
( event ) => {
|
( checkedValue ) => {
|
||||||
const isChecked = event.target.checked;
|
const isChecked = ! checked.includes( checkedValue );
|
||||||
const checkedValue = event.target.value;
|
|
||||||
const newChecked = checked.filter(
|
const newChecked = checked.filter(
|
||||||
( value ) => value !== checkedValue
|
( value ) => value !== checkedValue
|
||||||
);
|
);
|
||||||
|
const checkedOption = displayedOptions.find(
|
||||||
|
( option ) => option.value === checkedValue
|
||||||
|
);
|
||||||
|
|
||||||
if ( isChecked ) {
|
if ( isChecked ) {
|
||||||
newChecked.push( checkedValue );
|
newChecked.push( checkedValue );
|
||||||
newChecked.sort();
|
newChecked.sort();
|
||||||
|
speak(
|
||||||
|
sprintf(
|
||||||
|
__(
|
||||||
|
'%s filter added.',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
),
|
||||||
|
checkedOption.name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
speak(
|
||||||
|
sprintf(
|
||||||
|
__(
|
||||||
|
'%s filter removed.',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
),
|
||||||
|
checkedOption.name
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSelectedTerms = getSelectedTerms( newChecked );
|
const newSelectedTerms = getSelectedTerms( newChecked );
|
||||||
|
@ -212,6 +256,7 @@ const AttributeFilterBlock = ( {
|
||||||
setProductAttributesQuery,
|
setProductAttributesQuery,
|
||||||
attributeObject,
|
attributeObject,
|
||||||
blockAttributes,
|
blockAttributes,
|
||||||
|
displayedOptions,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -220,6 +265,8 @@ const AttributeFilterBlock = ( {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagName = `h${ blockAttributes.headingLevel }`;
|
const TagName = `h${ blockAttributes.headingLevel }`;
|
||||||
|
const isLoading = ! blockAttributes.isPreview && attributeTermsLoading;
|
||||||
|
const isDisabled = ! blockAttributes.isPreview && filteredCountsLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
@ -227,18 +274,26 @@ const AttributeFilterBlock = ( {
|
||||||
<TagName>{ blockAttributes.heading }</TagName>
|
<TagName>{ blockAttributes.heading }</TagName>
|
||||||
) }
|
) }
|
||||||
<div className="wc-block-attribute-filter">
|
<div className="wc-block-attribute-filter">
|
||||||
<CheckboxList
|
{ blockAttributes.displayStyle === 'dropdown' ? (
|
||||||
className={ 'wc-block-attribute-filter-list' }
|
<DropdownSelector
|
||||||
options={ displayedOptions }
|
attributeLabel={ attributeObject.label }
|
||||||
checked={ checked }
|
checked={ checked }
|
||||||
onChange={ onChange }
|
className={ 'wc-block-attribute-filter-dropdown' }
|
||||||
isLoading={
|
inputLabel={ blockAttributes.heading }
|
||||||
! blockAttributes.isPreview && attributeTermsLoading
|
isLoading={ isLoading }
|
||||||
}
|
onChange={ onChange }
|
||||||
isDisabled={
|
options={ displayedOptions }
|
||||||
! blockAttributes.isPreview && filteredCountsLoading
|
/>
|
||||||
}
|
) : (
|
||||||
/>
|
<CheckboxList
|
||||||
|
className={ 'wc-block-attribute-filter-list' }
|
||||||
|
options={ displayedOptions }
|
||||||
|
checked={ checked }
|
||||||
|
onChange={ onChange }
|
||||||
|
isLoading={ isLoading }
|
||||||
|
isDisabled={ isDisabled }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,6 +33,7 @@ const Edit = ( { attributes, setAttributes, debouncedSpeak } ) => {
|
||||||
const {
|
const {
|
||||||
attributeId,
|
attributeId,
|
||||||
className,
|
className,
|
||||||
|
displayStyle,
|
||||||
heading,
|
heading,
|
||||||
headingLevel,
|
headingLevel,
|
||||||
isPreview,
|
isPreview,
|
||||||
|
@ -151,6 +152,34 @@ const Edit = ( { attributes, setAttributes, debouncedSpeak } ) => {
|
||||||
} )
|
} )
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<ToggleButtonControl
|
||||||
|
label={ __(
|
||||||
|
'Display Style',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
) }
|
||||||
|
value={ displayStyle }
|
||||||
|
options={ [
|
||||||
|
{
|
||||||
|
label: __(
|
||||||
|
'List',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
),
|
||||||
|
value: 'list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __(
|
||||||
|
'Dropdown',
|
||||||
|
'woo-gutenberg-products-block'
|
||||||
|
),
|
||||||
|
value: 'dropdown',
|
||||||
|
},
|
||||||
|
] }
|
||||||
|
onChange={ ( value ) =>
|
||||||
|
setAttributes( {
|
||||||
|
displayStyle: value,
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
/>
|
||||||
</PanelBody>
|
</PanelBody>
|
||||||
<PanelBody
|
<PanelBody
|
||||||
title={ __(
|
title={ __(
|
||||||
|
|
|
@ -17,6 +17,7 @@ const getProps = ( el ) => {
|
||||||
queryType: el.dataset.queryType,
|
queryType: el.dataset.queryType,
|
||||||
heading: el.dataset.heading,
|
heading: el.dataset.heading,
|
||||||
headingLevel: el.dataset.headingLevel || 3,
|
headingLevel: el.dataset.headingLevel || 3,
|
||||||
|
displayStyle: el.dataset.displayStyle,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,6 +53,10 @@ registerBlockType( 'woocommerce/attribute-filter', {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
default: 3,
|
default: 3,
|
||||||
},
|
},
|
||||||
|
displayStyle: {
|
||||||
|
type: 'string',
|
||||||
|
default: 'list',
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Are we previewing?
|
* Are we previewing?
|
||||||
*/
|
*/
|
||||||
|
@ -73,6 +77,7 @@ registerBlockType( 'woocommerce/attribute-filter', {
|
||||||
attributeId,
|
attributeId,
|
||||||
heading,
|
heading,
|
||||||
headingLevel,
|
headingLevel,
|
||||||
|
displayStyle,
|
||||||
} = attributes;
|
} = attributes;
|
||||||
const data = {
|
const data = {
|
||||||
'data-attribute-id': attributeId,
|
'data-attribute-id': attributeId,
|
||||||
|
@ -81,6 +86,9 @@ registerBlockType( 'woocommerce/attribute-filter', {
|
||||||
'data-heading': heading,
|
'data-heading': heading,
|
||||||
'data-heading-level': headingLevel,
|
'data-heading-level': headingLevel,
|
||||||
};
|
};
|
||||||
|
if ( displayStyle !== 'list' ) {
|
||||||
|
data[ 'data-display-style' ] = displayStyle;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={ classNames( 'is-loading', className ) }
|
className={ classNames( 'is-loading', className ) }
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
.wc-block-attribute-filter {
|
.wc-block-attribute-filter {
|
||||||
|
margin-bottom: $gap-large;
|
||||||
|
|
||||||
|
.wc-block-attribute-filter-list-count::before {
|
||||||
|
content: " (";
|
||||||
|
}
|
||||||
|
.wc-block-attribute-filter-list-count::after {
|
||||||
|
content: ")";
|
||||||
|
}
|
||||||
|
|
||||||
.wc-block-attribute-filter-list {
|
.wc-block-attribute-filter-list {
|
||||||
margin: 0 0 $gap;
|
margin: 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
@ -14,11 +23,11 @@
|
||||||
.wc-block-attribute-filter-list-count {
|
.wc-block-attribute-filter-list-count {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
.wc-block-attribute-filter-list-count::before {
|
}
|
||||||
content: " (";
|
|
||||||
}
|
.wc-block-dropdown-selector {
|
||||||
.wc-block-attribute-filter-list-count::after {
|
.wc-block-dropdown-selector__list .wc-block-attribute-filter-list-count {
|
||||||
content: ")";
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7106,6 +7106,11 @@
|
||||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"compute-scroll-into-view": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-uUnglJowSe0IPmWOdDtrlHXof5CTIJitfJEyITHBW6zDVOGu9Pjk5puaLM73SLcwak0L4hEjO7Td88/a6P5i7A=="
|
||||||
|
},
|
||||||
"computed-style": {
|
"computed-style": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz",
|
||||||
|
@ -8176,6 +8181,17 @@
|
||||||
"is-obj": "^1.0.0"
|
"is-obj": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"downshift": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/downshift/-/downshift-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-lk0Q1VF4eTDe4EMzYtdVCPdu58ZRFyK3wxEAGUeKqPRDoHDgoS9/TaxW2w+hEbeh9yBMU2IKX8lQkNn6YTfZ4w==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.4.5",
|
||||||
|
"compute-scroll-into-view": "^1.0.9",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react-is": "^16.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"duplexer": {
|
"duplexer": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
||||||
|
|
|
@ -106,6 +106,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@woocommerce/components": "4.0.0",
|
"@woocommerce/components": "4.0.0",
|
||||||
"compare-versions": "3.5.1",
|
"compare-versions": "3.5.1",
|
||||||
|
"downshift": "^3.4.2",
|
||||||
"eslint-plugin-woocommerce": "file:bin/eslint-plugin-woocommerce",
|
"eslint-plugin-woocommerce": "file:bin/eslint-plugin-woocommerce",
|
||||||
"gridicons": "3.3.1",
|
"gridicons": "3.3.1",
|
||||||
"react-number-format": "4.3.1",
|
"react-number-format": "4.3.1",
|
||||||
|
|
Loading…
Reference in New Issue