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

Commit

Permalink
[terra-action-header] Action header A11y updates (#3736)
Browse files Browse the repository at this point in the history
* Action header A11y updates

* Added A11y Guide fir Terra-Action-Header

* linter update

---------

Co-authored-by: SM051274 <sm051274@cerner.net>
  • Loading branch information
supreethmr and SM051274 authored Feb 28, 2023
1 parent 8da9d9f commit a95ee8e
Show file tree
Hide file tree
Showing 63 changed files with 863 additions and 134 deletions.
5 changes: 5 additions & 0 deletions packages/terra-action-header/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

* Added
* Added `text` prop as a replacement of `title` prop to avoid confusion with HTML attribute `title`.
* Added Ally labels for action header navigation buttons.
* Added translations for back, next and previous button.

* Changed
* Updated jest snapshots for button changes.

Expand Down
4 changes: 3 additions & 1 deletion packages/terra-action-header/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
"dependencies": {
"@cerner/terra-docs": "^1.9.0",
"classnames": "^2.2.5",
"lodash.uniqueid": "^4.0.1",
"prop-types": "^15.5.8",
"terra-button": "^3.64.0",
"terra-mixins": "^1.40.0",
"terra-theme-context": "^1.0.0"
"terra-theme-context": "^1.0.0",
"terra-visually-hidden-text": "2.36.0"
},
"scripts": {
"compile": "babel --root-mode upward src --out-dir lib --copy-files",
Expand Down
58 changes: 50 additions & 8 deletions packages/terra-action-header/src/ActionHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import classNames from 'classnames/bind';
import Button, { ButtonVariants } from 'terra-button';
import { injectIntl } from 'react-intl';
import ThemeContext from 'terra-theme-context';
import uniqueid from 'lodash.uniqueid';
import ActionHeaderContainer from './_ActionHeaderContainer';
import styles from './ActionHeader.module.scss';

Expand All @@ -20,8 +21,10 @@ const propTypes = {
*/
intl: PropTypes.shape({ formatMessage: PropTypes.func }),
/**
* ![IMPORTANT](https://badgen.net/badge/UX/Accessibility/blue)
* Optionally sets the heading level. One of `1`, `2`, `3`, `4`, `5`, `6`. Default `level=1`. This helps screen readers to announce appropriate heading levels.
* Changing 'level' will not visually change the style of the content.
* `level` should be specified explicitly to allow screen readers to identify headers consistently.
*/
level: PropTypes.oneOf([1, 2, 3, 4, 5, 6]),
/**
Expand All @@ -33,6 +36,10 @@ const propTypes = {
* Callback function for when the back button is clicked. The back button will not display if this is not set.
*/
onBack: PropTypes.func,
/**
* Accessibility label for Back button. To be used with onBack prop.
*/
backButtonA11yLabel: PropTypes.string,
/**
* Callback function for when the expand button is clicked.
* The expand button will not display if this is not set or on small viewports.
Expand All @@ -55,29 +62,46 @@ const propTypes = {
* Callback function for when the next button is clicked. The previous-next button group will display if either this or onPrevious is set but the button for the one not set will be disabled.
*/
onNext: PropTypes.func,
/**
* Accessibility label for Next button. To be used with onNext prop
*/
nextButtonA11yLabel: PropTypes.string,
/**
* Callback function for when the previous button is clicked. The previous-next button group will display if either this or onNext is set but the button for the one not set will be disabled.
*/
onPrevious: PropTypes.func,
/**
* Accessibility label for Previous button. To be used with onPrevious prop.
*/
prevButtonA11yLabel: PropTypes.string,
/**
* ![IMPORTANT](https://badgen.net/badge/UX/Accessibility/blue)
* Text to be displayed as the title in the header bar.
*/
text: PropTypes.string,
/**
* ![IMPORTANT](https://badgen.net/badge/prop/deprecated/red)
* title prop has been deperecated and will be removed on next major version relase. Replace the `title` prop with `text` prop.
*/
title: PropTypes.string,
};

const defaultProps = {
title: undefined,
level: 1,
text: undefined,
onClose: undefined,
onBack: undefined,
onMaximize: undefined,
onMinimize: undefined,
onNext: undefined,
onPrevious: undefined,
children: undefined,
backButtonA11yLabel: undefined,
prevButtonA11yLabel: undefined,
nextButtonA11yLabel: undefined,
};

const ActionHeader = ({
text,
title,
intl,
level,
Expand All @@ -88,9 +112,16 @@ const ActionHeader = ({
onPrevious,
onNext,
children,
backButtonA11yLabel,
prevButtonA11yLabel,
nextButtonA11yLabel,
...customProps
}) => {
const theme = React.useContext(ThemeContext);
const buttonId = uniqueid();
const closeButtonId = `terra-action-header-close-button-${buttonId}`;
const maximizeButtonId = `terra-action-header-maximize-button-${buttonId}`;
const minimizeButtonId = `terra-action-header-minimize-button-${buttonId}`;

const closeButton = onClose
? (
Expand All @@ -102,6 +133,7 @@ const ActionHeader = ({
text={intl.formatMessage({ id: 'Terra.actionHeader.close' })}
onClick={onClose}
variant={ButtonVariants.UTILITY}
aria-describedby={closeButtonId}
/>
)
: null;
Expand All @@ -112,7 +144,7 @@ const ActionHeader = ({
data-terra-action-header="back-button"
isIconOnly
icon={<span className={cx(['header-icon', 'back'])} />}
text={intl.formatMessage({ id: 'Terra.actionHeader.back' })}
text={backButtonA11yLabel || intl.formatMessage({ id: 'Terra.actionHeader.back' })}
onClick={onBack}
variant={ButtonVariants.UTILITY}
/>
Expand All @@ -131,6 +163,7 @@ const ActionHeader = ({
text={intl.formatMessage({ id: 'Terra.actionHeader.maximize' })}
onClick={onMaximize}
variant={ButtonVariants.UTILITY}
aria-describedby={maximizeButtonId}
/>
);
} else if (onMinimize) {
Expand All @@ -143,6 +176,7 @@ const ActionHeader = ({
text={intl.formatMessage({ id: 'Terra.actionHeader.minimize' })}
onClick={onMinimize}
variant={ButtonVariants.UTILITY}
aria-describedby={minimizeButtonId}
/>
);
}
Expand All @@ -156,7 +190,7 @@ const ActionHeader = ({
data-terra-action-header="previous-button"
isIconOnly
icon={<span className={cx(['header-icon', 'previous'])} />}
text={intl.formatMessage({ id: 'Terra.actionHeader.previous' })}
text={prevButtonA11yLabel || intl.formatMessage({ id: 'Terra.actionHeader.previous' })}
onClick={onPrevious}
isDisabled={onPrevious === undefined}
variant={ButtonVariants.UTILITY}
Expand All @@ -166,7 +200,7 @@ const ActionHeader = ({
data-terra-action-header="next-button"
isIconOnly
icon={<span className={cx(['header-icon', 'next'])} />}
text={intl.formatMessage({ id: 'Terra.actionHeader.next' })}
text={nextButtonA11yLabel || intl.formatMessage({ id: 'Terra.actionHeader.next' })}
onClick={onNext}
isDisabled={onNext === undefined}
variant={ButtonVariants.UTILITY}
Expand All @@ -186,14 +220,22 @@ const ActionHeader = ({
: null;

const rightButtons = closeButton ? <div className={cx('right-buttons', theme.className)}>{closeButton}</div> : null;

if (title) {
// eslint-disable-next-line no-console
console.warn('`title` prop has been renamed to `text`. please update all the refernces of `title` prop to use prop `text`.'); // to be removed on next major version release.
}
if (!level) {
// eslint-disable-next-line no-console
console.warn('Default heading level may not appropriate has it would fail to convey context of heading in a site / application where it is used. Heading level should be set explicitly depending on the position of header in site / application to allow screen readers to identify headers consistently.'); // to be removed on next major version release.
}
return (
<ActionHeaderContainer
{...customProps}
startContent={leftButtons}
title={title}
text={text || title}
endContent={rightButtons}
level={level}
level={level || 1}
id={buttonId}
>
{children}
</ActionHeaderContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,8 @@
}
}
/* stylelint-enable selector-max-compound-selectors */

.hidden-label {
height: 0;
}
}
32 changes: 26 additions & 6 deletions packages/terra-action-header/src/_ActionHeaderContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import classNamesBind from 'classnames/bind';
import ThemeContext from 'terra-theme-context';
import VisuallyHiddenText from 'terra-visually-hidden-text';
import { injectIntl } from 'react-intl';
import styles from './ActionHeaderContainer.module.scss';

const cx = classNamesBind.bind(styles);
Expand All @@ -22,7 +24,7 @@ const propTypes = {
/**
* Text to be displayed as the title in the header bar.
*/
title: PropTypes.string,
text: PropTypes.string,

/**
* Content to be displayed at the end of the header.
Expand All @@ -34,16 +36,21 @@ const propTypes = {
* Changing 'level' will not visually change the style of the content.
*/
level: PropTypes.oneOf([1, 2, 3, 4, 5, 6]).isRequired,
/**
* @private
* The intl object to be injected for translations.
*/
intl: PropTypes.shape({ formatMessage: PropTypes.func }),
};

const defaultProps = {
title: undefined,
text: undefined,
startContent: undefined,
endContent: undefined,
};

const ActionHeaderContainer = ({
children, title, startContent, endContent, level, ...customProps
children, text, startContent, endContent, level, intl, ...customProps
}) => {
const theme = React.useContext(ThemeContext);
const HeaderElement = `h${level}`;
Expand All @@ -52,10 +59,22 @@ const ActionHeaderContainer = ({
React.cloneElement(child, { className: cx(['flex-collapse', children.props.className]) })
));

const titleElement = title ? (
const closeButtonId = `terra-action-header-close-button-${customProps.id}`;
const maximizeButtonId = `terra-action-header-maximize-button-${customProps.id}`;
const minimizeButtonId = `terra-action-header-minimize-button-${customProps.id}`;

const visuallyHiddenComponent = (
<>
<VisuallyHiddenText aria-hidden id={closeButtonId} text={intl.formatMessage({ id: 'Terra.actionHeader.close.description' }, { text })} />
<VisuallyHiddenText aria-hidden id={maximizeButtonId} text={intl.formatMessage({ id: 'Terra.actionHeader.maximize.description' }, { text })} />
<VisuallyHiddenText aria-hidden id={minimizeButtonId} text={intl.formatMessage({ id: 'Terra.actionHeader.minimize.description' }, { text })} />
</>
);

const titleElement = text ? (
<div className={cx('title-container')}>
<HeaderElement className={cx('title')}>
{title}
{text}
</HeaderElement>
</div>
) : undefined;
Expand All @@ -68,11 +87,12 @@ const ActionHeaderContainer = ({
</div>
{content}
{endContent && <div className={cx('flex-end')}>{endContent}</div>}
<div className={cx('hidden-label')}>{visuallyHiddenComponent}</div>
</div>
);
};

ActionHeaderContainer.propTypes = propTypes;
ActionHeaderContainer.defaultProps = defaultProps;

export default ActionHeaderContainer;
export default injectIntl(ActionHeaderContainer);
Loading

0 comments on commit a95ee8e

Please sign in to comment.