Skip to content

Commit

Permalink
fix: selectable box set props forwarding (#2461)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: aria-label is required when not using aria-labelledby
in the SelectableBoxSet component. An associated label for the component is a WCAG requirement.

Co-authored-by: Maxwell Frank <mfrank@2u.com>
  • Loading branch information
MaxFrank13 and MaxFrank13 authored Aug 15, 2023
1 parent d9ccbbc commit 1d6f95e
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 2 deletions.
51 changes: 51 additions & 0 deletions src/SelectableBox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ As ``Checkbox``
onChange={handleChange}
name="cheeses"
columns={isExtraSmall ? 1 : 2}
ariaLabel="cheese selection"
>
<SelectableBox value="swiss" type={type} aria-label="swiss checkbox">
<div>
Expand Down Expand Up @@ -83,6 +84,7 @@ As ``Checkbox``
onChange={handleChange}
name="colors"
columns={isExtraSmall ? 1 : 3}
ariaLabel="color selection"
>
<SelectableBox value="red" type={type} aria-label="red checkbox">
<div>
Expand Down Expand Up @@ -144,6 +146,7 @@ As ``Checkbox`` with ``isIndeterminate``
onChange={handleChange}
name="cheeses"
columns={isExtraSmall ? 1 : 3}
ariaLabel="cheese selection"
>
<SelectableBox value="swiss" type={type} aria-label="swiss checkbox">
<div>
Expand All @@ -162,3 +165,51 @@ As ``Checkbox`` with ``isIndeterminate``
);
}
```

As ``Checkbox`` with ``ariaLabelledby``

```jsx live
() => {
const type = 'checkbox';
const allCheeseOptions = ['swiss', 'cheddar', 'pepperjack'];
const [checkedCheeses, { add, remove, set, clear }] = useCheckboxSetValues(['swiss']);

const handleChange = e => {
e.target.checked ? add(e.target.value) : remove(e.target.value);
};

const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth });

return (
<div className="bg-light-200 p-3">
<h3 id="cheese selection" className="mb-4">
Select your favorite cheese
</h3>
<SelectableBox.Set
value={checkedCheeses}
type={type}
onChange={handleChange}
name="cheeses"
columns={isExtraSmall ? 1 : 3}
ariaLabelledby="cheese selection"
>
<SelectableBox value="swiss" inputHidden={false} type={type} aria-label="swiss checkbox">
<h3>
Swiss
</h3>
</SelectableBox>
<SelectableBox value="cheddar" inputHidden={false} type={type} aria-label="cheddar checkbox">
<h3>
Cheddar
</h3>
</SelectableBox>
<SelectableBox value="pepperjack" inputHidden={false} type={type} aria-label="pepperjack checkbox">
<h3>
Pepperjack
</h3>
</SelectableBox>
</SelectableBox.Set>
</div>
);
}
```
20 changes: 20 additions & 0 deletions src/SelectableBox/SelectableBoxSet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { getInputType } from './utils';
import { requiredWhenNot } from '../utils/propTypes';

const INPUT_TYPES = [
'radio',
Expand All @@ -19,6 +20,9 @@ const SelectableBoxSet = React.forwardRef(({
type,
columns,
className,
ariaLabel,
ariaLabelledby,
...props
}, ref) => {
const inputType = getInputType('SelectableBoxSet', type);

Expand All @@ -35,6 +39,9 @@ const SelectableBoxSet = React.forwardRef(({
`pgn__selectable_box-set--${columns || DEFAULT_COLUMNS_NUMBER}`,
className,
),
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledby,
...props,
},
children,
);
Expand Down Expand Up @@ -62,6 +69,17 @@ SelectableBoxSet.propTypes = {
columns: PropTypes.number,
/** A class that is be appended to the base element. */
className: PropTypes.string,
/**
* The ID of the label for the `SelectableBoxSet`.
*
* An accessible label must be provided to the `SelectableBoxSet`.
*/
ariaLabelledby: PropTypes.string,
/**
* A label for the `SelectableBoxSet`.
*
* If not using `ariaLabelledby`, then `ariaLabel` must be provided */
ariaLabel: requiredWhenNot(PropTypes.string, 'ariaLabelledby'),
};

SelectableBoxSet.defaultProps = {
Expand All @@ -72,6 +90,8 @@ SelectableBoxSet.defaultProps = {
type: 'radio',
columns: DEFAULT_COLUMNS_NUMBER,
className: undefined,
ariaLabelledby: undefined,
ariaLabel: undefined,
};

export default SelectableBoxSet;
28 changes: 26 additions & 2 deletions src/SelectableBox/tests/SelectableBoxSet.test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { mount } from 'enzyme';
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

import { Form } from '../..';
import SelectableBox from '..';
Expand All @@ -11,9 +13,11 @@ const checkboxText = (text) => `SelectableCheckbox${text}`;
const radioType = 'radio';
const radioText = (text) => `SelectableRadio${text}`;

const ariaLabel = 'test-default-label';

function SelectableCheckboxSet(props) {
return (
<SelectableBox.Set name={radioType} type={checkboxType} {...props}>
<SelectableBox.Set name={radioType} type={checkboxType} ariaLabel={ariaLabel} {...props}>
<SelectableBox value={1} type={checkboxType}>{checkboxText(1)}</SelectableBox>
<SelectableBox value={2} type={checkboxType}>{checkboxText(2)}</SelectableBox>
<SelectableBox value={3} type={checkboxType}>{checkboxText(3)}</SelectableBox>
Expand All @@ -23,7 +27,7 @@ function SelectableCheckboxSet(props) {

function SelectableRadioSet(props) {
return (
<SelectableBox.Set name={radioType} type={radioType} {...props}>
<SelectableBox.Set name={radioType} type={radioType} ariaLabel={ariaLabel} {...props}>
<SelectableBox value={1} type={radioType}>{radioText(1)}</SelectableBox>
<SelectableBox value={2} type={radioType}>{radioText(2)}</SelectableBox>
<SelectableBox value={3} type={radioType}>{radioText(3)}</SelectableBox>
Expand All @@ -37,6 +41,10 @@ describe('<SelectableBox.Set />', () => {
const tree = renderer.create((<SelectableRadioSet name="testName" />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('forwards props', () => {
render((<SelectableRadioSet name="testName" data-testid="test-radio-set-name" />));
expect(screen.getByTestId('test-radio-set-name')).toBeInTheDocument();
});
it('correct render when type prop is changed', () => {
const setWrapper = mount(<SelectableRadioSet name="set" />);
expect(setWrapper.find(Form.RadioSet).length).toBeGreaterThan(0);
Expand Down Expand Up @@ -84,5 +92,21 @@ describe('<SelectableBox.Set />', () => {
const selectableBoxSet = wrapper.find(Form.RadioSet);
expect(selectableBoxSet.hasClass(`pgn__selectable_box-set--${columns}`)).toBe(true);
});
it('renders with an aria-label attribute', () => {
render((<SelectableRadioSet name="testName" ariaLabel="test-radio-set-label" />));
expect(screen.getByLabelText('test-radio-set-label')).toBeInTheDocument();
});
it('renders with an aria-labelledby attribute', () => {
render((
<>
<h2 id="test-radio-set-label">Radio Set Label text</h2>
<SelectableRadioSet
name="testName"
ariaLabelledby="test-radio-set-label"
/>
</>
));
expect(screen.getByLabelText('Radio Set Label text')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

exports[`<SelectableBox.Set /> correct rendering renders without props 1`] = `
<div
aria-label="test-default-label"
className="pgn__selectable_box-set pgn__selectable_box-set--2 pgn__form-control-set"
role="radiogroup"
>
Expand Down

0 comments on commit 1d6f95e

Please sign in to comment.