Skip to content

Commit

Permalink
STCOM-1192 Group checkboxes in FilterGroups (a11y) (#2226)
Browse files Browse the repository at this point in the history
* accept full label id from outside of the component via headerProps

* allow for elements in label proptype. Derive string from within FormatMessage, if supplied.

* Get string from possible FormattedMessage in group label for Accordion id of FilterGroup

* log changes
  • Loading branch information
JohnC-80 authored Feb 28, 2024
1 parent 7d979e1 commit 85ebbbb
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* Apply highest z-index to focused `<Accordion>` within `AccordionSet`. Refs STCOM-1257.
* Upgrade storybook to v7. Refs STCOM-1176.
* Fix bug with MCL not re-enabling paging buttons after more data was loaded. Refs STCOM-1262.
* Accessible grouping for filter group checkboxes via `role="group"`. Refs STCOM-1192.

## [12.0.0](https://github.com/folio-org/stripes-components/tree/v12.0.0) (2023-10-11)
[Full Changelog](https://github.com/folio-org/stripes-components/compare/v11.0.0...v12.0.0)
Expand Down
6 changes: 3 additions & 3 deletions lib/Accordion/Accordion.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const Accordion = (props) => {
}).current;
const contentId = useRef(contentIdProp || uniqueId('accordion')).current;
const trackingId = useRef(id || uniqueId('acc')).current;
const labelId = useRef(`accordion-toggle-button-${trackingId}`).current;
const labelId = useRef(headerProps?.labelId || `accordion-toggle-button-${trackingId}`).current;


const getRef = useRef(() => toggle.current).current;
Expand Down Expand Up @@ -146,7 +146,7 @@ const Accordion = (props) => {
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps

const accordionHeaderProps = Object.assign({}, omitProps(props, ['contentHeight']), {
const accordionHeaderProps = Object.assign({}, omitProps(props, ['contentHeight', 'headerProps']), {
contentId,
toggleRef: (ref) => { toggle.current = ref; },
open: isOpen,
Expand Down Expand Up @@ -180,7 +180,7 @@ const Accordion = (props) => {
className={getContentClass(isOpen)}
ref={setContentRef}
id={contentId}
aria-labelledby={labelId}
aria-labelledby={accordionHeaderProps.labelId}
style={contentHeight ? { height: contentHeight } : null}
data-test-accordion-wrapper
>
Expand Down
26 changes: 14 additions & 12 deletions lib/FilterControlGroup/FilterControlGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@ const propTypes = {
style: PropTypes.object,
};

const FilterControlGroup = props => (
<ul
data-test-filter-control-group
style={props.style}
className={css.filterList}
aria-label={props.label}
>
{React.Children.map(props.children, child => (
<li key={child.id} className={css.listItem}>{child}</li>
))}
</ul>
);
const FilterControlGroup = props => {
return (
<ul
data-test-filter-control-group
style={props.style}
className={css.filterList}
aria-label={props.label}
>
{React.Children.map(props.children, child => (
<li key={child.id} className={css.listItem}>{child}</li>
))}
</ul>
);
}

FilterControlGroup.propTypes = propTypes;

Expand Down
56 changes: 35 additions & 21 deletions lib/FilterGroups/FilterGroups.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import kebabCase from 'lodash/kebabCase';
import React from 'react';
import React, { isValidElement } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import FilterControlGroup from '../FilterControlGroup';
import css from './FilterGroups.css';

Expand All @@ -18,6 +19,14 @@ export const FILTER_SEPARATOR = ',';
// a constant used to split filter groups
export const FILTER_GROUP_SEPARATOR = '.';

const getStringFromMessage = (label, formatMessage) => {
if (isValidElement(label) && label?.props?.id) {
return formatMessage({ id: label.props.id })
}
return label;
}


// private
const FilterCheckbox = (props) => {
const { groupName, name, displayName, checked, onChangeFilter: ocf, disabled } = props;
Expand Down Expand Up @@ -49,32 +58,35 @@ FilterCheckbox.propTypes = {
// private
const FilterGroup = (props) => {
const { label, groupName, names, filters, onChangeFilter: ocf, disabled } = props;

const { formatMessage } = useIntl();
const stringLabel = getStringFromMessage(label, formatMessage);
return (
<FilterControlGroup
data-test-filter-group
label={label}
disabled={disabled}
>
{names.map((name, index) => (
<FilterCheckbox
key={`${name}-${index}`}
groupName={groupName}
name={name.value}
displayName={name.displayName}
onChangeFilter={ocf}
checked={!!filters[`${groupName}.${name.value}`]}
disabled={disabled}
/>))}
</FilterControlGroup>
<div role="group" aria-label={stringLabel}>
<FilterControlGroup
data-test-filter-group
label={stringLabel}
disabled={disabled}
>
{names.map((name, index) => (
<FilterCheckbox
key={`${name}-${index}`}
groupName={groupName}
name={name.value}
displayName={name.displayName}
onChangeFilter={ocf}
checked={!!filters[`${groupName}.${name.value}`]}
disabled={disabled}
/>))}
</FilterControlGroup>
</div>
);
};

FilterGroup.propTypes = {
disabled: PropTypes.bool,
filters: PropTypes.object.isRequired,
groupName: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
names: PropTypes.arrayOf(PropTypes.object).isRequired,
onChangeFilter: PropTypes.func.isRequired,
};
Expand Down Expand Up @@ -260,6 +272,7 @@ export function handleClearAllFilters() {
const FilterGroups = (props) => {
const { config, filters, onChangeFilter: ocf, onClearFilter } = props;
const disableNames = props.disableNames || {};
const { formatMessage } = useIntl();

const filterGroupNames = (group) => {
const names = [];
Expand All @@ -283,7 +296,8 @@ const FilterGroups = (props) => {
{config.map((group, index) => (
<Accordion
label={group.label}
id={`${group.label}-${index}`}
id={`${getStringFromMessage(group.label, formatMessage)}-${index}`}
headerProps={{ labelId: `${group.name}-${index}` }}
name={group.name}
key={`acc-${group.name}-${index}`}
separator={false}
Expand Down Expand Up @@ -314,7 +328,7 @@ FilterGroups.propTypes = {
config: PropTypes.arrayOf(
PropTypes.shape({
cql: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
name: PropTypes.string.isRequired,
values: PropTypes.arrayOf(
PropTypes.oneOfType([
Expand Down

0 comments on commit 85ebbbb

Please sign in to comment.