From 7cec2ff1b3cb16d3053981d7394c7d15aa9740bf Mon Sep 17 00:00:00 2001 From: Matthew <29732250+Matth10@users.noreply.github.com> Date: Thu, 23 Sep 2021 18:49:19 +0200 Subject: [PATCH] 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 --- CHANGELOG.en-US.md | 1 + src/checkbox/src/Checkbox.tsx | 9 +++++++- src/checkbox/src/CheckboxGroup.tsx | 4 +++- src/checkbox/tests/Checkbox.spec.tsx | 32 ++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 2ed81e08e..02d7b10b4 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -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) diff --git a/src/checkbox/src/Checkbox.tsx b/src/checkbox/src/Checkbox.tsx index 7f14d80b6..56d2945e5 100644 --- a/src/checkbox/src/Checkbox.tsx +++ b/src/checkbox/src/Checkbox.tsx @@ -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({
{label !== null || $slots.default ? ( - + {renderSlot($slots, 'default', undefined, () => [label])} ) : null} diff --git a/src/checkbox/src/CheckboxGroup.tsx b/src/checkbox/src/CheckboxGroup.tsx index 3099efa24..5042cbdf0 100644 --- a/src/checkbox/src/CheckboxGroup.tsx +++ b/src/checkbox/src/CheckboxGroup.tsx @@ -161,7 +161,9 @@ export default defineComponent({ }, render () { return ( -
{this.$slots}
+
+ {this.$slots} +
) } }) diff --git a/src/checkbox/tests/Checkbox.spec.tsx b/src/checkbox/tests/Checkbox.spec.tsx index 955c03a1d..71822d591 100644 --- a/src/checkbox/tests/Checkbox.spec.tsx +++ b/src/checkbox/tests/Checkbox.spec.tsx @@ -5,6 +5,9 @@ import { NForm, NFormItem } from '../../form' function expectChecked (wrapper: VueWrapper, 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(() => ) + 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: {