Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

feat(Accessibility): Make Grid keyboard navigable by adding Grid Behavior #398

Merged
merged 12 commits into from
Nov 5, 2018
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react'
import { Grid, Image, Button, gridBehavior } from '@stardust-ui/react'
import _ from 'lodash'

const imageNames = [
'ade',
'chris',
'christian',
'daniel',
'elliot',
'helen',
'jenny',
'joe',
'justen',
'laura',
'matt',
'nan',
'nom',
'stevie',
'steve',
'tom',
'veronika',
]

const imageButtonStyles = {
minWidth: '72px',
maxWidth: '72px',
height: '72px',
padding: '0',
margin: '0',
background: '#fff',
}
const renderImages = () => {
return _.map(imageNames, imageName => (
<Image
key={imageName}
fluid
src={`public/images/avatar/large/${imageName}.jpg`}
data-is-focusable="true"
/>
))
}

const renderImageButtons = () => {
return _.map(imageNames, imageName => (
<Button key={imageName} styles={imageButtonStyles}>
<Image fluid src={`public/images/avatar/large/${imageName}.jpg`} />
</Button>
))
}

const gridStyles = {
gridColumnGap: '10px',
gridRowGap: '10px',
}

const GridExample = () => (
<div>
Grid with images, which are not natively focusable elements. Set 'data-is-focusable=true' to
each item to make grid items focusable and navigable.
<Grid accessibility={gridBehavior} styles={gridStyles} columns="7" content={renderImages()} />
<br />
Grid with images, wrapped with buttons, which are natively focusable elements. No need to add
'data-is-focusable'='true'.
<Grid
accessibility={gridBehavior}
styles={gridStyles}
columns="7"
content={renderImageButtons()}
/>
</div>
)

export default GridExample
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react'
import { Grid, Image, Button, gridBehavior } from '@stardust-ui/react'
import _ from 'lodash'

const imageNames = [
'ade',
'chris',
'christian',
'daniel',
'elliot',
'helen',
'jenny',
'joe',
'justen',
'laura',
'matt',
'nan',
'nom',
'stevie',
'steve',
'tom',
'veronika',
]

const imageButtonStyles = {
minWidth: '72px',
maxWidth: '72px',
height: '72px',
padding: '0',
margin: '0',
background: '#fff',
}
const renderImages = () => {
return _.map(imageNames, imageName => (
<Image
key={imageName}
fluid
src={`public/images/avatar/large/${imageName}.jpg`}
data-is-focusable="true"
/>
))
}

const renderImageButtons = () => {
return _.map(imageNames, imageName => (
<Button key={imageName} styles={imageButtonStyles}>
<Image fluid src={`public/images/avatar/large/${imageName}.jpg`} />
</Button>
))
}

const gridStyles = {
gridColumnGap: '10px',
gridRowGap: '10px',
}

const GridExample = () => (
<div>
Grid with images, which are not natively focusable elements. Set 'data-is-focusable=true' to
each item to make grid items focusable and navigable.
<Grid accessibility={gridBehavior} styles={gridStyles} columns="7">
{renderImages()}
</Grid>
<br />
Grid with images, wrapped with button components, which are natively focusable elements. No need
to add 'data-is-focusable'='true'
<Grid accessibility={gridBehavior} styles={gridStyles} columns="7">
{renderImageButtons()}
</Grid>
</div>
)

export default GridExample
5 changes: 5 additions & 0 deletions docs/src/examples/components/Grid/Variations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const Variations = () => (
description="We can specify a certain amount of columns and rows or the explicit columns and rows for a grid."
examplePath="components/Grid/Variations/GridExampleColumnsAndRows"
/>
<ComponentExample
title="Navigable with keyboard arrow buttons"
description="Use a Grid accessibility behavior, so Grid items can be keyboard navigable by adding 'data-is-focusable=true' attribute to each item. This attribute can be skipped if the Grid items are natively focusable elements, like buttons, anchors etc."
examplePath="components/Grid/Variations/GridExampleKeyboardNavigable"
/>
</ExampleSection>
)

Expand Down
10 changes: 9 additions & 1 deletion src/components/Grid/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import * as React from 'react'
import { UIComponent, childrenExist, customPropTypes, RenderResultConfig } from '../../lib'
import { ComponentVariablesInput, ComponentSlotStyle } from '../../themes/types'
import { Extendable, ShorthandValue, ReactChildren } from '../../../types/utils'
import { Accessibility } from '../../lib/accessibility/types'
import { defaultBehavior } from '../../lib/accessibility'

import ReactNode = React.ReactNode

export interface GridProps {
as?: any
accessibility?: Accessibility
className?: string
children?: ReactChildren
columns?: string | number
Expand Down Expand Up @@ -59,10 +63,14 @@ class Grid extends UIComponent<Extendable<GridProps>, any> {

/** Override for theme site variables to allow modifications of component styling via themes. */
variables: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),

/** Accessibility behavior if overridden by the user. */
accessibility: PropTypes.func,
}

public static defaultProps = {
public static defaultProps: GridProps = {
as: 'div',
accessibility: defaultBehavior,
}

public renderComponent({ ElementType, classes, rest }: RenderResultConfig<any>): ReactNode {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export {
export {
default as chatMessageEnterEscBehavior,
} from './lib/accessibility/Behaviors/Chat/chatMessageEnterEscBehavior'
export { default as gridBehavior } from './lib/accessibility/Behaviors/Grid/gridBehavior'

//
// Utilities
Expand Down
17 changes: 17 additions & 0 deletions src/lib/accessibility/Behaviors/Grid/gridBehavior.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Accessibility, FocusZoneMode } from '../../types'

/**
* @description
* Wraps component in FocusZone allowing circular arrow key navigation through the children of the component.
*/
const gridBehavior: Accessibility = (props: any) => ({
focusZone: {
mode: FocusZoneMode.Wrap,
props: {
isCircularNavigation: true,
preventDefaultWhenHandled: true,
},
},
})

export default gridBehavior
1 change: 1 addition & 0 deletions src/lib/accessibility/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export { default as chatMessageBehavior } from './Behaviors/Chat/chatMessageBeha
export {
default as chatMessageEnterEscBehavior,
} from './Behaviors/Chat/chatMessageEnterEscBehavior'
export { default as gridBehavior } from './Behaviors/Grid/gridBehavior'
2 changes: 2 additions & 0 deletions test/specs/behaviors/behavior-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
toggleButtonBehavior,
toolbarBehavior,
toolbarButtonBehavior,
gridBehavior,
} from 'src/lib/accessibility'
import { TestHelper } from './testHelper'
import definitions from './testDefinitions'
Expand Down Expand Up @@ -51,5 +52,6 @@ testHelper.addBehavior('tabListBehavior', tabListBehavior)
testHelper.addBehavior('toolbarBehavior', toolbarBehavior)
testHelper.addBehavior('toggleButtonBehavior', toggleButtonBehavior)
testHelper.addBehavior('toolbarButtonBehavior', toolbarButtonBehavior)
testHelper.addBehavior('gridBehavior', gridBehavior)

testHelper.run(behaviorMenuItems)
4 changes: 2 additions & 2 deletions test/specs/behaviors/testDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ definitions.push({
},
})

// Wraps component in FocusZone allowing arrow key navigation through the children of the component.
// [Circular navigation] Wraps component in FocusZone allowing circular arrow key navigation through the children of the component.
definitions.push({
regexp: /Wraps component in FocusZone allowing arrow key navigation through the children of the component.\.+/g,
regexp: /Wraps component in FocusZone allowing circular arrow key navigation through the children of the component\.+/g,
testMethod: (parameters: TestMethod) => {
const property = {
isCircularNavigation: undefined,
Expand Down