Skip to content

Commit

Permalink
Add tabIndex prop to React Icon component (#849)
Browse files Browse the repository at this point in the history
* 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 <colebemis@github.com>
  • Loading branch information
broccolinisoup and colebemis committed Oct 7, 2022
1 parent 0c758fe commit f6c8d2b
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/sixty-nails-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/octicons': minor
---

Add `tabIndex` prop to React icon components
18 changes: 18 additions & 0 deletions docs/content/packages/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => (
<PlusIcon aria-label="Interactive Plus Icon" tabIndex={0} /> New Item
)
```

### Sizes

The `size` prop takes `small`, `medium`, and `large` values that can be used to
Expand Down
2 changes: 1 addition & 1 deletion lib/octicons_react/__tests__/tree-shaking.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"`)
})
Original file line number Diff line number Diff line change
Expand Up @@ -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;"
Expand Down
17 changes: 17 additions & 0 deletions lib/octicons_react/src/__tests__/octicon.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(<AlertIcon />)
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(<AlertIcon aria-label="icon" tabIndex={0} />)
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(<AlertIcon aria-label="icon" tabIndex={-1} />)
expect(container.querySelector('svg')).toHaveAttribute('tabindex', '-1')
expect(container.querySelector('svg')).toHaveAttribute('focusable', 'false')
})

it('respects the className prop', () => {
const {container} = render(<AlertIcon className="foo" />)
expect(container.querySelector('svg')).toHaveAttribute('class', 'foo')
Expand Down
4 changes: 3 additions & 1 deletion lib/octicons_react/src/createIconComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,6 +20,8 @@ export function createIconComponent(name, defaultClassName, getSVGData) {
return (
<svg
aria-hidden={ariaLabel ? 'false' : 'true'}
tabIndex={tabIndex}
focusable={tabIndex >= 0 ? 'true' : 'false'}
aria-label={ariaLabel}
role="img"
className={className}
Expand Down
1 change: 1 addition & 0 deletions lib/octicons_react/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type Size = 'small' | 'medium' | 'large'

export interface OcticonProps {
'aria-label'?: string
tabIndex?: number
children?: React.ReactElement<any>
className?: string
fill?: string
Expand Down

0 comments on commit f6c8d2b

Please sign in to comment.