feat(checkbox): aria (#1226)

* add role for checkboxes

* add aria-checked for checkboxes

* handle indeterminate state

* update changelog

* set aria-labelledby for checkbox

remove unused arg

use seemly for generating a uniq id

use static value for labelId

fix typo

* add role attr for checkbox group
This commit is contained in:
Matthew 2021-09-23 18:49:19 +02:00 committed by GitHub
parent d269054fdf
commit 7cec2ff1b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 2 deletions

View File

@ -11,6 +11,7 @@
- Fix `n-global-style` applies style transition on first mount.
- Fix `n-drawer` border transition [#1211].
- Fix `n-alert` aria support.
- Fix `n-checkbox` aria support.
## 2.19.1 (2021-09-21)

View File

@ -10,6 +10,7 @@ import {
CSSProperties
} from 'vue'
import { useMergedState, useMemo } from 'vooks'
import { createId } from 'seemly'
import { useConfig, useFormItem, useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { NIconSwitchTransition } from '../../_internal'
@ -153,6 +154,7 @@ export default defineComponent({
props,
mergedClsPrefixRef
)
function toggle (e: MouseEvent | KeyboardEvent): void {
if (NCheckboxGroup && props.value !== undefined) {
NCheckboxGroup.toggleCheckbox(!renderedCheckedRef.value, props.value)
@ -197,6 +199,7 @@ export default defineComponent({
mergedDisabled: mergedDisabledRef,
renderedChecked: renderedCheckedRef,
mergedTheme: themeRef,
labelId: `n-checkbox-label-${createId(4)}`,
handleClick,
handleKeyUp,
handleKeyDown,
@ -265,6 +268,7 @@ export default defineComponent({
indeterminate,
privateTableHeader,
cssVars,
labelId,
label,
mergedClsPrefix,
focusable,
@ -284,6 +288,9 @@ export default defineComponent({
}
]}
tabindex={mergedDisabled || !focusable ? undefined : 0}
role="checkbox"
aria-checked={indeterminate ? 'mixed' : renderedChecked}
aria-labelledby={labelId}
style={cssVars as CSSProperties}
onKeyup={handleKeyUp}
onKeydown={handleKeyDown}
@ -319,7 +326,7 @@ export default defineComponent({
<div class={`${mergedClsPrefix}-checkbox-box__border`} />
</div>
{label !== null || $slots.default ? (
<span class={`${mergedClsPrefix}-checkbox__label`}>
<span class={`${mergedClsPrefix}-checkbox__label`} id={labelId}>
{renderSlot($slots, 'default', undefined, () => [label])}
</span>
) : null}

View File

@ -161,7 +161,9 @@ export default defineComponent({
},
render () {
return (
<div class={`${this.mergedClsPrefix}-checkbox-group`}>{this.$slots}</div>
<div class={`${this.mergedClsPrefix}-checkbox-group`} role="group">
{this.$slots}
</div>
)
}
})

View File

@ -5,6 +5,9 @@ import { NForm, NFormItem } from '../../form'
function expectChecked (wrapper: VueWrapper<any>, value: boolean): void {
expect(wrapper.classes().some((c) => c.includes('checked'))).toEqual(value)
expect(wrapper.find('.n-checkbox').attributes('aria-checked')).toBe(
value.toString()
)
}
describe('n-checkbox', () => {
@ -43,6 +46,7 @@ describe('n-checkbox', () => {
expect(wrapper.find('.n-checkbox').classes()).toContain(
'n-checkbox--indeterminate'
)
expect(wrapper.find('.n-checkbox').attributes('aria-checked')).toBe('mixed')
})
it('should work with `disabled` prop', () => {
@ -136,6 +140,29 @@ describe('n-checkbox', () => {
expect(wrapper.find('.n-checkbox').attributes('style')).toMatchSnapshot()
})
describe('accessibility', () => {
it('should have a role of "checkbox"', () => {
const wrapper = mount(NCheckbox)
expect(wrapper.find('.n-checkbox').attributes('role')).toBe('checkbox')
})
it('should set a default aria-labelledby', () => {
const labelId = 'custom-id'
const wrapper = mount(() => <NCheckbox aria-labelledby={labelId} />)
expect(wrapper.find('.n-checkbox').attributes('aria-labelledby')).toMatch(
labelId
)
})
it('should allow to set aria-labelledby from outside', () => {
const wrapper = mount(NCheckbox)
const labelId = wrapper.find('.n-checkbox__label').attributes('id')
expect(wrapper.find('.n-checkbox').attributes('aria-labelledby')).toBe(
labelId
)
})
})
})
describe('n-checkbox-group', () => {
@ -143,6 +170,11 @@ describe('n-checkbox-group', () => {
mount(NCheckboxGroup)
})
it('should have a role of "group"', () => {
const wrapper = mount(NCheckboxGroup)
expect(wrapper.find('.n-checkbox-group').attributes('role')).toBe('group')
})
it('should work with `disabled` prop', () => {
const wrapper = mount(NCheckboxGroup, {
props: {