From 7357148fc4855afc884519cc703f82c8477d7f69 Mon Sep 17 00:00:00 2001 From: Cagatay Civici Date: Mon, 25 Apr 2022 18:13:22 +0300 Subject: [PATCH] a11y for MultiStateCheckbox --- .../components/multistatecheckbox.js | 28 +++----- components/doc/multistatecheckbox/index.js | 69 +++++++++++++------ .../multistatecheckbox/MultiStateCheckbox.js | 37 ++++++---- pages/multistatecheckbox/index.js | 2 +- pages/tristatecheckbox/index.js | 2 +- 5 files changed, 81 insertions(+), 57 deletions(-) diff --git a/api-generator/components/multistatecheckbox.js b/api-generator/components/multistatecheckbox.js index 453e80b4a6..de87fc9b74 100644 --- a/api-generator/components/multistatecheckbox.js +++ b/api-generator/components/multistatecheckbox.js @@ -5,12 +5,6 @@ const MultiStateCheckboxProps = [ default: 'null', description: 'Unique identifier of the element.' }, - { - name: 'inputId', - type: 'string', - default: 'null', - description: 'Unique identifier of the native checkbox element.' - }, { name: 'value', type: 'any', @@ -29,6 +23,12 @@ const MultiStateCheckboxProps = [ default: 'null', description: 'Property name to use as the value of an option, defaults to the option itself when not defined.' }, + { + name: 'optionLabel', + type: 'string', + default: 'null', + description: 'Property name to refer to the option label, used by screen readers only. Defaults to optionValue.' + }, { name: 'iconTemplate', type: 'any', @@ -36,10 +36,10 @@ const MultiStateCheckboxProps = [ description: 'Template of icon for the selected option.' }, { - name: 'name', + name: 'dataKey', type: 'string', default: 'null', - description: 'Name of the checkbox element .' + description: 'A property to uniquely match the value in options for better performance.' }, { name: 'style', @@ -82,18 +82,6 @@ const MultiStateCheckboxProps = [ type: 'object', default: 'null', description: 'Configuration of the tooltip, refer to the tooltip documentation for more information.' - }, - { - name: 'ariaLabelledBy', - type: 'string', - default: 'null', - description: 'Establishes relationships between the component and label(s) where its value should be one or more element IDs.' - }, - { - name: 'dataKey', - type: 'string', - default: 'null', - description: 'A property to uniquely match the value in options for better performance.' } ]; diff --git a/components/doc/multistatecheckbox/index.js b/components/doc/multistatecheckbox/index.js index cb64de1fe1..fd4115faa8 100644 --- a/components/doc/multistatecheckbox/index.js +++ b/components/doc/multistatecheckbox/index.js @@ -210,12 +210,6 @@ import { MultiStateCheckbox } from 'primereact/multistatecheckbox'; null Unique identifier of the element. - - inputId - string - null - Unique identifier of the native checkbox element. - value any @@ -234,6 +228,12 @@ import { MultiStateCheckbox } from 'primereact/multistatecheckbox'; null Property name to use as the value of an option, defaults to the option itself when not defined. + + optionLabel + string + null + Property name to refer to the option label, used by screen readers only. Defaults to optionValue. + iconTemplate any @@ -241,10 +241,10 @@ import { MultiStateCheckbox } from 'primereact/multistatecheckbox'; Template of icon for the selected option. - name + dataKey string null - Name of the checkbox element . + A property to uniquely match the value in options for better performance. style @@ -270,6 +270,12 @@ import { MultiStateCheckbox } from 'primereact/multistatecheckbox'; false When present, it specifies that the element value cannot be altered. + + tabIndex + number + null + Index of the element in tabbing order. + empty boolean @@ -288,18 +294,6 @@ import { MultiStateCheckbox } from 'primereact/multistatecheckbox'; null Configuration of the tooltip, refer to the tooltip documentation for more information. - - ariaLabelledBy - string - null - Establishes relationships between the component and label(s) where its value should be one or more element IDs. - - - dataKey - string - null - A property to uniquely match the value in options for better performance. - @@ -357,6 +351,41 @@ import { MultiStateCheckbox } from 'primereact/multistatecheckbox'; +
Accessibility
+
Screen Reader
+

MultiStateCheckbox component uses an element with checkbox role. Value to describe the component can either be provided with aria-labelledby or aria-label props. Component adds an element with + aria-live attribute that is only visible to screen readers to read the value displayed. Values to read are defined with the optionLabel property that defaults to optionValue if not defined. Unchecked state label on the other hand is + retrieved from nullLabel key of the aria property from the locale API. This is an example of a custom accessibility implementation as there is no one to one mapping between the component design and the WCAG specification.

+ +{` +Access Type + + + +`} + +
Keyboard Support
+
+ + + + + + + + + + + + + + + + + +
KeyFunction
tabMoves focus to the checkbox.
spaceToggles between the values.
+
+
Dependencies

None.

diff --git a/components/lib/multistatecheckbox/MultiStateCheckbox.js b/components/lib/multistatecheckbox/MultiStateCheckbox.js index b8a4da5d6b..0fd1c83ccc 100644 --- a/components/lib/multistatecheckbox/MultiStateCheckbox.js +++ b/components/lib/multistatecheckbox/MultiStateCheckbox.js @@ -1,18 +1,17 @@ import * as React from 'react'; import { useMountEffect } from '../hooks/Hooks'; import { Tooltip } from '../tooltip/Tooltip'; +import { ariaLabel } from '../api/Api'; import { classNames, ObjectUtils } from '../utils/Utils'; export const MultiStateCheckbox = React.memo(React.forwardRef((props, ref) => { const [focusedState, setFocusedState] = React.useState(false); const elementRef = React.useRef(null); - const inputRef = React.useRef(props.inputRef); const equalityKey = props.optionValue ? null : props.dataKey; const onClick = (event) => { if (!props.disabled && !props.readOnly) { toggle(event); - inputRef.current.focus(); } } @@ -20,6 +19,11 @@ export const MultiStateCheckbox = React.memo(React.forwardRef((props, ref) => { return props.optionValue ? ObjectUtils.resolveFieldData(option, props.optionValue) : option; } + const getOptionAriaLabel = (option) => { + const ariaField = props.optionLabel || props.optionValue; + return ariaField ? ObjectUtils.resolveFieldData(option, ariaField) : option; + } + const findNextOption = () => { if (props.options) { return selectedOptionIndex === props.options.length - 1 ? (props.empty ? null : props.options[0]) : props.options[selectedOptionIndex + 1]; @@ -54,6 +58,13 @@ export const MultiStateCheckbox = React.memo(React.forwardRef((props, ref) => { setFocusedState(false); } + const onKeyDown = (e) => { + if (e.keyCode === 32) { + toggle(e); + e.preventDefault(); + } + } + const getSelectedOptionMap = () => { let option, index; @@ -65,10 +76,6 @@ export const MultiStateCheckbox = React.memo(React.forwardRef((props, ref) => { return { option, index }; } - React.useEffect(() => { - ObjectUtils.combinedRefs(inputRef, props.inputRef); - }, [inputRef, props.inputRef]); - useMountEffect(() => { if (!props.empty && props.value === null) { toggle(); @@ -107,17 +114,17 @@ export const MultiStateCheckbox = React.memo(React.forwardRef((props, ref) => { 'p-focus': focusedState }, selectedOption && selectedOption.className); const icon = createIcon(); + console.log(!!selectedOption); + const ariaValueLabel = !!selectedOption ? getOptionAriaLabel(selectedOption) : ariaLabel('nullLabel'); return ( <>
-
- -
-
+
{icon}
+ {focusedState && {ariaValueLabel}}
{hasTooltip && } @@ -128,21 +135,21 @@ MultiStateCheckbox.displayName = 'MultiStateCheckbox'; MultiStateCheckbox.defaultProps = { __TYPE: 'MultiStateCheckbox', id: null, - inputRef: null, - inputId: null, value: null, options: null, optionValue: null, + optionLabel: null, iconTemplate: null, dataKey: null, - name: null, style: null, className: null, disabled: false, readOnly: false, empty: true, + tabIndex: "0", + 'aria-label': null, + 'aria-labelledby': null, tooltip: null, tooltipOptions: null, - ariaLabelledBy: null, onChange: null } diff --git a/pages/multistatecheckbox/index.js b/pages/multistatecheckbox/index.js index e6bca30d0c..b9d493be97 100644 --- a/pages/multistatecheckbox/index.js +++ b/pages/multistatecheckbox/index.js @@ -31,7 +31,7 @@ const MultiStateCheckboxDemo = () => {
- setValue(e.value)} /> + setValue(e.value)} aria-label="Access Type" />
diff --git a/pages/tristatecheckbox/index.js b/pages/tristatecheckbox/index.js index 6d31604967..9df9402cf1 100644 --- a/pages/tristatecheckbox/index.js +++ b/pages/tristatecheckbox/index.js @@ -26,7 +26,7 @@ const TriStateCheckboxDemo = () => {
- setValue(e.value)} aria-label="Confirmation" /> + setValue(e.value)} aria-label="Terms Accepted" />