* Improve cross character used in dropdown selector

* Add is-open and has-checked classes to dropdown selector so we stop relying on complex pseudoselector combinations

* Update Chip component with new props

* Update Chip styles

* Update dropdown selector selected chip with the Chip component

* Update active filters chip with the Chip component

* Style fixes

* Update snapshots and add tests

* Change onRemove prop signature

* Fix wrong bottom margin

* Fix misaligned remove icon

* Update assets/js/base/components/chip/index.js

Co-authored-by: Darren Ethier <darren@roughsmootheng.in>

* Update assets/js/base/components/chip/index.js

Co-authored-by: Darren Ethier <darren@roughsmootheng.in>

* Add missing default prop

* Prettier

* Ensure showScreenReaderText is a bool

* Rename removeOnClick with removeOnAnyClick

* Update snapshots

* Fix Chip aria-label logic

* Split Chip and RemovableChip

* Replace chip close character with an icon

* Fix outdated comment

Co-authored-by: Darren Ethier <darren@roughsmootheng.in>
This commit is contained in:
Albert Juhé Lluveras 2020-07-10 11:09:49 +02:00 committed by GitHub
parent 92067c0d04
commit 8c06276b00
15 changed files with 693 additions and 316 deletions

View File

@ -4,7 +4,7 @@
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings'; import { DISPLAY_CART_PRICES_INCLUDING_TAX } from '@woocommerce/block-settings';
import LoadingMask from '@woocommerce/base-components/loading-mask'; import LoadingMask from '@woocommerce/base-components/loading-mask';
import Chip from '@woocommerce/base-components/chip'; import { RemovableChip } from '@woocommerce/base-components/chip';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
/** /**
@ -51,7 +51,7 @@ const TotalsDiscountItem = ( {
> >
<ul className="wc-block-components-totals-discount__coupon-list"> <ul className="wc-block-components-totals-discount__coupon-list">
{ cartCoupons.map( ( cartCoupon ) => ( { cartCoupons.map( ( cartCoupon ) => (
<Chip <RemovableChip
key={ 'coupon-' + cartCoupon.code } key={ 'coupon-' + cartCoupon.code }
className="wc-block-components-totals-discount__coupon-list-item" className="wc-block-components-totals-discount__coupon-list-item"
text={ cartCoupon.code } text={ cartCoupon.code }
@ -68,6 +68,14 @@ const TotalsDiscountItem = ( {
removeCoupon( cartCoupon.code ); removeCoupon( cartCoupon.code );
} } } }
radius="large" radius="large"
ariaLabel={ sprintf(
/* Translators: %s is a coupon code. */
__(
'Remove coupon "%s"',
'woo-gutenberg-products-block'
),
cartCoupon.code
) }
/> />
) ) } ) ) }
</ul> </ul>

View File

@ -0,0 +1,63 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import classNames from 'classnames';
/**
* Internal dependencies
*/
import './style.scss';
/**
* Component used to render a "chip" -- a list item containing some text.
*
* Each chip defaults to a list element but this can be customized by providing
* a wrapperElement.
*/
const Chip = ( {
text,
screenReaderText = '',
element = 'li',
className = '',
radius = 'small',
children = null,
...props
} ) => {
const Wrapper = element;
const wrapperClassName = classNames(
className,
'wc-block-components-chip',
'wc-block-components-chip--radius-' + radius
);
const showScreenReaderText = Boolean(
screenReaderText && screenReaderText !== text
);
return (
// @ts-ignore
<Wrapper className={ wrapperClassName } { ...props }>
<span
aria-hidden={ showScreenReaderText }
className="wc-block-components-chip__text"
>
{ text }
</span>
{ showScreenReaderText && (
<span className="screen-reader-text">{ screenReaderText }</span>
) }
{ children }
</Wrapper>
);
};
Chip.propTypes = {
text: PropTypes.node.isRequired,
screenReaderText: PropTypes.string,
element: PropTypes.elementType,
className: PropTypes.string,
radius: PropTypes.oneOf( [ 'none', 'small', 'medium', 'large' ] ),
};
export default Chip;

View File

@ -1,70 +1,2 @@
/** export { default as Chip } from './chip';
* External dependencies export { default as RemovableChip } from './removable-chip';
*/
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { __, sprintf } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import './style.scss';
/**
* Component used to render a "chip" -- a list item containing some text with
* an X button to remove/dismiss each chip.
*
* Each chip defaults to a list element but this can be customized by providing
* a wrapperElement.
*/
const Chip = ( {
text,
screenReaderText,
element = 'li',
className = '',
onRemove = () => {},
disabled = false,
radius = 'small',
} ) => {
const Wrapper = element;
const wrapperClassName = classNames(
className,
'wc-block-components-chip',
'wc-block-components-chip--radius-' + radius
);
return (
// @ts-ignore
<Wrapper className={ wrapperClassName }>
<span aria-hidden="true" className="wc-block-components-chip__text">
{ text }
</span>
<span className="screen-reader-text">
{ screenReaderText ? screenReaderText : text }
</span>
<button
className="wc-block-components-chip__remove"
onClick={ onRemove }
disabled={ disabled }
aria-label={ sprintf(
/* translators: %s chip text. */
__( 'Remove coupon "%s"', 'woo-gutenberg-products-block' ),
text
) }
>
</button>
</Wrapper>
);
};
Chip.propTypes = {
text: PropTypes.string.isRequired,
screenReaderText: PropTypes.string,
element: PropTypes.elementType,
className: PropTypes.string,
onRemove: PropTypes.func,
radius: PropTypes.oneOf( [ 'none', 'small', 'medium', 'large' ] ),
};
export default Chip;

View File

@ -0,0 +1,91 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { __, sprintf } from '@wordpress/i18n';
import { Icon, noAlt } from '@woocommerce/icons';
/**
* Internal dependencies
*/
import { Chip } from './index.js';
/**
* Component used to render a "chip" -- an item containing some text with
* an X button to remove/dismiss each chip.
*/
const RemovableChip = ( {
ariaLabel = '',
className = '',
disabled = false,
onRemove = () => void null,
removeOnAnyClick = false,
text,
screenReaderText = '',
...props
} ) => {
const RemoveElement = removeOnAnyClick ? 'span' : 'button';
if ( ! ariaLabel ) {
const ariaLabelText =
screenReaderText && typeof screenReaderText === 'string'
? screenReaderText
: text;
ariaLabel =
typeof ariaLabelText !== 'string'
? /* translators: Remove chip. */
__( 'Remove', 'woo-gutenberg-products-block' )
: sprintf(
/* translators: %s text of the chip to remove. */
__( 'Remove "%s"', 'woo-gutenberg-products-block' ),
ariaLabelText
);
}
const clickableElementProps = {
'aria-label': ariaLabel,
disabled,
onClick: onRemove,
onKeyDown: ( e ) => {
if ( e.key === 'Backspace' || e.key === 'Delete' ) {
onRemove();
}
},
};
const chipProps = removeOnAnyClick ? clickableElementProps : {};
const removeProps = removeOnAnyClick
? { 'aria-hidden': true }
: clickableElementProps;
return (
<Chip
{ ...props }
{ ...chipProps }
className={ classNames( className, 'is-removable' ) }
element={ removeOnAnyClick ? 'button' : props.element }
screenReaderText={ screenReaderText }
text={ text }
>
<RemoveElement
className="wc-block-components-chip__remove"
{ ...removeProps }
>
<Icon srcElement={ noAlt } size={ 16 } />
</RemoveElement>
</Chip>
);
};
RemovableChip.propTypes = {
text: PropTypes.node.isRequired,
ariaLabel: PropTypes.string,
className: PropTypes.string,
disabled: PropTypes.bool,
onRemove: PropTypes.func,
removeOnAnyClick: PropTypes.bool,
screenReaderText: PropTypes.string,
};
export default RemovableChip;

View File

@ -1,13 +1,23 @@
.wc-block-components-chip { .wc-block-components-chip {
display: inline-block; @include reset-typography();
background: $core-grey-light-500; align-items: center;
padding: 0.365em 0.5em; border: 0;
display: inline-flex;
padding: em($gap-smallest / 2) 0.5em em($gap-smallest);
margin: 0 0.365em 0.365em 0; margin: 0 0.365em 0.365em 0;
color: $core-grey-dark-800;
border-radius: 0; border-radius: 0;
line-height: 1em; line-height: 1;
max-width: 100%; max-width: 100%;
// Chip might be a button, so we need to override theme styles.
&,
&:hover,
&:focus,
&:active {
background: $core-grey-light-500;
color: $core-grey-dark-800;
}
&.wc-block-components-chip--radius-small { &.wc-block-components-chip--radius-small {
border-radius: 3px; border-radius: 3px;
} }
@ -15,30 +25,41 @@
border-radius: 0.433em; border-radius: 0.433em;
} }
&.wc-block-components-chip--radius-large { &.wc-block-components-chip--radius-large {
border-radius: 0.865em; border-radius: 2em;
padding: 0.365em 0.75em; padding-left: 0.75em;
padding-right: 0.75em;
} }
.wc-block-components-chip__text { .wc-block-components-chip__text {
flex-grow: 1;
}
&.is-removable {
padding-right: 0.5em; padding-right: 0.5em;
} }
&.is-removable .wc-block-components-chip__text {
padding-right: 0.25em;
}
.wc-block-components-chip__remove { .wc-block-components-chip__remove {
@include font-size(smaller); @include font-size(smaller);
background: transparent; background: transparent;
border: 0; border: 0;
appearance: none; appearance: none;
float: none; padding: 0;
vertical-align: middle;
line-height: 1.33em;
padding: 0.66em; // Should equate to ~8px; chip has ~6px padding, and font size difference/2 is 2px.
margin: -0.66em;
&:hover, > .dashicon {
&:focus { vertical-align: middle;
color: #d94f4f;
}
&:disabled {
color: $core-grey-dark-100;
cursor: not-allowed;
} }
} }
} }
button.wc-block-components-chip:hover > .wc-block-components-chip__remove,
button.wc-block-components-chip:focus > .wc-block-components-chip__remove,
.wc-block-components-chip__remove:hover,
.wc-block-components-chip__remove:focus {
fill: #d94f4f;
}
button.wc-block-components-chip:disabled > .wc-block-components-chip__remove,
.wc-block-components-chip__remove:disabled {
fill: $core-grey-dark-100;
cursor: not-allowed;
}

View File

@ -1,105 +1,312 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Chip should render children nodes 1`] = `
<li
className="wc-block-components-chip wc-block-components-chip--radius-small"
>
<span
aria-hidden={false}
className="wc-block-components-chip__text"
>
Test
</span>
Lorem Ipsum
</li>
`;
exports[`Chip should render defined radius 1`] = `
<li
className="wc-block-components-chip wc-block-components-chip--radius-large"
>
<span
aria-hidden={false}
className="wc-block-components-chip__text"
>
Test
</span>
</li>
`;
exports[`Chip should render nodes as the text 1`] = `
<li
className="wc-block-components-chip wc-block-components-chip--radius-small"
>
<span
aria-hidden={false}
className="wc-block-components-chip__text"
>
<h1>
Test
</h1>
</span>
</li>
`;
exports[`Chip should render screen reader text 1`] = `
<li
className="wc-block-components-chip wc-block-components-chip--radius-small"
>
<span
aria-hidden={true}
className="wc-block-components-chip__text"
>
Test
</span>
<span
className="screen-reader-text"
>
Test 2
</span>
</li>
`;
exports[`Chip should render text 1`] = `
<li
className="wc-block-components-chip wc-block-components-chip--radius-small"
>
<span
aria-hidden={false}
className="wc-block-components-chip__text"
>
Test
</span>
</li>
`;
exports[`Chip with custom wrapper should render a chip made up of a div instead of a li 1`] = ` exports[`Chip with custom wrapper should render a chip made up of a div instead of a li 1`] = `
<div <div
className="wc-block-components-chip wc-block-components-chip--radius-small" className="wc-block-components-chip wc-block-components-chip--radius-small"
> >
<span <span
aria-hidden="true" aria-hidden={false}
className="wc-block-components-chip__text" className="wc-block-components-chip__text"
> >
Test Test
</span> </span>
<span
className="screen-reader-text"
>
Test
</span>
<button
aria-label="Remove coupon \\"Test\\""
className="wc-block-components-chip__remove"
disabled={false}
onClick={[Function]}
>
</button>
</div> </div>
`; `;
exports[`Chip without custom wrapper should render defined radius 1`] = ` exports[`RemovableChip should render custom aria label 1`] = `
<li <li
className="wc-block-components-chip wc-block-components-chip--radius-large" className="is-removable wc-block-components-chip wc-block-components-chip--radius-small"
> >
<span <span
aria-hidden="true" aria-hidden={false}
className="wc-block-components-chip__text" className="wc-block-components-chip__text"
> >
Test <h1>
</span> Test
<span </h1>
className="screen-reader-text"
>
Test
</span> </span>
<button <button
aria-label="Remove coupon \\"Test\\"" aria-label="Aria test"
className="wc-block-components-chip__remove" className="wc-block-components-chip__remove"
disabled={false} disabled={false}
onClick={[Function]} onClick={[Function]}
onKeyDown={[Function]}
> >
<svg
aria-hidden="true"
className="dashicon dashicons-arrow-down-alt2"
focusable="false"
height={16}
role="img"
viewBox="0 0 20 20"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z"
/>
</svg>
</button> </button>
</li> </li>
`; `;
exports[`Chip without custom wrapper should render text and the remove button 1`] = ` exports[`RemovableChip should render default aria label if text is a node 1`] = `
<li <li
className="wc-block-components-chip wc-block-components-chip--radius-small" className="is-removable wc-block-components-chip wc-block-components-chip--radius-small"
> >
<span <span
aria-hidden="true" aria-hidden={true}
className="wc-block-components-chip__text" className="wc-block-components-chip__text"
> >
Test <h1>
Test
</h1>
</span> </span>
<span <span
className="screen-reader-text" className="screen-reader-text"
> >
Test Test 2
</span> </span>
<button <button
aria-label="Remove coupon \\"Test\\"" aria-label="Remove \\"Test 2\\""
className="wc-block-components-chip__remove" className="wc-block-components-chip__remove"
disabled={false} disabled={false}
onClick={[Function]} onClick={[Function]}
onKeyDown={[Function]}
> >
<svg
aria-hidden="true"
className="dashicon dashicons-arrow-down-alt2"
focusable="false"
height={16}
role="img"
viewBox="0 0 20 20"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z"
/>
</svg>
</button> </button>
</li> </li>
`; `;
exports[`Chip without custom wrapper should render with disabled remove button 1`] = ` exports[`RemovableChip should render screen reader text aria label 1`] = `
<li <li
className="wc-block-components-chip wc-block-components-chip--radius-small" className="is-removable wc-block-components-chip wc-block-components-chip--radius-small"
> >
<span <span
aria-hidden="true" aria-hidden={true}
className="wc-block-components-chip__text" className="wc-block-components-chip__text"
> >
Test Test
</span> </span>
<span <span
className="screen-reader-text" className="screen-reader-text"
>
Test 2
</span>
<button
aria-label="Remove \\"Test 2\\""
className="wc-block-components-chip__remove"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
>
<svg
aria-hidden="true"
className="dashicon dashicons-arrow-down-alt2"
focusable="false"
height={16}
role="img"
viewBox="0 0 20 20"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z"
/>
</svg>
</button>
</li>
`;
exports[`RemovableChip should render text and the remove button 1`] = `
<li
className="is-removable wc-block-components-chip wc-block-components-chip--radius-small"
>
<span
aria-hidden={false}
className="wc-block-components-chip__text"
> >
Test Test
</span> </span>
<button <button
aria-label="Remove coupon \\"Test\\"" aria-label="Remove \\"Test\\""
className="wc-block-components-chip__remove"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
>
<svg
aria-hidden="true"
className="dashicon dashicons-arrow-down-alt2"
focusable="false"
height={16}
role="img"
viewBox="0 0 20 20"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z"
/>
</svg>
</button>
</li>
`;
exports[`RemovableChip should render with disabled remove button 1`] = `
<li
className="is-removable wc-block-components-chip wc-block-components-chip--radius-small"
>
<span
aria-hidden={false}
className="wc-block-components-chip__text"
>
Test
</span>
<button
aria-label="Remove \\"Test\\""
className="wc-block-components-chip__remove" className="wc-block-components-chip__remove"
disabled={true} disabled={true}
onClick={[Function]} onClick={[Function]}
onKeyDown={[Function]}
> >
<svg
aria-hidden="true"
className="dashicon dashicons-arrow-down-alt2"
focusable="false"
height={16}
role="img"
viewBox="0 0 20 20"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z"
/>
</svg>
</button> </button>
</li> </li>
`; `;
exports[`RemovableChip with removeOnAnyClick should be a button when removeOnAnyClick is set to true 1`] = `
<button
aria-label="Remove \\"Test\\""
className="is-removable wc-block-components-chip wc-block-components-chip--radius-small"
disabled={false}
onClick={[Function]}
onKeyDown={[Function]}
>
<span
aria-hidden={false}
className="wc-block-components-chip__text"
>
Test
</span>
<span
aria-hidden={true}
className="wc-block-components-chip__remove"
>
<svg
aria-hidden="true"
className="dashicon dashicons-arrow-down-alt2"
focusable="false"
height={16}
role="img"
viewBox="0 0 20 20"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z"
/>
</svg>
</span>
</button>
`;

View File

@ -6,31 +6,45 @@ import TestRenderer from 'react-test-renderer';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import Chip from '../'; import { Chip, RemovableChip } from '..';
describe( 'Chip', () => { describe( 'Chip', () => {
describe( 'without custom wrapper', () => { test( 'should render text', () => {
test( 'should render text and the remove button', () => { const component = TestRenderer.create( <Chip text="Test" /> );
const component = TestRenderer.create( <Chip text="Test" /> );
expect( component.toJSON() ).toMatchSnapshot(); expect( component.toJSON() ).toMatchSnapshot();
} ); } );
test( 'should render defined radius', () => { test( 'should render nodes as the text', () => {
const component = TestRenderer.create( const component = TestRenderer.create(
<Chip text="Test" radius="large" /> <Chip text={ <h1>Test</h1> } />
); );
expect( component.toJSON() ).toMatchSnapshot(); expect( component.toJSON() ).toMatchSnapshot();
} ); } );
test( 'should render with disabled remove button', () => { test( 'should render defined radius', () => {
const component = TestRenderer.create( const component = TestRenderer.create(
<Chip text="Test" disabled={ true } /> <Chip text="Test" radius="large" />
); );
expect( component.toJSON() ).toMatchSnapshot(); expect( component.toJSON() ).toMatchSnapshot();
} ); } );
test( 'should render screen reader text', () => {
const component = TestRenderer.create(
<Chip text="Test" screenReaderText="Test 2" />
);
expect( component.toJSON() ).toMatchSnapshot();
} );
test( 'should render children nodes', () => {
const component = TestRenderer.create(
<Chip text="Test">Lorem Ipsum</Chip>
);
expect( component.toJSON() ).toMatchSnapshot();
} ); } );
describe( 'with custom wrapper', () => { describe( 'with custom wrapper', () => {
@ -43,3 +57,53 @@ describe( 'Chip', () => {
} ); } );
} ); } );
} ); } );
describe( 'RemovableChip', () => {
test( 'should render text and the remove button', () => {
const component = TestRenderer.create( <RemovableChip text="Test" /> );
expect( component.toJSON() ).toMatchSnapshot();
} );
test( 'should render with disabled remove button', () => {
const component = TestRenderer.create(
<RemovableChip text="Test" disabled={ true } />
);
expect( component.toJSON() ).toMatchSnapshot();
} );
test( 'should render custom aria label', () => {
const component = TestRenderer.create(
<RemovableChip text={ <h1>Test</h1> } ariaLabel="Aria test" />
);
expect( component.toJSON() ).toMatchSnapshot();
} );
test( 'should render default aria label if text is a node', () => {
const component = TestRenderer.create(
<RemovableChip text={ <h1>Test</h1> } screenReaderText="Test 2" />
);
expect( component.toJSON() ).toMatchSnapshot();
} );
test( 'should render screen reader text aria label', () => {
const component = TestRenderer.create(
<RemovableChip text="Test" screenReaderText="Test 2" />
);
expect( component.toJSON() ).toMatchSnapshot();
} );
describe( 'with removeOnAnyClick', () => {
test( 'should be a button when removeOnAnyClick is set to true', () => {
const component = TestRenderer.create(
<RemovableChip text="Test" removeOnAnyClick={ true } />
);
expect( component.toJSON() ).toMatchSnapshot();
} );
} );
} );

View File

@ -91,6 +91,8 @@ const DropdownSelector = ( {
className={ classNames( classes, { className={ classNames( classes, {
'is-multiple': multiple, 'is-multiple': multiple,
'is-single': ! multiple, 'is-single': ! multiple,
'has-checked': checked.length > 0,
'is-open': isOpen,
} ) } } ) }
> >
{ /* eslint-disable-next-line jsx-a11y/label-has-for */ } { /* eslint-disable-next-line jsx-a11y/label-has-for */ }

View File

@ -2,31 +2,23 @@
* External dependencies * External dependencies
*/ */
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { RemovableChip } from '@woocommerce/base-components/chip';
const DropdownSelectorSelectedChip = ( { onRemoveItem, option } ) => { const DropdownSelectorSelectedChip = ( { onRemoveItem, option } ) => {
return ( return (
<button <RemovableChip
className="wc-block-dropdown-selector__selected-chip wc-block-components-dropdown-selector__selected-chip" className="wc-block-dropdown-selector__selected-chip wc-block-components-dropdown-selector__selected-chip"
onClick={ () => { removeOnAnyClick={ true }
onRemove={ () => {
onRemoveItem( option.value ); onRemoveItem( option.value );
} } } }
onKeyDown={ ( e ) => { ariaLabel={ sprintf(
if ( e.key === 'Backspace' || e.key === 'Delete' ) {
onRemoveItem( option.value );
}
} }
aria-label={ sprintf(
__( 'Remove %s filter', 'woo-gutenberg-products-block' ), __( 'Remove %s filter', 'woo-gutenberg-products-block' ),
option.name option.name
) } ) }
> text={ option.label }
<span className="wc-block-dropdown-selector__selected-chip__label wc-block-components-dropdown-selector__selected-chip__label"> radius="large"
{ option.label } />
</span>
<span className="wc-block-dropdown-selector__selected-chip__remove wc-block-components-dropdown-selector__selected-chip__remove">
𝘅
</span>
</button>
); );
}; };

View File

@ -3,6 +3,7 @@
*/ */
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { useEffect, useRef } from '@wordpress/element'; import { useEffect, useRef } from '@wordpress/element';
import { Icon, noAlt } from '@woocommerce/icons';
const DropdownSelectorSelectedValue = ( { onClick, onRemoveItem, option } ) => { const DropdownSelectorSelectedValue = ( { onClick, onRemoveItem, option } ) => {
const labelRef = useRef( null ); const labelRef = useRef( null );
@ -47,7 +48,7 @@ const DropdownSelectorSelectedValue = ( { onClick, onRemoveItem, option } ) => {
option.name option.name
) } ) }
> >
𝘅 <Icon srcElement={ noAlt } size={ 16 } />
</button> </button>
</div> </div>
); );

View File

@ -1,3 +1,7 @@
// 18px is the minimum input field line-height and 14px is the font-size of
// the drop down selector elements.
$dropdown-selector-line-height: 18/14;
.wc-block-components-dropdown-selector { .wc-block-components-dropdown-selector {
max-width: 300px; max-width: 300px;
position: relative; position: relative;
@ -5,34 +9,37 @@
} }
.wc-block-components-dropdown-selector__input-wrapper { .wc-block-components-dropdown-selector__input-wrapper {
background: #fff;
border: 1px solid $input-border-gray;
color: $input-text-active;
align-items: center; align-items: center;
border: 1px solid #9f9f9f;
border-radius: 4px; border-radius: 4px;
cursor: text; cursor: text;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
padding: 2px; padding: 2px $gap-smaller;
.is-disabled & { .is-disabled & {
background-color: $core-grey-light-500; background-color: $core-grey-light-500;
} }
}
.wc-block-components-dropdown-selector__placeholder { .is-multiple.has-checked > & {
@include font-size(small); padding: 2px $gap-smallest;
height: 1.8em; }
margin: 0 $gap-smallest;
white-space: nowrap; .is-open > & {
border-radius: 4px 4px 0 0;
}
} }
.wc-block-components-dropdown-selector__input { .wc-block-components-dropdown-selector__input {
@include font-size(small); @include font-size(small);
height: 1.8em; line-height: $dropdown-selector-line-height;
margin: em($gap-small/4) 0;
min-width: 0; min-width: 0;
padding: em($gap-smallest * 0.75) 0 em($gap-smallest * 0.75);
.is-single & { .is-single & {
margin: 0 4px;
padding: 0;
width: 100%; width: 100%;
&:hover, &:hover,
@ -40,24 +47,23 @@
&:active { &:active {
outline: 0; outline: 0;
} }
}
&:not(:first-child):focus { .is-single.has-checked.is-open & {
margin-bottom: 1.5px; margin-bottom: 1.5px;
margin-top: 1.5px; margin-top: 1.5px;
} }
&:not(:first-child):not(:focus) { .is-single.has-checked:not(.is-open) & {
@include visually-hidden(); @include visually-hidden();
// Fixes an issue in Firefox that `flex: wrap` in the container was making // Fixes an issue in Firefox that `flex: wrap` in the container was making
// this element to still occupy one line. // this element to still occupy one line.
position: absolute; position: absolute;
}
} }
.is-multiple & { .is-multiple & {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
margin: 1.5px;
} }
} }
@ -76,7 +82,6 @@
.wc-block-components-dropdown-selector { .wc-block-components-dropdown-selector {
// Reset <button> styles // Reset <button> styles
.wc-block-components-dropdown-selector__selected-chip,
.wc-block-components-dropdown-selector__selected-value__label, .wc-block-components-dropdown-selector__selected-value__label,
.wc-block-components-dropdown-selector__selected-value__remove { .wc-block-components-dropdown-selector__selected-value__remove {
background-color: transparent; background-color: transparent;
@ -95,50 +100,39 @@
} }
.wc-block-components-dropdown-selector__selected-value { .wc-block-components-dropdown-selector__selected-value {
@include font-size(small);
align-items: center; align-items: center;
color: $core-grey-dark-600; color: $core-grey-dark-600;
display: inline-flex; display: inline-flex;
height: 1.8em; margin: em($gap-small/4) 0;
padding: 1.5px 1.5px 1.5px 4px; padding: em($gap-smallest * 0.75) 0 em($gap-smallest * 0.75);
width: 100%; width: 100%;
} }
.wc-block-components-dropdown-selector__selected-chip { .wc-block-components-dropdown-selector__selected-value__label {
align-items: center;
background-color: $core-grey-light-600;
border: 1px solid #9f9f9f;
border-radius: 4px;
color: $core-grey-dark-600;
display: inline-flex;
height: 1.8em;
margin: 1.5px;
padding: 0 0 0 4px;
white-space: nowrap;
&:hover,
&:focus,
&:active {
background-color: $core-grey-light-400;
border: 1px solid #9f9f9f;
color: $core-grey-dark-600;
}
}
.wc-block-components-dropdown-selector__selected-value__label,
.wc-block-components-dropdown-selector__selected-chip__label {
@include font-size(small);
flex-grow: 1; flex-grow: 1;
line-height: $dropdown-selector-line-height;
padding: 0; padding: 0;
text-align: left; text-align: left;
} }
.wc-block-components-dropdown-selector__selected-value__remove, .wc-block-components-dropdown-selector__selected-value__remove {
.wc-block-components-dropdown-selector__selected-chip__remove {
background-color: transparent; background-color: transparent;
border: 0; border: 0;
display: inline-block; display: inline-block;
line-height: 1; line-height: 1;
padding: 0 0.3em; padding: 0 0 0 0.3em;
> .dashicon {
display: block;
}
}
.wc-block-components-dropdown-selector__selected-chip {
@include font-size(small);
margin-top: em($gap-small/4);
margin-bottom: em($gap-small/4);
line-height: $dropdown-selector-line-height;
} }
} }

View File

@ -18,6 +18,7 @@ const ActiveAttributeFilters = ( {
attributeObject = {}, attributeObject = {},
slugs = [], slugs = [],
operator = 'in', operator = 'in',
displayStyle,
} ) => { } ) => {
const { results, isLoading } = useCollection( { const { results, isLoading } = useCollection( {
namespace: '/wc/store', namespace: '/wc/store',
@ -74,6 +75,7 @@ const ActiveAttributeFilters = ( {
); );
}, },
showLabel: false, showLabel: false,
displayStyle,
} ); } );
} ) } } ) }
</ul> </ul>

View File

@ -41,6 +41,7 @@ const ActiveFiltersBlock = ( {
setMinPrice( undefined ); setMinPrice( undefined );
setMaxPrice( undefined ); setMaxPrice( undefined );
}, },
displayStyle: blockAttributes.displayStyle,
} ); } );
}, [ minPrice, maxPrice, formatPriceRange ] ); }, [ minPrice, maxPrice, formatPriceRange ] );
@ -52,6 +53,7 @@ const ActiveFiltersBlock = ( {
return ( return (
<ActiveAttributeFilters <ActiveAttributeFilters
attributeObject={ attributeObject } attributeObject={ attributeObject }
displayStyle={ blockAttributes.displayStyle }
slugs={ attribute.slug } slugs={ attribute.slug }
key={ attribute.attribute } key={ attribute.attribute }
operator={ attribute.operator } operator={ attribute.operator }
@ -96,6 +98,7 @@ const ActiveFiltersBlock = ( {
'Small', 'Small',
'woo-gutenberg-products-block' 'woo-gutenberg-products-block'
), ),
displayStyle: blockAttributes.displayStyle,
} ) } } ) }
{ renderRemovableListItem( { { renderRemovableListItem( {
type: __( type: __(
@ -106,6 +109,7 @@ const ActiveFiltersBlock = ( {
'Blue', 'Blue',
'woo-gutenberg-products-block' 'woo-gutenberg-products-block'
), ),
displayStyle: blockAttributes.displayStyle,
} ) } } ) }
</Fragment> </Fragment>
) : ( ) : (

View File

@ -5,16 +5,20 @@
.wc-block-active-filters__clear-all { .wc-block-active-filters__clear-all {
@include font-size(regular); @include font-size(regular);
float: right; float: right;
background: transparent none;
border: none; border: none;
padding: 0; padding: 0;
text-decoration: underline; text-decoration: underline;
cursor: pointer; cursor: pointer;
&:hover { &,
background: transparent none; &:hover,
&:focus,
&:active {
background: transparent;
color: inherit;
} }
} }
.wc-block-active-filters__list { .wc-block-active-filters__list {
margin: 0 0 $gap-smallest; margin: 0 0 $gap-smallest;
list-style: none outside; list-style: none outside;
@ -32,95 +36,72 @@
list-style: none outside; list-style: none outside;
} }
.wc-block-active-filters__list-item-type {
@include font-size(smaller);
text-transform: uppercase;
letter-spacing: 0.1em;
margin: $gap 0 0;
display: block;
}
.wc-block-active-filters__list-item-operator {
font-weight: normal;
font-style: italic;
}
.wc-block-active-filters__list-item-name {
font-weight: bold;
display: block;
position: relative;
padding: 0 16px 0 0;
}
&:first-child { &:first-child {
.wc-block-active-filters__list-item-type { .wc-block-active-filters__list-item-type {
margin: 0; margin: 0;
} }
} }
} }
}
button { .wc-block-active-filters__list-item-type {
background: transparent; @include font-size(smaller);
border: 0; text-transform: uppercase;
appearance: none; letter-spacing: 0.1em;
height: 0; margin: $gap 0 0;
padding: 16px 0 0 0; display: block;
}
.wc-block-active-filters__list-item-operator {
font-weight: normal;
font-style: italic;
}
.wc-block-active-filters__list-item-name {
font-weight: bold;
display: block;
position: relative;
padding: 0 16px 0 0;
}
.wc-block-active-filters__list-item-remove {
background: transparent;
border: 0;
appearance: none;
height: 0;
padding: 16px 0 0 0;
width: 16px;
overflow: hidden;
position: absolute;
right: 0;
top: 50%;
margin: -8px 0 0 0;
&::before {
width: 16px; width: 16px;
overflow: hidden; height: 16px;
background: transparent url("data:image/svg+xml,%3Csvg viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='9' cy='9' r='9' fill='%2324292d'/%3E%3Crect x='4.5' y='6.8866' width='3.375' height='9.9466' transform='rotate(-45 4.5 6.8866)' fill='white'/%3E%3Crect x='11.5334' y='4.5' width='3.375' height='9.9466' transform='rotate(45 11.5334 4.5)' fill='white'/%3E%3C/svg%3E%0A") center center no-repeat; /* stylelint-disable-line */
display: block;
content: "";
position: absolute; position: absolute;
right: 0; top: 0;
top: 50%; }
margin: -8px 0 0 0; }
&::before { .wc-block-active-filters__list--chips {
width: 16px; ul,
height: 16px; li {
background: transparent url("data:image/svg+xml,%3Csvg viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='9' cy='9' r='9' fill='%2324292d'/%3E%3Crect x='4.5' y='6.8866' width='3.375' height='9.9466' transform='rotate(-45 4.5 6.8866)' fill='white'/%3E%3Crect x='11.5334' y='4.5' width='3.375' height='9.9466' transform='rotate(45 11.5334 4.5)' fill='white'/%3E%3C/svg%3E%0A") center center no-repeat; /* stylelint-disable-line */ display: inline;
display: block;
content: "";
position: absolute;
top: 0;
}
} }
&.wc-block-active-filters__list--chips { .wc-block-active-filters__list-item-type {
li { display: none;
display: inline-block; }
.wc-block-active-filters__list-item-type { .wc-block-components-chip {
display: none; @include font-size(small);
} margin-top: em($gap-small/4);
.wc-block-active-filters__list-item-name { margin-bottom: em($gap-small/4);
padding: 0;
}
}
li.wc-block-active-filters__list-item {
background: #c4c4c4;
border-radius: 4px;
padding: 4px 8px;
margin: 0 6px 6px 0;
color: #24292d;
}
button {
float: none;
vertical-align: middle;
margin: -2px 0 0 9px;
height: 0;
padding: 12px 0 0 0;
width: 12px;
overflow: hidden;
position: relative;
&::before {
width: 12px;
height: 12px;
background: transparent url("data:image/svg+xml,%3Csvg width='12' viewBox='0 0 9 9' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='7.03329' width='2' height='9.9466' transform='rotate(45 7.03329 0)' fill='%2324292d'/%3E%3Crect x='8.4476' y='7.07104' width='2' height='9.9466' transform='rotate(135 8.4476 7.07104)' fill='%2324292d'/%3E%3C/svg%3E%0A") center center no-repeat; /* stylelint-disable-line */
display: block;
content: "";
position: absolute;
top: 0;
}
}
} }
} }
} }

View File

@ -3,7 +3,7 @@
*/ */
import { __, sprintf } from '@wordpress/i18n'; import { __, sprintf } from '@wordpress/i18n';
import { formatPrice } from '@woocommerce/base-utils'; import { formatPrice } from '@woocommerce/base-utils';
import { Fragment } from '@wordpress/element'; import { RemovableChip } from '@woocommerce/base-components/chip';
/** /**
* Format a min/max price range to display. * Format a min/max price range to display.
@ -44,6 +44,7 @@ export const formatPriceRange = ( minPrice, maxPrice ) => {
* @param {string} listItem.name Name string. * @param {string} listItem.name Name string.
* @param {string} listItem.prefix Prefix shown before item name. * @param {string} listItem.prefix Prefix shown before item name.
* @param {Function} listItem.removeCallback Callback to remove item. * @param {Function} listItem.removeCallback Callback to remove item.
* @param {string} listItem.displayStyle Whether it's a list or chips.
* @param {boolean} [listItem.showLabel=true] Should the label be shown for * @param {boolean} [listItem.showLabel=true] Should the label be shown for
* this item? * this item?
*/ */
@ -53,7 +54,23 @@ export const renderRemovableListItem = ( {
prefix, prefix,
removeCallback = () => {}, removeCallback = () => {},
showLabel = true, showLabel = true,
displayStyle,
} ) => { } ) => {
const prefixedName = prefix ? (
<>
{ prefix }
&nbsp;
{ name }
</>
) : (
name
);
const removeText = sprintf(
/* translators: %s attribute value used in the filter. For example: yellow, green, small, large. */
__( 'Remove %s filter', 'woo-gutenberg-products-block' ),
name
);
return ( return (
<li <li
className="wc-block-active-filters__list-item" className="wc-block-active-filters__list-item"
@ -64,27 +81,25 @@ export const renderRemovableListItem = ( {
{ type + ': ' } { type + ': ' }
</span> </span>
) } ) }
<span className="wc-block-active-filters__list-item-name"> { displayStyle === 'chips' ? (
{ prefix ? ( <RemovableChip
<Fragment> element="span"
{ prefix } text={ prefixedName }
&nbsp; onRemove={ removeCallback }
{ name } radius="large"
</Fragment> ariaLabel={ removeText }
) : ( />
name ) : (
) } <span className="wc-block-active-filters__list-item-name">
<button onClick={ removeCallback }> { prefixedName }
{ sprintf( <button
/* translators: %s attribute value used in the filter. For example: yellow, green, small, large. */ className="wc-block-active-filters__list-item-remove"
__( onClick={ removeCallback }
'Remove %s filter', >
'woo-gutenberg-products-block' { removeText }
), </button>
name </span>
) } ) }
</button>
</span>
</li> </li>
); );
}; };