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

feat(list): enter and spacebar keyboard handling for List and Listitem #279

Merged
merged 11 commits into from
Dec 20, 2018
1 change: 0 additions & 1 deletion .github/add-a-feature.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Add a feature
- [Propose feature](#propose-feature)
- [Prototype](#prototype)
- [Spec out the API](#spec-out-the-api)
- [Component anatomy](#component-anatomy)
- [Create a component](#create-a-component)
- [How to create a component](#how-to-create-a-component)
- [Good practice](#good-practice)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Close `Popup` on outside click @kuzhelov ([#410](https://github.com/stardust-ui/react/pull/410))
- Set default `chatBehavior` which uses Enter/Esc keys @sophieH29 ([#443](https://github.com/stardust-ui/react/pull/443))
- Add `iconPosition` property to `Input` component @mnajdova ([#442](https://github.com/stardust-ui/react/pull/442))
- Handle `Enter` and `Spacebar` keys for selectable `ListItem` @jurokapsiar ([#279](https://github.com/stardust-ui/react/pull/279))

### Documentation
- Add all missing component descriptions and improve those existing @levithomason ([#400](https://github.com/stardust-ui/react/pull/400))
Expand Down
25 changes: 22 additions & 3 deletions src/components/List/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as React from 'react'

import * as PropTypes from 'prop-types'
import * as _ from 'lodash'
import { createShorthandFactory, customPropTypes, UIComponent } from '../../lib'
import ItemLayout from '../ItemLayout/ItemLayout'
import { listItemBehavior } from '../../lib/accessibility'
import { Accessibility } from '../../lib/accessibility/types'
import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibility/types'
import { ComponentVariablesInput, ComponentSlotStyle } from '../../themes/types'
import { Extendable } from '../../../types/utils'
import { Extendable, ComponentEventHandler } from '../../../types/utils'

export interface ListItemProps {
accessibility?: Accessibility
Expand All @@ -20,6 +20,7 @@ export interface ListItemProps {
headerMedia?: any
important?: boolean
media?: any
onClick?: ComponentEventHandler<ListItemProps>
selection?: boolean
truncateContent?: boolean
truncateHeader?: boolean
Expand Down Expand Up @@ -63,6 +64,14 @@ class ListItem extends UIComponent<Extendable<ListItemProps>, ListItemState> {
important: PropTypes.bool,
media: PropTypes.any,

/**
* Called on click.
*
* @param {SyntheticEvent} event - React's original SyntheticEvent.
* @param {object} data - All props.
*/
onClick: PropTypes.func,

/** A list item can indicate that it can be selected. */
selection: PropTypes.bool,
truncateContent: PropTypes.bool,
Expand Down Expand Up @@ -94,6 +103,14 @@ class ListItem extends UIComponent<Extendable<ListItemProps>, ListItemState> {

private itemRef = React.createRef<HTMLElement>()

protected actionHandlers: AccessibilityActionHandlers = {
performClick: event => this.handleClick(event),
jurokapsiar marked this conversation as resolved.
Show resolved Hide resolved
}

handleClick = e => {
_.invoke(this.props, 'onClick', e, this.props)
}

renderComponent({ ElementType, classes, accessibility, rest, styles }) {
const {
as,
Expand Down Expand Up @@ -127,7 +144,9 @@ class ListItem extends UIComponent<Extendable<ListItemProps>, ListItemState> {
headerMediaCSS={styles.headerMedia}
contentCSS={styles.content}
ref={this.itemRef}
onClick={this.handleClick}
{...accessibility.attributes.root}
{...accessibility.keyHandlers.root}
{...rest}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Accessibility } from '../../types'
* Adds role='listbox'.
* The listbox role is used to identify an element that creates a list from which a user may select one or more items.
*/

const selectableListBehavior: Accessibility = (props: any) => ({
attributes: {
root: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as keyboardKey from 'keyboard-key'
import { Accessibility } from '../../types'

/**
* @description
* Adds role='option'. This role is used for a selectable item in a list.
* Adds attribute 'aria-selected=true' based on the property 'active'. Based on this screen readers will recognize the selected state of the item.
* Performs click action with 'Enter' and 'Spacebar' on 'root'.
*/

const selectableListItemBehavior: Accessibility = (props: any) => ({
Expand All @@ -16,6 +18,13 @@ const selectableListItemBehavior: Accessibility = (props: any) => ({
}),
},
},
keyActions: {
root: {
performClick: {
keyCombinations: [{ keyCode: keyboardKey.Enter }, { keyCode: keyboardKey.Spacebar }],
},
},
},
})

export default selectableListItemBehavior
1 change: 1 addition & 0 deletions src/lib/accessibility/Behaviors/Menu/menuItemBehavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as keyboardKey from 'keyboard-key'
* Adds attribute 'aria-label' based on the property 'aria-label' to 'anchor' component's part.
* Adds attribute 'aria-labelledby' based on the property 'aria-labelledby' to 'anchor' component's part.
* Adds attribute 'aria-describedby' based on the property 'aria-describedby' to 'anchor' component's part.
* Performs click action with 'Enter' and 'Spacebar' on 'anchor'.
* The behavior is designed for particular structure of menu item. The item consists of root element and anchor inside the root element.
*/

Expand Down
8 changes: 0 additions & 8 deletions test/specs/components/List/ListItem-test.ts

This file was deleted.

31 changes: 31 additions & 0 deletions test/specs/components/List/ListItem-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react'
import { isConformant, handlesAccessibility } from 'test/specs/commonTests'
import { mountWithProvider } from 'test/utils'

import ListItem from 'src/components/List/ListItem'
import { selectableListItemBehavior } from 'src/lib/accessibility'

describe('ListItem', () => {
isConformant(ListItem)
handlesAccessibility(ListItem, { defaultRootRole: 'listitem' })

describe('selectable list handleClick', () => {
jurokapsiar marked this conversation as resolved.
Show resolved Hide resolved
test('is executed when Enter is pressed', () => {
const onClick = jest.fn()
const listItem = mountWithProvider(
<ListItem accessibility={selectableListItemBehavior} onClick={onClick} />,
).find('ListItem')
listItem.simulate('keydown', { keyCode: 13 })
expect(onClick).toHaveBeenCalled()
})

test('is executed when Spacebar is pressed', () => {
const onClick = jest.fn()
const listItem = mountWithProvider(
<ListItem accessibility={selectableListItemBehavior} onClick={onClick} />,
).find('ListItem')
listItem.simulate('keydown', { keyCode: 32 })
jurokapsiar marked this conversation as resolved.
Show resolved Hide resolved
expect(onClick).toHaveBeenCalled()
jurokapsiar marked this conversation as resolved.
Show resolved Hide resolved
})
})
})