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

Commit

Permalink
[terra-icon] accessibility upgrade (#3598)
Browse files Browse the repository at this point in the history
  • Loading branch information
sdadn authored and Saad Adnan committed Sep 30, 2022
1 parent 7b0c3ea commit 1943427
Show file tree
Hide file tree
Showing 557 changed files with 7,714 additions and 866 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import IconBasePropsTable from 'terra-icon/src/IconBase?dev-site-props-table';
# Terra Icon

The terra-icon component is used to visually represent a literal or symbolic object intended to initiate an action, communicate a status, or navigate the workflow.
Icons can be `meaningful` where they used to convey a semantic meaning or `decorative` where they are are simply used for aesthetic purposes.
See [Usage](#usage) for more information.

## Getting Started

Expand All @@ -28,6 +30,9 @@ This component requires the following peer dependencies be installed in your app

## Usage

### Meaningful Icons
Each Icon can be imported individually.

```jsx
import IconAdd from 'terra-icon/lib/icon/IconAdd';
import IconEdit from 'terra-icon/lib/icon/IconEdit';
Expand All @@ -38,6 +43,19 @@ import IconEdit from 'terra-icon/lib/icon/IconEdit';
</div>
```
By default, the icons imported are `meaningful` which means that they are intended to convey meaning and thus, the `a11yLabel` prop is required for screenreaders.
If icons are intended for aesthetic purposes, then `decorative` icons and be imported as follows:
```jsx
import IconAddDecorative from 'terra-icon/lib/icon/decorative/IconAdd';
import IconEditDecorative from 'terra-icon/lib/icon/decoartive/IconEdit';
<div>
<IconAdd />
<IconEdit />
</div>
```

## Component Features
* [Cross-Browser Support](https://engineering.cerner.com/terra-ui/about/terra-ui/component-standards#cross-browser-support)
* [Responsive Support](https://engineering.cerner.com/terra-ui/about/terra-ui/component-standards#responsive-support)
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import IconAlert from 'terra-icon/lib/icon/IconAlert';
import IconAlertDecorative from 'terra-icon/lib/icon/decorative/IconAlert';

const IconAccessibleLabel = () => (
<div>
<h3>Meaningful icon</h3>
<div>
<IconAlert a11yLabel="alert icon"/>
</div>

<h3>Decorative icon</h3>
<div>
<IconAlertDecorative a11yLabel="alert icon"/>
</div>
</div>
);

export default IconAccessibleLabel;
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
:local {
.icon {
height: 40px;
margin: 5px;
width: 40px;
margin: 5px;
}

.icon-inverse {
Expand Down
627 changes: 310 additions & 317 deletions packages/terra-core-docs/src/terra-dev-site/test/icon/IconAll.test.jsx

Large diffs are not rendered by default.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ const IconBidi = () => (
<div dir="rtl">
<h3>Icon where isBidi is false by default</h3>
<h4>Default</h4>
<IconDeviceCheck />
<IconDeviceCheck a11yLabel="device check icon"/>
<h4>Set isBidi true</h4>
<IconDeviceCheck isBidi />
<IconDeviceCheck a11yLabel="device check icon" isBidi />
<h4>Set isBidi false</h4>
<IconDeviceCheck isBidi={false} />
<IconDeviceCheck a11yLabel="device check icon" isBidi={false} />

<h3>Icon where isBidi is true by default</h3>
<h4>Default</h4>
<IconComment />
<IconComment a11yLabel="comment icon"/>
<h4>Set isBidi true</h4>
<IconComment isBidi />
<IconComment a11yLabel="comment icon" isBidi />
<h4>Set isBidi false</h4>
<IconComment isBidi={false} />
<IconComment a11yLabel="comment icon" isBidi={false} />
</div>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import IconAdd from 'terra-icon/lib/icon/IconAdd';
const IconDefault = () => (
<div>
<h3>Default Icon</h3>
<IconAdd id="icon-default" />
<IconAdd id="icon-default" a11yLabel="add icon"/>
</div>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ const cx = classNames.bind(styles);
const IconHeightWidth = () => (
<div>
<h3>Default</h3>
<IconAlert />
<IconAlert a11yLabel="alert icon"/>
<h3>Height and Width are 50px</h3>
<IconAlert id="icon-height-50-width-50" height="50" width="50" />
<IconAlert a11yLabel="alert icon" id="icon-height-50-width-50" height="50" width="50" />
<h3>Height and Width are 5em</h3>
<IconAlert id="icon-height-5em-width-5em" height="5em" width="5em" />
<IconAlert a11yLabel="alert icon" id="icon-height-5em-width-5em" height="5em" width="5em" />
<h3>Container font size is 5em</h3>
<div className={cx('icon-wrapper')}>
<IconAlert />
<IconAlert a11yLabel="alert icon"/>
</div>
<h3>Height of 5em</h3>
<IconAlert id="icon-height-5em" height="5em" />
<IconAlert a11yLabel="alert icon" id="icon-height-5em" height="5em" />
<h3>Width of 5em</h3>
<IconAlert id="icon-width-5em" width="5em" />
<IconAlert a11yLabel="alert icon" id="icon-width-5em" width="5em" />
</div>
);

Expand Down
2 changes: 2 additions & 0 deletions packages/terra-icon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changelog

## Unreleased
* Breaking changes
* Updated terra-icon to meet accessibility standards.

* Changed
* Updated to `one-cerner-style-icons` v1.48.0
Expand Down
10 changes: 8 additions & 2 deletions packages/terra-icon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,24 @@
"clean:all-icons": "find . -name \\all_icons.png -type f -exec rm -f {} \\;",
"compilescripts": "npm run compilescripts:clean && npm run compilescripts:build",
"compilescripts:clean": "rm -rf scripts/lib",
"create-decorative-dir": "mkdir -p src/icon/decorative && cp -r src/icon/*.jsx src/icon/decorative/",
"compilescripts:build": "babel --root-mode upward scripts/src --out-dir scripts/lib --copy-files",
"migrate-csv": "node scripts/lib/migrate-csv/index.js",
"migrate-svg": "node scripts/lib/migrate-svg/index.js",
"generate-json": "node scripts/lib/generate-json/index.js",
"generate-icons": "node scripts/lib/generate-icons/index.js",
"generate-all-icons": "npm run generate-meaningful-icons && npm run generate-decorative-icons",
"generate-meaningful-icons": "npm run compilescripts && node scripts/lib/generate-icon/index.js",
"generate-decorative-icons": "npm run create-decorative-dir && npm run replace-decorative-baseclass && npm run replace-decorative-import && npm run replace-decorative-displayname",
"generate-example": "node scripts/lib/generate-example/index.js",
"migrate-ocs-icons": "npm run compilescripts && npm run migrate-csv && npm run migrate-svg && npm run generate-json && npm run generate-icons && npm run generate-example",
"migrate-cerner-one-icons": "npm run compilescripts && npm run migrate-csv && npm run migrate-svg && npm run generate-json && npm run generate-all-icons && npm run generate-example",
"compile": "babel --root-mode upward src --out-dir lib --copy-files",
"lint": "npm run lint:js && npm run lint:scss",
"lint:js": "eslint --ext .js,.jsx . --ignore-path ../../.eslintignore",
"lint:scss": "stylelint src/**/*.scss",
"precompile": "rm -rf lib",
"replace-decorative-baseclass": "find src/icon/decorative -type f -exec perl -i -pe's|IconBase|IconBaseDec|g' {} +",
"replace-decorative-import": "find src/icon/decorative -type f -exec perl -i -pe's|\\Q../\\E|../../|g' {} +",
"replace-decorative-displayname": "find src/icon/decorative -type f -exec perl -i -pe's/(SvgIcon\\.displayName = \")(\\w[\\w\\d]+)(\";)/\\1\\2Dec\\3/g' {} +",
"test": "npm run lint && npm run jest && npm run wdio",
"jest": "jest --config ../../jest.config.js",
"wdio-default": "cd ../.. && terra wdio",
Expand Down
27 changes: 26 additions & 1 deletion packages/terra-icon/scripts/src/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
Each script has its own readme. Instructions on when and how to use these scripts are in the [terra-icons readme](../../README.md).
# Update all SVG icons

### Step 1. Initate scripts

npm run compilescripts

### Step 2. (Optional) Migrate icons from a csv file
You can skip this step if you're not updating the svg files in `packages/terra-icon/src/svg`.

npm run migrate-csv
npm run migrate-svg

### Step 3. Generate icons based
This will generate icons based on the files in `packages/terra-icon/src/svg`, whether they are newly migrated or pre-existing.

npm run generate-all-icons

Meaningful icons will be generated in `packages/terra-icon/src/icon`, directly in the folder.
Decorative icons will be generated in `packages/terra-icon/src/icon/decorative`.


### Alternative run all steps command

Alternatively, if you need to run steps 1, 2 and 3, then you can execute them all with a single command:

npm run migrate-cerner-one-icons
2 changes: 1 addition & 1 deletion packages/terra-icon/scripts/src/generate-icons/parseSvg.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Icon from './Icon';
const parseSvg = filepath => new Promise((resolve) => {
const source = fs.readFileSync(filepath, 'utf-8');
const { name } = path.parse(filepath);

const { document } = new JSDOM(source).window;
const icon = new Icon(name, document.querySelector('svg'));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const SvgIcon = (customProps) => {
const attributes = Object.assign({}, customProps);

return (
<IconBase {...attributes}>
<IconBase {...attributes} >
<%= icon.children %>
</IconBase>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import { FormattedMessage } from 'react-intl';

const SvgIcon = (customProps) => {
const attributes = Object.assign({}, customProps);

return (
<FormattedMessage id="Terra.icon.<%= icon.name %>.title">
{iconTitle => (
<span aria-label={iconTitle} title={iconTitle}>
<IconBase {...attributes}>
<IconBase {...attributes} >
<%= icon.children %>
</IconBase>
</span>
Expand Down
30 changes: 13 additions & 17 deletions packages/terra-icon/src/IconBase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import styles from './Icon.module.scss';
const cx = classNamesBind.bind(styles);

const propTypes = {
/**
* String that labels the current element.
*/
a11yLabel: PropTypes.string.isRequired,
/**
* Should the svg mirror when dir="rtl".
*/
Expand All @@ -29,11 +33,6 @@ const propTypes = {
* Width of SVG.
*/
width: PropTypes.string,
/**
* String that labels the current element. If 'aria-label' is present,
* role is set to 'img' and aria-hidden is removed.
*/
ariaLabel: PropTypes.string,
/**
* Focusable attribute. IE 10/11 are focusable without this attribute.
*/
Expand All @@ -46,17 +45,20 @@ const defaultProps = {
children: null,
height: '1em',
width: '1em',
ariaLabel: null,
focusable: 'false',
};

// Returns a SVG representing the icon. Is utilized as: <Iconbase {..props} ><svg children></IconBase>
// Note: while an img is the ideal recommended approach by accessibility guidelines,
// IconBase returns a svg so that non-static icons can be themable by using the CSS color property.

const IconBase = ({
a11yLabel,
isBidi,
isSpin,
children,
height,
width,
ariaLabel,
focusable,
...customProps
}) => {
Expand All @@ -73,20 +75,14 @@ const IconBase = ({
attributes.className,
);

// aria-label is present, remove aria-hidden, set role to img
if (ariaLabel) {
attributes['aria-label'] = ariaLabel;
attributes.role = 'img';
attributes['aria-hidden'] = null;
} else {
attributes['aria-hidden'] = 'true';
}

attributes.height = height;
attributes.width = width;
attributes.focusable = focusable;

const svgA11yLabel = React.createElement('title', {}, a11yLabel);
const svgChildren = new Array(svgA11yLabel).concat(children);

return <svg {...attributes} className={classes}>{children}</svg>;
return <svg {...attributes} className={classes}>{svgChildren}</svg>;
};

IconBase.propTypes = propTypes;
Expand Down
81 changes: 81 additions & 0 deletions packages/terra-icon/src/IconBaseDec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import classNamesBind from 'classnames/bind';

// eslint-disable-next-line import/no-unresolved, import/no-webpack-loader-syntax
import styles from './Icon.module.scss';

const cx = classNamesBind.bind(styles);

const propTypes = {
/**
* Should the svg mirror when dir="rtl".
*/
isBidi: PropTypes.bool,
/**
* Should the SVG rotate.
*/
isSpin: PropTypes.bool,
/**
* Child nodes.
*/
children: PropTypes.node,
/**
* Height of SVG.
*/
height: PropTypes.string,
/**
* Width of SVG.
*/
width: PropTypes.string,
/**
* Focusable attribute. IE 10/11 are focusable without this attribute.
*/
focusable: PropTypes.string,
};

const defaultProps = {
isBidi: false,
isSpin: false,
children: null,
height: '1em',
width: '1em',
focusable: 'false',
};

const IconBase = ({
isBidi,
isSpin,
children,
height,
width,
title,
focusable,
...customProps
}) => {
const attributes = { ...customProps };

// append to existing classNames
const classes = classNames(
cx(
'tui-Icon',
'icon',
{ 'is-bidi': isBidi },
{ 'is-spin': isSpin },
),
attributes.className,
);

attributes.height = height;
attributes.width = width;
attributes.focusable = focusable;
attributes.role = 'presentation';

return <svg {...attributes} className={classes}>{children}</svg>;
};

IconBase.propTypes = propTypes;
IconBase.defaultProps = defaultProps;

export default IconBase;
2 changes: 1 addition & 1 deletion packages/terra-icon/src/icon/IconAdd.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const SvgIcon = (customProps) => {
const attributes = Object.assign({}, customProps);

return (
<IconBase {...attributes}>
<IconBase {...attributes} >
<path d="M48 21H27V0h-6v21H0v6h21v21h6V27h21z" ></path>
</IconBase>
);
Expand Down
Loading

0 comments on commit 1943427

Please sign in to comment.