소스 검색

feat(a11y): checkbox aria #205

走鹃 3 년 전
부모
커밋
139008655d

+ 13 - 4
packages/semi-foundation/checkbox/checkboxFoundation.ts

@@ -1,4 +1,5 @@
 import BaseFoundation, { DefaultAdapter, noopFunction } from '../base/foundation';
+import isEnterPress from '../utils/isEnterPress';
 
 export interface BasicTargetObject {
     [x: string]: any;
@@ -28,7 +29,7 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
     // eslint-disable-next-line @typescript-eslint/no-empty-function
     init() {}
 
-    getEvent(checked: boolean, e: MouseEvent) {
+    getEvent(checked: boolean, e: any) {
         const props = this.getProps();
         const cbValue = {
             target: {
@@ -45,12 +46,12 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
         return cbValue;
     }
 
-    notifyChange(checked: boolean, e: MouseEvent) {
+    notifyChange(checked: boolean, e: any) {
         const cbValue = this.getEvent(checked, e);
         this._adapter.notifyChange(cbValue);
     }
 
-    handleChange(e: MouseEvent) {
+    handleChange(e: any) {
         const disabled = this.getProp('disabled');
 
         if (disabled) {
@@ -78,7 +79,7 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
         }
     }
 
-    handleChangeInGroup(e: MouseEvent) {
+    handleChangeInGroup(e: any) {
         const { value } = this.getProps();
         const groupValue = this._adapter.getGroupValue();
         const checked = groupValue.includes(value);
@@ -88,6 +89,12 @@ class CheckboxFoundation<P = Record<string, any>, S = Record<string, any>> exten
         this._adapter.notifyGroupChange(event);
     }
 
+    handleEnterPress(e: any) {
+        if (isEnterPress(e)) {
+            this.handleChange(e);
+        }
+    }
+
     setChecked(checked: boolean) {
         this._adapter.setNativeControlChecked(checked);
     }
@@ -110,6 +117,8 @@ export interface BaseCheckboxProps {
     onMouseEnter?: (e: any) => void;
     onMouseLeave?: (e: any) => void;
     extra?: any;
+    addonId?: string;
+    extraId?: string;
 }
 
 export default CheckboxFoundation;

+ 20 - 4
packages/semi-ui/checkbox/checkbox.tsx

@@ -44,6 +44,9 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
         onMouseEnter: PropTypes.func,
         onMouseLeave: PropTypes.func,
         extra: PropTypes.node,
+        addonId: PropTypes.string, // A11y aria-labelledby
+        extraId: PropTypes.string, // A11y aria-describedby
+        index: PropTypes.number,
     };
 
     static defaultProps = {
@@ -74,6 +77,7 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
         };
     }
 
+    foundation: CheckboxFoundation;
     constructor(props: CheckboxProps) {
         super(props);
 
@@ -111,6 +115,8 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
 
     handleChange: React.MouseEventHandler<HTMLSpanElement> = e => this.foundation.handleChange(e);
 
+    handleEnterPress = (e: React.KeyboardEvent<HTMLSpanElement>) => this.foundation.handleEnterPress(e);
+
     render() {
         const {
             disabled,
@@ -123,6 +129,8 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             onMouseLeave,
             extra,
             value,
+            addonId,
+            extraId
         } = this.props;
         const { checked } = this.state;
         const props: Record<string, any> = {
@@ -130,7 +138,8 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             disabled,
         };
 
-        if (this.isInGroup()) {
+        const inGroup = this.isInGroup();
+        if (inGroup) {
             if (this.context.checkboxGroup.value) {
                 const realChecked = (this.context.checkboxGroup.value || []).includes(value);
                 props.checked = realChecked;
@@ -161,21 +170,28 @@ class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
             [`${prefix}-cardType_extra_noChildren`]: props.isCardType && !children,
         });
 
-        const name = this.isInGroup() && this.context.checkboxGroup.name;
+        const name = inGroup && this.context.checkboxGroup.name;
 
         const renderContent = () => (
             <>
-                {children ? <span className={`${prefix}-addon`}>{children}</span> : null}
-                {extra ? <div className={extraCls}>{extra}</div> : null}
+                {children ? <span id={addonId} className={`${prefix}-addon`}>{children}</span> : null}
+                {extra ? <div id={extraId} className={extraCls}>{extra}</div> : null}
             </>
         );
         return (
             <span
+                role='checkbox'
+                tabIndex={disabled ? -1 : 0}
+                aria-disabled={props.checked}
+                aria-checked={props.checked}
+                aria-labelledby={addonId}
+                aria-describedby={extraId}
                 style={style}
                 className={wrapper}
                 onMouseEnter={onMouseEnter}
                 onMouseLeave={onMouseLeave}
                 onClick={this.handleChange}
+                onKeyPress={this.handleEnterPress}
             >
                 <CheckboxInner
                     {...this.props}

+ 11 - 4
packages/semi-ui/checkbox/checkboxGroup.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import classnames from 'classnames';
 import { checkboxGroupClasses as css, strings } from '@douyinfe/semi-foundation/checkbox/constants';
-import CheckboxGroupFoudation, { CheckboxGroupAdapter } from '@douyinfe/semi-foundation/checkbox/checkboxGroupFoundation';
+import CheckboxGroupFoundation, { CheckboxGroupAdapter } from '@douyinfe/semi-foundation/checkbox/checkboxGroupFoundation';
 import BaseComponent from '../_base/baseComponent';
 import { Context } from './context';
 import { isEqual } from 'lodash-es';
@@ -68,12 +68,13 @@ class CheckboxGroup extends BaseComponent<CheckboxGroupProps, CheckboxGroupState
         };
     }
 
+    foundation: CheckboxGroupFoundation;
     constructor(props: CheckboxGroupProps) {
         super(props);
         this.state = {
             value: props.value || props.defaultValue,
         };
-        this.foundation = new CheckboxGroupFoudation(this.adapter);
+        this.foundation = new CheckboxGroupFoundation(this.adapter);
         this.onChange = this.onChange.bind(this);
     }
 
@@ -96,7 +97,7 @@ class CheckboxGroup extends BaseComponent<CheckboxGroupProps, CheckboxGroupState
     }
 
     render() {
-        const { children, options, prefixCls, direction, className, style, type } = this.props;
+        const { children, options, prefixCls, direction, className, style, type, disabled } = this.props;
 
         const isPureCardType = type === strings.TYPE_PURECARD;
         const isCardType = type === strings.TYPE_CARD || isPureCardType;
@@ -148,7 +149,13 @@ class CheckboxGroup extends BaseComponent<CheckboxGroupProps, CheckboxGroupState
         }
 
         return (
-            <div className={prefixClsDisplay} style={style}>
+            <div
+                role="listbox"
+                aria-label="Checkbox group"
+                aria-disabled={disabled}
+                className={prefixClsDisplay} 
+                style={style}
+            >
                 <Context.Provider
                     value={{
                         checkboxGroup: {

+ 7 - 1
packages/semi-ui/checkbox/checkboxInner.tsx

@@ -15,6 +15,8 @@ export interface CheckboxInnerProps {
     name?: string;
     isPureCardType?: boolean;
     ref?: React.MutableRefObject<CheckboxInner> | ((ref: CheckboxInner) => void);
+    addonId?: string;
+    extraId?: string;
 }
 
 class CheckboxInner extends PureComponent<CheckboxInnerProps> {
@@ -28,6 +30,8 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
         grouped: PropTypes.bool,
         value: PropTypes.any,
         isPureCardType: PropTypes.bool,
+        addonId: PropTypes.string,
+        extraId: PropTypes.string,
     };
 
     static defaultProps = {
@@ -44,7 +48,7 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
     }
 
     render() {
-        const { indeterminate, checked, disabled, prefixCls, name, isPureCardType } = this.props;
+        const { indeterminate, checked, disabled, prefixCls, name, isPureCardType, addonId, extraId } = this.props;
         const prefix = prefixCls || css.PREFIX;
 
         const wrapper = classnames(
@@ -69,6 +73,8 @@ class CheckboxInner extends PureComponent<CheckboxInnerProps> {
         return (
             <span className={wrapper}>
                 <input
+                    aria-hidden={true}
+                    tabIndex={-1}
                     ref={ref => {
                         this.inputEntity = ref;
                     }}