From f6c8d2b37a4f38d99d5f9279f793a88a020933cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arma=C4=9Fan?= Date: Sat, 8 Oct 2022 08:56:39 +1000 Subject: [PATCH] Add `tabIndex` prop to React `Icon` component (#849) * add focusable prop to svg * add changeset and write tests * update snapshots and tests and add docs * tabIndex prop introduced * update tree shaking snapshot * changeset update & remove type * Minor copy edits Co-authored-by: Cole Bemis --- .changeset/sixty-nails-juggle.md | 5 +++++ docs/content/packages/react.mdx | 18 ++++++++++++++++++ .../__tests__/tree-shaking.test.js | 2 +- .../__tests__/__snapshots__/octicon.js.snap | 1 + lib/octicons_react/src/__tests__/octicon.js | 17 +++++++++++++++++ lib/octicons_react/src/createIconComponent.js | 4 +++- lib/octicons_react/src/index.d.ts | 1 + 7 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 .changeset/sixty-nails-juggle.md diff --git a/.changeset/sixty-nails-juggle.md b/.changeset/sixty-nails-juggle.md new file mode 100644 index 000000000..e3835d632 --- /dev/null +++ b/.changeset/sixty-nails-juggle.md @@ -0,0 +1,5 @@ +--- +'@primer/octicons': minor +--- + +Add `tabIndex` prop to React icon components diff --git a/docs/content/packages/react.mdx b/docs/content/packages/react.mdx index 64d4973d4..d6d220bcb 100644 --- a/docs/content/packages/react.mdx +++ b/docs/content/packages/react.mdx @@ -66,6 +66,24 @@ export default () => ( ) ``` +### `tabIndex` + +You can add the `tabindex` attribute to an SVG element via the `tabIndex` prop if the SVG element is intended to be interactive. +`tabIndex` prop also controls the `focusable` attribute of the SVG element which is defined by SVG Tiny 1.2 and only implemented in +Internet Explorer and Microsoft Edge. + +If there is no `tabIndex` prop is present (default behavior), it will set the `focusable` attribute to `false`. This is helpful +for preventing the decorative SVG from being announced by some specialized assistive technology browsing modes which can get delayed +while trying to parse the SVG markup. + +```js +// Example usage +import {PlusIcon} from '@primer/octicons-react' +export default () => ( + New Item +) +``` + ### Sizes The `size` prop takes `small`, `medium`, and `large` values that can be used to diff --git a/lib/octicons_react/__tests__/tree-shaking.test.js b/lib/octicons_react/__tests__/tree-shaking.test.js index 0d1b8d2f3..b4c2bdec3 100644 --- a/lib/octicons_react/__tests__/tree-shaking.test.js +++ b/lib/octicons_react/__tests__/tree-shaking.test.js @@ -50,5 +50,5 @@ test('tree shaking single export', async () => { }) const bundleSize = Buffer.byteLength(output[0].code.trim()) / 1000 - expect(`${bundleSize}kB`).toMatchInlineSnapshot(`"2.484kB"`) + expect(`${bundleSize}kB`).toMatchInlineSnapshot(`"2.595kB"`) }) diff --git a/lib/octicons_react/src/__tests__/__snapshots__/octicon.js.snap b/lib/octicons_react/src/__tests__/__snapshots__/octicon.js.snap index bf7fb20e3..74fd31a01 100644 --- a/lib/octicons_react/src/__tests__/__snapshots__/octicon.js.snap +++ b/lib/octicons_react/src/__tests__/__snapshots__/octicon.js.snap @@ -5,6 +5,7 @@ exports[`An icon component matches snapshot 1`] = ` aria-hidden="true" class="octicon octicon-alert" fill="currentColor" + focusable="false" height="16" role="img" style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;" diff --git a/lib/octicons_react/src/__tests__/octicon.js b/lib/octicons_react/src/__tests__/octicon.js index d2cdc044d..642d645e4 100644 --- a/lib/octicons_react/src/__tests__/octicon.js +++ b/lib/octicons_react/src/__tests__/octicon.js @@ -69,6 +69,23 @@ describe('An icon component', () => { expect(container.querySelector('svg')).toHaveAttribute('aria-label', 'icon') }) + it('set the focusable prop to false if tabIndex prop is not present', () => { + const {container} = render() + expect(container.querySelector('svg')).toHaveAttribute('focusable', 'false') + }) + + it('sets focusable prop to true if tabIndex prop is present and greater than 0', () => { + const {container} = render() + expect(container.querySelector('svg')).toHaveAttribute('tabindex', '0') + expect(container.querySelector('svg')).toHaveAttribute('focusable', 'true') + }) + + it('sets focusable prop to false if tabIndex prop is -1', () => { + const {container} = render() + expect(container.querySelector('svg')).toHaveAttribute('tabindex', '-1') + expect(container.querySelector('svg')).toHaveAttribute('focusable', 'false') + }) + it('respects the className prop', () => { const {container} = render() expect(container.querySelector('svg')).toHaveAttribute('class', 'foo') diff --git a/lib/octicons_react/src/createIconComponent.js b/lib/octicons_react/src/createIconComponent.js index 7f08ecb86..75849f0c3 100644 --- a/lib/octicons_react/src/createIconComponent.js +++ b/lib/octicons_react/src/createIconComponent.js @@ -10,7 +10,7 @@ export function createIconComponent(name, defaultClassName, getSVGData) { const svgDataByHeight = getSVGData() const heights = Object.keys(svgDataByHeight) - function Icon({'aria-label': ariaLabel, className, fill = 'currentColor', size, verticalAlign}) { + function Icon({'aria-label': ariaLabel, tabIndex, className, fill = 'currentColor', size, verticalAlign}) { const height = sizeMap[size] || size const naturalHeight = closestNaturalHeight(heights, height) const naturalWidth = svgDataByHeight[naturalHeight].width @@ -20,6 +20,8 @@ export function createIconComponent(name, defaultClassName, getSVGData) { return ( = 0 ? 'true' : 'false'} aria-label={ariaLabel} role="img" className={className} diff --git a/lib/octicons_react/src/index.d.ts b/lib/octicons_react/src/index.d.ts index 0c733cb8d..23012e42e 100644 --- a/lib/octicons_react/src/index.d.ts +++ b/lib/octicons_react/src/index.d.ts @@ -6,6 +6,7 @@ type Size = 'small' | 'medium' | 'large' export interface OcticonProps { 'aria-label'?: string + tabIndex?: number children?: React.ReactElement className?: string fill?: string