Skip to content
This repository has been archived by the owner on May 24, 2024. It is now read-only.

Commit

Permalink
[Terra-Button-Group] Button group A11y Updates (#3731)
Browse files Browse the repository at this point in the history
* Button group A11y Updates

* Added JEst and WDIO test

* jest snapshot update

* fixed terra-button A11y issue of not hounoring A11ylabel of icon when provided

* Updated change logs

* A11y Guide Updated

* typo

* to fix WDIO focus missmatch issue

* increased timer

* increased timer

* Fix WDiO

---------

Co-authored-by: SM051274 <sm051274@cerner.net>
Co-authored-by: saket2403 <saket.bajaj1998@gmail.com>
  • Loading branch information
3 people authored Feb 27, 2023
1 parent c5e3808 commit 8da9d9f
Show file tree
Hide file tree
Showing 45 changed files with 516 additions and 104 deletions.
3 changes: 3 additions & 0 deletions packages/terra-action-header/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Changed
* Updated jest snapshots for button changes.

## 2.77.0 - (February 16, 2023)

* Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ exports[`ActionHeader correctly applies the theme context className 1`] = `
className="header-icon maximize"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -58,7 +57,6 @@ exports[`ActionHeader correctly applies the theme context className 1`] = `
className="header-icon close"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -96,7 +94,6 @@ exports[`ActionHeader should render an action header with back and close buttons
className="header-icon close"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand All @@ -122,7 +119,6 @@ exports[`ActionHeader should render an action header with back and close buttons
className="header-icon back"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -155,7 +151,6 @@ exports[`ActionHeader should render an action header with back button and title
className="header-icon back"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -186,7 +181,6 @@ exports[`ActionHeader should render an action header with close button and title
className="header-icon close"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand All @@ -213,7 +207,6 @@ exports[`ActionHeader should render an action header with custom button and titl
title="Action Header"
>
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -252,7 +245,6 @@ exports[`ActionHeader should render an action header with maximize button and ti
className="header-icon maximize"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -285,7 +277,6 @@ exports[`ActionHeader should render an action header with minimize button and ti
className="header-icon minimize"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand All @@ -311,7 +302,6 @@ exports[`ActionHeader should render an action header with multiple custom button
>
<span>
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand All @@ -323,7 +313,6 @@ exports[`ActionHeader should render an action header with multiple custom button
variant="neutral"
/>
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -357,7 +346,6 @@ exports[`ActionHeader should render an action header with next and previous butt
className="header-icon previous"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand All @@ -376,7 +364,6 @@ exports[`ActionHeader should render an action header with next and previous butt
className="header-icon next"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -422,7 +409,6 @@ exports[`ActionHeader should render an action header with title, enabled next bu
className="header-icon previous"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={true}
Expand All @@ -440,7 +426,6 @@ exports[`ActionHeader should render an action header with title, enabled next bu
className="header-icon next"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -477,7 +462,6 @@ exports[`ActionHeader should render an action header with title, enabled previou
className="header-icon previous"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand All @@ -496,7 +480,6 @@ exports[`ActionHeader should render an action header with title, enabled previou
className="header-icon next"
/>
}
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={true}
Expand Down
3 changes: 3 additions & 0 deletions packages/terra-alert/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Changed
* Updated jest snapshots for button changes.

## 4.65.0 - (February 16, 2023)

* Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,6 @@ exports[`Alert of type success with an action button text content should render
<InjectIntl(Alert)
action={
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -797,7 +796,6 @@ exports[`Alert of type success with an action button text content should render
<Alert
action={
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -903,7 +901,6 @@ exports[`Alert of type success with an action button text content should render
className="actions"
>
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -1574,7 +1571,6 @@ exports[`Dismissable Alert of type custom with action button, custom title and t
<InjectIntl(Alert)
action={
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -1627,7 +1623,6 @@ exports[`Dismissable Alert of type custom with action button, custom title and t
<Alert
action={
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -1741,7 +1736,6 @@ exports[`Dismissable Alert of type custom with action button, custom title and t
className="actions actions-custom"
>
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -1776,7 +1770,6 @@ exports[`Dismissable Alert of type custom with action button, custom title and t
</button>
</Button>
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down Expand Up @@ -1944,7 +1937,6 @@ exports[`Dismissible Alert that includes actions section should render an alert
className="actions"
>
<Button
iconType="decorative"
isBlock={false}
isCompact={false}
isDisabled={false}
Expand Down
6 changes: 6 additions & 0 deletions packages/terra-button-group/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

* Added
* Added support for decoratove icons and multi-selection.

* Fixed
* Fixed keyboard navigation feature and fixes to convey selection states.

## 3.63.0 - (February 16, 2023)

* Changed
Expand Down
52 changes: 49 additions & 3 deletions packages/terra-button-group/src/ButtonGroup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import classNamesBind from 'classnames/bind';
import ThemeContext from 'terra-theme-context';
import { KEY_RIGHT, KEY_LEFT } from 'keycode-js';
import ButtonGroupButton from './ButtonGroupButton';
import ButtonGroupUtils from './ButtonGroupUtils';
import styles from './ButtonGroup.module.scss';
Expand All @@ -20,6 +21,11 @@ const propTypes = {
*/
isBlock: PropTypes.bool,

/**
* Whether or not it is a multi select button group.
*/
isMultiSelect: PropTypes.bool,

/**
* Callback function when the state changes. Parameters are (event, key).
*/
Expand All @@ -35,12 +41,14 @@ const defaultProps = {
children: [],
isBlock: false,
selectedKeys: [],
isMultiSelect: false,
};

class ButtonGroup extends React.Component {
constructor(props) {
super(props);
this.handleOnChange = this.handleOnChange.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}

handleOnChange(event, key) {
Expand All @@ -49,6 +57,38 @@ class ButtonGroup extends React.Component {
}
}

handleKeyDown(event, idx) {
const allBtns = this.btnGrpRef.querySelectorAll('[data-terra-button-group-button]');
let key = idx;

if (event.keyCode === KEY_RIGHT && allBtns[key + 1]) {
key += 1;
while (allBtns[key] && allBtns[key].hasAttribute('disabled')) {
key += 1;
}
if (allBtns[key]) allBtns[key].focus();
}

if (event.keyCode === KEY_LEFT && allBtns[key - 1]) {
key -= 1;
while (allBtns[key] && allBtns[key].hasAttribute('disabled')) {
key -= 1;
}
if (allBtns[key]) allBtns[key].focus();
}
}

wrapKeyDown(item, idx) {
const { onKeyDown } = item.props;
return (event) => {
this.handleKeyDown(event, idx);

if (onKeyDown) {
onKeyDown(event);
}
};
}

wrapOnClick(item) {
const { onClick } = item.props;
return (event) => {
Expand All @@ -64,6 +104,7 @@ class ButtonGroup extends React.Component {
const {
children,
isBlock,
isMultiSelect,
onChange,
selectedKeys,
...customProps
Expand All @@ -81,19 +122,24 @@ class ButtonGroup extends React.Component {
);

const allButtons = children ? [] : undefined;
// eslint-disable-next-line no-nested-ternary
const btnRole = onChange ? isMultiSelect ? 'checkbox' : 'radio' : 'button';

React.Children.forEach(children, (child) => {
React.Children.forEach(children, (child, index) => {
const isSelected = selectedKeys.indexOf(child.key) > -1;
const cloneChild = React.cloneElement(child, {
role: btnRole,
onClick: this.wrapOnClick(child),
onKeyDown: this.wrapKeyDown(child, index),
className: cx([{ 'is-selected': isSelected && !child.props.isDisabled }, child.props.className]),
'aria-pressed': child.props.isDisabled ? null : isSelected,
'aria-pressed': btnRole === 'button' && !child.props.isDisabled ? isSelected : undefined,
'aria-checked': btnRole !== 'button' && !child.props.isDisabled ? isSelected : undefined,
});
allButtons.push(cloneChild);
});

return (
<div {...customProps} className={buttonGroupClassNames}>
<div {...customProps} ref={(btnGrpRef) => { this.btnGrpRef = btnGrpRef; }} role={btnRole === 'radio' ? 'radiogroup' : 'group'} className={buttonGroupClassNames}>
{allButtons}
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion packages/terra-button-group/src/ButtonGroupButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class ButtonGroupButton extends React.Component {
handleKeyUp(event) {
// Apply focus styles for keyboard navigation.
// The onFocus event doesn't get triggered in some browsers, hence, the focus state needs to be managed here.
if (event.nativeEvent.keyCode === KeyCode.KEY_TAB) {
if (event.nativeEvent.keyCode === KeyCode.KEY_TAB || event.nativeEvent.keyCode === KeyCode.KEY_LEFT || event.nativeEvent.keyCode === KeyCode.KEY_RIGHT) {
this.setState({ focused: true });
this.shouldShowFocus = true;
}
Expand Down Expand Up @@ -140,6 +140,7 @@ class ButtonGroupButton extends React.Component {
onFocus={this.handleFocus}
variant={Button.Opts.Variants.NEUTRAL}
className={buttonClassName}
data-terra-button-group-button
/>
);
}
Expand Down
22 changes: 22 additions & 0 deletions packages/terra-button-group/tests/jest/ButtonGroup.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,28 @@ it('should select a button', () => {
expect(buttonGroup).toMatchSnapshot();
});

it('should apply correct role for multiSelect button group', () => {
const onChange = jest.fn();
const buttonGroup = shallow((
<ButtonGroup isMultiSelect onChange={onChange}>
{button1}
{button2}
</ButtonGroup>
));
expect(buttonGroup).toMatchSnapshot();
});

it('should apply correct role for single select button group', () => {
const onChange = jest.fn();
const buttonGroup = shallow((
<ButtonGroup onChange={onChange}>
{button1}
{button2}
</ButtonGroup>
));
expect(buttonGroup).toMatchSnapshot();
});

it('correctly applies the theme context className', () => {
const buttonGroup = mount(
<ThemeContextProvider theme={{ className: 'orion-fusion-theme' }}>
Expand Down
Loading

0 comments on commit 8da9d9f

Please sign in to comment.