Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions __snapshots__/src/index.test.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ Generated by [AVA](https://ava.li).
className="dropdown"
>
<Trigger
clientId="rdts"
onTrigger={Function {}}
showDropdown={true}
tags={[]}
texts={{}}
>
<Input
clientId="rdts"
inputRef={Function inputRef {}}
onBlur={Function {}}
onFocus={Function {}}
Expand Down Expand Up @@ -177,12 +180,15 @@ Generated by [AVA](https://ava.li).
className="dropdown"
>
<Trigger
clientId="rdts"
disabled={true}
onTrigger={Function {}}
showDropdown={false}
tags={[]}
texts={{}}
>
<Input
clientId="rdts"
disabled={true}
inputRef={Function inputRef {}}
onBlur={Function {}}
Expand Down Expand Up @@ -287,21 +293,25 @@ Generated by [AVA](https://ava.li).
className="dropdown radio-select"
>
<Trigger
clientId="rdts"
mode="radioSelect"
onTrigger={Function {}}
showDropdown={false}
tags={[]}
texts={{}}
>
<a
"aria-expanded"="false"
"aria-haspopup"="tree"
className="dropdown-trigger arrow bottom"
id="rdts_trigger"
onClick={Function {}}
onKeyDown={Function {}}
role="button"
tabIndex={0}
>
<Input
clientId="rdts"
inputRef={Function inputRef {}}
mode="radioSelect"
onBlur={Function {}}
Expand Down Expand Up @@ -426,20 +436,24 @@ Generated by [AVA](https://ava.li).
className="dropdown"
>
<Trigger
clientId="rdts"
onTrigger={Function {}}
showDropdown={false}
tags={[]}
texts={{}}
>
<a
"aria-expanded"="false"
"aria-haspopup"="tree"
className="dropdown-trigger arrow bottom"
id="rdts_trigger"
onClick={Function {}}
onKeyDown={Function {}}
role="button"
tabIndex={0}
>
<Input
clientId="rdts"
inputRef={Function inputRef {}}
onBlur={Function {}}
onFocus={Function {}}
Expand Down Expand Up @@ -486,11 +500,14 @@ Generated by [AVA](https://ava.li).
className="dropdown"
>
<Trigger
clientId="rdts"
onTrigger={Function {}}
showDropdown={true}
tags={[]}
texts={{}}
>
<Input
clientId="rdts"
inputRef={Function inputRef {}}
onBlur={Function {}}
onFocus={Function {}}
Expand Down
Binary file modified __snapshots__/src/index.test.js.snap
Binary file not shown.
4 changes: 3 additions & 1 deletion __snapshots__/src/tag/index.test.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ Generated by [AVA](https://ava.li).
> Snapshot 1

<span
"aria-label"="hello"
className="tag"
id="abc_tag"
>
hello
<button
"aria-label"="Remove"
"aria-labelledby"="abc_tag"
"aria-labelledby"="abc_button abc_tag"
className="tag-remove"
id="abc_button"
onClick={Function {}}
onKeyDown={Function {}}
onKeyUp={Function {}}
Expand Down
Binary file modified __snapshots__/src/tag/index.test.js.snap
Binary file not shown.
1 change: 1 addition & 0 deletions __snapshots__/src/trigger/index.test.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Generated by [AVA](https://ava.li).
"aria-expanded"="false"
"aria-haspopup"="tree"
className="dropdown-trigger arrow bottom"
id="rtds_trigger"
onClick={Function {}}
onKeyDown={Function {}}
role="button"
Expand Down
Binary file modified __snapshots__/src/trigger/index.test.js.snap
Binary file not shown.
3 changes: 2 additions & 1 deletion docs/src/stories/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class WithOptions extends PureComponent {
</select>
</div>
<div style={{ marginBottom: '10px' }}>
<label htmlFor={showDropdown}>ShowDropdown: </label>
<label htmlFor={showDropdown}>Show dropdown: </label>
<select
id="showDropdown"
value={showDropdown}
Expand Down Expand Up @@ -123,6 +123,7 @@ class WithOptions extends PureComponent {
disabled={disabled}
readOnly={readOnly}
showDropdown={showDropdown}
texts={{ label: 'Demo Dropdown' }}
/>
</div>
</div>
Expand Down
22 changes: 16 additions & 6 deletions src/a11y/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
export function getAriaLabel(label) {
if (!label) return undefined
export function getAriaLabel(label, additionalLabelledBy) {
const attributes = getAriaAttributeForLabel(label)

if (label && label[0] === '#') {
/* See readme for label. When label starts with # it references ids of dom nodes instead.
When used on aria-labelledby, they should be referenced without a starting hash/# */
return { 'aria-labelledby': label.replace(/#/g, '') }
if (additionalLabelledBy) {
attributes['aria-labelledby'] = `${attributes['aria-labelledby'] || ''} ${additionalLabelledBy}`.trim()
}

return attributes
}

function getAriaAttributeForLabel(label) {
if (!label) return {}

/* See readme for label. When label starts with # it references ids of dom nodes instead.
When used on aria-labelledby, they should be referenced without a starting hash/# */
if (label[0] === '#') {
return { 'aria-labelledby': label.substring(1).replace(/ #/g, ' ') }
}
return { 'aria-label': label }
}
9 changes: 4 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,11 @@ class DropdownTreeSelect extends Component {

render() {
const { disabled, readOnly, mode, texts } = this.props
const { showDropdown, currentFocus } = this.state
const { showDropdown, currentFocus, tags } = this.state

const activeDescendant = currentFocus ? `${currentFocus}_li` : undefined

const commonProps = { disabled, readOnly, activeDescendant, texts, mode }
const commonProps = { disabled, readOnly, activeDescendant, texts, mode, clientId: this.clientId }

return (
<div
Expand All @@ -298,12 +298,12 @@ class DropdownTreeSelect extends Component {
{ 'radio-select': mode === 'radioSelect' }
)}
>
<Trigger onTrigger={this.onTrigger} showDropdown={showDropdown} {...commonProps}>
<Trigger onTrigger={this.onTrigger} showDropdown={showDropdown} {...commonProps} tags={tags}>
<Input
inputRef={el => {
this.searchInput = el
}}
tags={this.state.tags}
tags={tags}
onInputChange={this.onInputChange}
onFocus={this.onInputFocus}
onBlur={this.onInputBlur}
Expand All @@ -327,7 +327,6 @@ class DropdownTreeSelect extends Component {
onNodeToggle={this.onNodeToggle}
mode={mode}
showPartiallySelected={this.props.showPartiallySelected}
clientId={this.clientId}
{...commonProps}
/>
)}
Expand Down
16 changes: 16 additions & 0 deletions src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,19 @@ test('adds aria-label when having label on search input', t => {
t.deepEqual(wrapper.find('.search').prop('aria-labelledby'), undefined)
t.deepEqual(wrapper.find('.search').prop('aria-label'), 'hello world')
})

test('appends selected tags to aria-labelledby with provided aria-labelledby', t => {
const { tree } = t.context
tree[0].checked = true
const wrapper = mount(<DropdownTreeSelect id="rdts" data={tree} texts={{ label: '#hello #world' }} />)
t.deepEqual(wrapper.find('.dropdown-trigger').prop('aria-labelledby'), 'hello world rdts-0_tag')
t.deepEqual(wrapper.find('.dropdown-trigger').prop('aria-label'), undefined)
})

test('appends selected tags to aria-labelledby with text label', t => {
const { tree } = t.context
tree[0].checked = true
const wrapper = mount(<DropdownTreeSelect id="rdts" data={tree} texts={{ label: 'hello world' }} />)
t.deepEqual(wrapper.find('.dropdown-trigger').prop('aria-labelledby'), 'rdts_trigger rdts-0_tag')
t.deepEqual(wrapper.find('.dropdown-trigger').prop('aria-label'), 'hello world')
})
19 changes: 10 additions & 9 deletions src/tag/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import styles from './index.css'

const cx = cn.bind(styles)

export const getTagId = id => `${id}_tag`

class Tag extends PureComponent {
static propTypes = {
id: PropTypes.string.isRequired,
Expand Down Expand Up @@ -40,24 +42,23 @@ class Tag extends PureComponent {
render() {
const { id, label, labelRemove = 'Remove', readOnly, disabled } = this.props

const tagId = `${id}_tag`
const tagId = getTagId(id)
const buttonId = `${id}_button`
const className = cx('tag-remove', { readOnly }, { disabled })
const isDisabled = readOnly || disabled
const onClick = !isDisabled ? this.handleClick : undefined
const onKeyDown = !isDisabled ? this.onKeyDown : undefined
const onKeyUp = !isDisabled ? this.onKeyUp : undefined

return (
<span className={cx('tag')} id={tagId}>
<span className={cx('tag')} id={tagId} aria-label={label}>
{label}
<button
onClick={onClick}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
id={buttonId}
onClick={!isDisabled ? this.handleClick : undefined}
onKeyDown={!isDisabled ? this.onKeyDown : undefined}
onKeyUp={!isDisabled ? this.onKeyUp : undefined}
className={className}
type="button"
aria-label={labelRemove}
aria-labelledby={tagId}
aria-labelledby={`${buttonId} ${tagId}`}
aria-disabled={isDisabled}
>
x
Expand Down
3 changes: 2 additions & 1 deletion src/tree-manager/keyboardNavigation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import nodeVisitor from './nodeVisitor'
import { getTagId } from '../tag'

const Keys = {
Up: 'ArrowUp',
Expand Down Expand Up @@ -150,7 +151,7 @@ const getNextFocusAfterTagDelete = (deletedId, prevTags, tags, fallback) => {

index = tags.length > index ? index : tags.length - 1
const newFocusId = tags[index]._id
const focusNode = document.getElementById(`${newFocusId}_tag`)
const focusNode = document.getElementById(getTagId(newFocusId))
if (focusNode) {
return focusNode.firstElementChild || fallback
}
Expand Down
22 changes: 20 additions & 2 deletions src/trigger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import cn from 'classnames/bind'

import { getAriaLabel } from '../a11y'
import { getTagId } from '../tag'

import styles from '../index.css'

Expand All @@ -16,17 +17,34 @@ class Trigger extends PureComponent {
showDropdown: PropTypes.bool,
mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect', 'hierarchical']),
texts: PropTypes.object,
clientId: PropTypes.string,
tags: PropTypes.array,
}

getAriaAttributes = () => {
const { mode, texts = {}, showDropdown } = this.props
const { mode, texts = {}, showDropdown, clientId, tags } = this.props

const triggerId = `${clientId}_trigger`
const labelledBy = []
let labelAttributes = getAriaLabel(texts.label)
if (tags && tags.length) {
if (labelAttributes['aria-label']) {
// Adds reference to self when having aria-label
labelledBy.push(triggerId)
}
tags.forEach(t => {
labelledBy.push(getTagId(t._id))
})
labelAttributes = getAriaLabel(texts.label, labelledBy.join(' '))
}

const attributes = {
id: triggerId,
role: 'button',
tabIndex: 0,
'aria-haspopup': mode === 'simpleSelect' ? 'listbox' : 'tree',
'aria-expanded': showDropdown ? 'true' : 'false',
...getAriaLabel(texts.label),
...labelAttributes,
}

return attributes
Expand Down
2 changes: 1 addition & 1 deletion src/trigger/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ import toJson from 'enzyme-to-json'
import Trigger from './index'

test('Trigger component', t => {
const input = toJson(shallow(<Trigger />))
const input = toJson(shallow(<Trigger clientId={'rtds'} />))
t.snapshot(input)
})