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

[terra-action-header] Action header A11y updates #3736

Merged
merged 4 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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