All Products & filters accessibility improvements (https://github.com/woocommerce/woocommerce-blocks/pull/1656)

* Add aria-label to All Products ratings

* Add specific screen reader text to some buttons

* Increase All Products regular price color constrast

* Remove invalid CSS declaration

* Make styleint-disable comment more specific

* Attributes Filter: make input non-focusable if we display the 'change filter' button

* Improve translator documentation

* Hide price slider from screen readers if price inputs are enabled

* Linting fixes

* Price slider: make it non-focusable if input fields are displayed

* All Products: announce how many products were found

* All Products: announce when a filter is removed

* Revert "All Products: announce when a filter is removed"

This reverts commit 2c861bf1b988155313ad44bafbcaf3f4f1549296.

* Pagination component: improve screen reader texts

* Filter submit button: improve screen reader texts

* Remove unnecessary text

* Improve comment

* Use %d for numeric values

* Add label and screenReaderLabel props to FilterSubmitButton component
This commit is contained in:
Albert Juhé Lluveras 2020-01-30 11:04:39 +01:00 committed by GitHub
parent aa4bc302a5
commit c8f297a700
12 changed files with 126 additions and 30 deletions

View File

@ -18,6 +18,11 @@ const ProductRating = ( { className, product } ) => {
width: ( rating / 5 ) * 100 + '%',
};
const ratingText = sprintf(
__( 'Rated %d out of 5', 'woo-gutenberg-products-block' ),
rating
);
return (
<div
className={ classnames(
@ -28,16 +33,9 @@ const ProductRating = ( { className, product } ) => {
<div
className={ `${ layoutStyleClassPrefix }__product-rating__stars` }
role="img"
aria-label={ ratingText }
>
<span style={ starStyle }>
{ sprintf(
__(
'Rated %d out of 5',
'woo-gutenberg-products-block'
),
rating
) }
</span>
<span style={ starStyle }>{ ratingText }</span>
</div>
</div>
);

View File

@ -4,6 +4,7 @@
import { __ } from '@wordpress/i18n';
import classnames from 'classnames';
import { useProductLayoutContext } from '@woocommerce/base-context/product-layout-context';
import Label from '@woocommerce/base-components/label';
const ProductSaleBadge = ( { className, product, align } ) => {
const { layoutStyleClassPrefix } = useProductLayoutContext();
@ -21,7 +22,13 @@ const ProductSaleBadge = ( { className, product, align } ) => {
`${ layoutStyleClassPrefix }__product-onsale`
) }
>
{ __( 'Sale', 'woo-gutenberg-products-block' ) }
<Label
label={ __( 'Sale', 'woo-gutenberg-products-block' ) }
screenReaderLabel={ __(
'Product on sale',
'woo-gutenberg-products-block'
) }
/>
</div>
);
}

View File

@ -145,6 +145,13 @@ const DropdownSelector = ( {
attributeLabel
)
}
tabIndex={
// When it's a single selector and there is one element selected,
// we make the input non-focusable with the keyboard because it's
// visually hidden. The input is still rendered, though, because it
// must be possible to focus it when pressing the select value chip.
! multiple && checked.length > 0 ? '-1' : '0'
}
value={ inputValue }
/>
</DropdownSelectorInputWrapper>

View File

@ -6,6 +6,7 @@ const DropdownSelectorInput = ( {
onFocus,
onRemoveItem,
placeholder,
tabIndex,
value,
} ) => {
return (
@ -25,6 +26,7 @@ const DropdownSelectorInput = ( {
}
},
placeholder,
tabIndex,
} ) }
/>
);

View File

@ -21,6 +21,7 @@ const DropdownSelectorSelectedValue = ( { onClick, onRemoveItem, option } ) => {
onClick( option.value );
} }
aria-label={ sprintf(
/* translators: %s attribute value used in the filter. For example: yellow, green, small, large. */
__(
'Replace current %s filter',
'woo-gutenberg-products-block'
@ -41,6 +42,7 @@ const DropdownSelectorSelectedValue = ( { onClick, onRemoveItem, option } ) => {
}
} }
aria-label={ sprintf(
/* translators: %s attribute value used in the filter. For example: yellow, green, small, large. */
__( 'Remove %s filter', 'woo-gutenberg-products-block' ),
option.name
) }

View File

@ -4,13 +4,21 @@
import { __ } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Label from '@woocommerce/base-components/label';
/**
* Internal dependencies
*/
import './style.scss';
const FilterSubmitButton = ( { className, disabled, onClick } ) => {
const FilterSubmitButton = ( {
className,
disabled,
// translators: Submit button text for filters.
label = __( 'Go', 'woo-gutenberg-products-block' ),
onClick,
screenReaderLabel = __( 'Apply filter', 'woo-gutenberg-products-block' ),
} ) => {
return (
<button
type="submit"
@ -21,8 +29,7 @@ const FilterSubmitButton = ( { className, disabled, onClick } ) => {
disabled={ disabled }
onClick={ onClick }
>
{ // translators: Submit button text for filters.
__( 'Go', 'woo-gutenberg-products-block' ) }
<Label label={ label } screenReaderLabel={ screenReaderLabel } />
</button>
);
};
@ -37,6 +44,8 @@ FilterSubmitButton.propTypes = {
* On click callback.
*/
onClick: PropTypes.func.isRequired,
label: PropTypes.string,
screenReaderLabel: PropTypes.string,
};
FilterSubmitButton.defaultProps = {

View File

@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { __, sprintf } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Label from '@woocommerce/base-components/label';
@ -83,7 +83,14 @@ const Pagination = ( {
onClick={ () => onPageChange( 1 ) }
disabled={ currentPage === 1 }
>
1
<Label
label={ 1 }
screenReaderLabel={ sprintf(
/* translators: %d is the page number (1, 2, 3...). */
__( 'Page %d', 'woo-gutenberg-products-block' ),
1
) }
/>
</button>
) }
{ showFirstPageEllipsis && (
@ -109,7 +116,14 @@ const Pagination = ( {
}
disabled={ currentPage === page }
>
{ page }
<Label
label={ page }
screenReaderLabel={ sprintf(
/* translators: %d is the page number (1, 2, 3...). */
__( 'Page %d', 'woo-gutenberg-products-block' ),
page
) }
/>
</button>
);
} ) }
@ -130,7 +144,14 @@ const Pagination = ( {
onClick={ () => onPageChange( totalPages ) }
disabled={ currentPage === totalPages }
>
{ totalPages }
<Label
label={ totalPages }
screenReaderLabel={ sprintf(
/* translators: %d is the page number (1, 2, 3...). */
__( 'Page %d', 'woo-gutenberg-products-block' ),
totalPages
) }
/>
</button>
) }
{ displayNextAndPreviousArrows && (

View File

@ -241,7 +241,7 @@ const PriceSlider = ( {
onFocus={ findClosestRange }
>
{ hasValidConstraints && (
<Fragment>
<div aria-hidden={ showInputFields }>
<div
className="wc-block-price-filter__range-input-progress"
style={ progressStyles }
@ -264,6 +264,7 @@ const PriceSlider = ( {
max={ maxConstraint }
ref={ minRange }
disabled={ isLoading }
tabIndex={ showInputFields ? '-1' : '0' }
/>
<input
type="range"
@ -283,8 +284,9 @@ const PriceSlider = ( {
max={ maxConstraint }
ref={ maxRange }
disabled={ isLoading }
tabIndex={ showInputFields ? '-1' : '0' }
/>
</Fragment>
</div>
) }
</div>
<div className="wc-block-price-filter__controls">
@ -333,8 +335,8 @@ const PriceSlider = ( {
Number.isFinite( minPrice ) &&
Number.isFinite( maxPrice ) && (
<div className="wc-block-price-filter__range-text">
{ __( 'Price', 'woo-gutenberg-products-block' ) }:
&nbsp;
{ __( 'Price', 'woo-gutenberg-products-block' ) }
: &nbsp;
<FormattedMonetaryAmount
currency={ currency }
displayType="text"
@ -353,6 +355,10 @@ const PriceSlider = ( {
className="wc-block-price-filter__button"
disabled={ isLoading || ! hasValidConstraints }
onClick={ onSubmit }
screenReaderLabel={ __(
'Apply price filter',
'woo-gutenberg-products-block'
) }
/>
) }
</div>

View File

@ -1,6 +1,7 @@
/**
* External dependencies
*/
import { __, _n, sprintf } from '@wordpress/i18n';
import { isEqual } from 'lodash';
import PropTypes from 'prop-types';
import classnames from 'classnames';
@ -16,6 +17,7 @@ import {
} from '@woocommerce/base-hooks';
import withScrollToTop from '@woocommerce/base-hocs/with-scroll-to-top';
import { useProductLayoutContext } from '@woocommerce/base-context/product-layout-context';
import { speak } from '@wordpress/a11y';
/**
* Internal dependencies
@ -70,6 +72,29 @@ const extractPaginationAndSortAttributes = ( query ) => {
return totalQuery;
};
const announceLoadingCompletion = ( totalProducts ) => {
if ( ! Number.isFinite( totalProducts ) ) {
return;
}
if ( totalProducts === 0 ) {
speak( __( 'No products found', 'woo-gutenberg-products-block' ) );
} else {
speak(
sprintf(
// translators: %s is an integer higher than 0 (1, 2, 3...)
_n(
'%d product found',
'%d products found',
totalProducts,
'woo-gutenberg-products-block'
),
totalProducts
)
);
}
};
const ProductList = ( {
attributes,
currentPage,
@ -119,6 +144,11 @@ const ProductList = ( {
// reset pagination to the first page.
if ( ! isPreviousTotalQueryEqual ) {
onPageChange( 1 );
// Make sure there was a previous query, so we don't announce it on page load.
if ( previousQueryTotals ) {
announceLoadingCompletion( totalProducts );
}
}
}, [ queryState ] );

View File

@ -108,7 +108,7 @@
.wc-block-grid__product-price__regular {
font-size: 0.8em;
line-height: 1;
color: #aaa;
color: #555;
margin-top: -0.25em;
display: block;
}
@ -181,9 +181,9 @@
height: 1.618em;
line-height: 1.618;
font-size: 1em;
font-family: star; /* stylelint-disable-line */
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
font-family: star;
font-weight: 400;
display: -block;
margin: 0 auto;
text-align: left;

View File

@ -6,6 +6,7 @@ import { useQueryStateByKey } from '@woocommerce/base-hooks';
import { useMemo, Fragment } from '@wordpress/element';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import Label from '@woocommerce/base-components/label';
/**
* Internal dependencies
@ -37,8 +38,8 @@ const ActiveFiltersBlock = ( {
__( 'Price', 'woo-gutenberg-products-block' ),
formatPriceRange( minPrice, maxPrice ),
() => {
setMinPrice( null );
setMaxPrice( null );
setMinPrice( undefined );
setMaxPrice( undefined );
}
);
}, [ minPrice, maxPrice, formatPriceRange ] );
@ -104,12 +105,21 @@ const ActiveFiltersBlock = ( {
<button
className="wc-block-active-filters__clear-all"
onClick={ () => {
setMinPrice( null );
setMaxPrice( null );
setMinPrice( undefined );
setMaxPrice( undefined );
setProductAttributes( [] );
} }
>
{ __( 'Clear All', 'woo-gutenberg-products-block' ) }
<Label
label={ __(
'Clear All',
'woo-gutenberg-products-block'
) }
screenReaderLabel={ __(
'Clear All Filters',
'woo-gutenberg-products-block'
) }
/>
</button>
</div>
</Fragment>

View File

@ -59,7 +59,11 @@ export const renderRemovableListItem = (
{ name }
</strong>
<button onClick={ removeCallback }>
{ __( 'Remove', 'woo-gutenberg-products-block' ) }
{ sprintf(
/* translators: %s attribute value used in the filter. For example: yellow, green, small, large. */
__( 'Remove %s filter', 'woo-gutenberg-products-block' ),
name
) }
</button>
</li>
);