1
0
Эх сурвалжийг харах

feat(a11y): switch focus (#805)

YannLynn 3 жил өмнө
parent
commit
75ff2c9b6a

+ 1 - 0
packages/semi-foundation/switch/constants.ts

@@ -2,6 +2,7 @@ import { BASE_CLASS_PREFIX } from '../base/constants';
 
 const cssClasses = {
     PREFIX: `${BASE_CLASS_PREFIX}-switch`,
+    FOCUS: `${BASE_CLASS_PREFIX}-switch-focus`,
     LARGE: `${BASE_CLASS_PREFIX}-switch-large`,
     SMALL: `${BASE_CLASS_PREFIX}-switch-small`,
     CHECKED: `${BASE_CLASS_PREFIX}-switch-checked`,

+ 16 - 0
packages/semi-foundation/switch/foundation.ts

@@ -3,6 +3,7 @@ import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 export interface SwitchAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
     setNativeControlChecked: (nativeControlChecked: boolean | undefined) => void;
     setNativeControlDisabled: (nativeControlDisabled: boolean | undefined) => void;
+    setFocusVisible: (focusVisible: boolean) => void;
     notifyChange: (checked: boolean, e: any) => void;
 }
 
@@ -37,6 +38,21 @@ export default class SwitchFoundation<P = Record<string, any>, S = Record<string
         }
     }
 
+    handleFocusVisible = (event: any) => {
+        const { target } = event;
+        try {
+            if (target.matches(':focus-visible')) {
+                this._adapter.setFocusVisible(true);
+            }
+        } catch (error){
+            console.warn('The current browser does not support the focus-visible');
+        }
+    }
+
+    handleBlur = () => {
+        this._adapter.setFocusVisible(false);
+    }
+
     // eslint-disable-next-line @typescript-eslint/no-empty-function
     destroy(): void {}
 }

+ 4 - 0
packages/semi-foundation/switch/switch.scss

@@ -28,6 +28,10 @@ $module: #{$prefix}-switch;
         }
     }
 
+    &-focus {
+        outline: $width-switch-outline solid $color-switch_primary-outline-focus;
+    }
+
     &-checked {
         background-color: $color-switch_checked-bg-default;
 

+ 2 - 0
packages/semi-foundation/switch/variables.scss

@@ -37,6 +37,7 @@ $color-switch_unchecked-text-default: var(--semi-color-text-2); // 关闭态开
 $color-switch_loading_spin-default: var(--semi-color-white); // 加载态开关loading图标颜色
 $color-switch_spin_checked-bg-default: var(--semi-color-success-hover); // 已开启开关加载态loading背景颜色
 $color-switch_spin_unchecked-bg-default: var(--semi-color-fill-1); // 已关闭开关加载态loading背景颜色
+$color-switch_primary-outline-focus: var(--semi-color-primary-light-active); // 开关轮廓 - 聚焦
 
 // Width/Height
 $width-switch: 40px; // 开关宽度
@@ -57,6 +58,7 @@ $width-switch_checked_unchecked_text: 26px; // 开关按钮文字宽度
 $width-switch_spin-small: 10px; // 小尺寸开关加载 spin 宽度
 $width-switch_spin-default: 18px; // 默认尺寸开关加载 spin 宽度
 $width-switch_spin-large: 28px; // 大尺寸开关加载 spin 宽度
+$width-switch-outline: 2px; // 开关轮廓宽度
 
 
 // Spacing

+ 20 - 3
packages/semi-ui/switch/index.tsx

@@ -1,4 +1,4 @@
-/* eslint-disable max-len, jsx-a11y/role-supports-aria-props */
+/* eslint-disable max-len */
 import React from 'react';
 import cls from 'classnames';
 import PropTypes from 'prop-types';
@@ -33,6 +33,7 @@ export interface SwitchProps {
 export interface SwitchState {
     nativeControlChecked: boolean;
     nativeControlDisabled: boolean;
+    focusVisible: boolean;
 }
 
 class Switch extends BaseComponent<SwitchProps, SwitchState> {
@@ -74,6 +75,7 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
         this.state = {
             nativeControlChecked: false,
             nativeControlDisabled: false,
+            focusVisible: false
         };
         this.switchRef = React.createRef();
         this.foundation = new SwitchFoudation(this.adapter);
@@ -105,14 +107,25 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
             setNativeControlDisabled: (nativeControlDisabled: boolean): void => {
                 this.setState({ nativeControlDisabled });
             },
+            setFocusVisible: (focusVisible: boolean): void => {
+                this.setState({ focusVisible });
+            },
             notifyChange: (checked: boolean, e: React.ChangeEvent<HTMLInputElement>): void => {
                 this.props.onChange(checked, e);
             },
         };
     }
 
+    handleFocusVisible = (event: React.FocusEvent) => {
+        this.foundation.handleFocusVisible(event);
+    }
+
+    handleBlur = (event: React.FocusEvent) => {
+        this.foundation.handleBlur();
+    }
+
     render() {
-        const { nativeControlChecked, nativeControlDisabled } = this.state;
+        const { nativeControlChecked, nativeControlDisabled, focusVisible } = this.state;
         const { className, style, onMouseEnter, onMouseLeave, size, checkedText, uncheckedText, loading, id } = this.props;
         const wrapperCls = cls(className, {
             [cssClasses.PREFIX]: true,
@@ -121,10 +134,10 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
             [cssClasses.LARGE]: size === 'large',
             [cssClasses.SMALL]: size === 'small',
             [cssClasses.LOADING]: loading,
+            [cssClasses.FOCUS]: focusVisible,
         });
         const switchProps = {
             type: 'checkbox',
-            role: 'switch',
             className: cssClasses.NATIVE_CONTROL,
             disabled: nativeControlDisabled || loading,
             checked: nativeControlChecked || false,
@@ -157,13 +170,17 @@ class Switch extends BaseComponent<SwitchProps, SwitchState> {
                     {...switchProps}
                     ref={this.switchRef}
                     id={id}
+                    role='switch'
                     aria-checked={nativeControlChecked}
                     aria-invalid={this.props['aria-invalid']}
                     aria-errormessage={this.props['aria-errormessage']}
                     aria-label={this.props['aria-label']}
                     aria-labelledby={this.props['aria-labelledby']}
                     aria-describedby={this.props["aria-describedby"]}
+                    aria-disabled={this.props['disabled']}
                     onChange={e => this.foundation.handleChange(e.target.checked, e)}
+                    onFocus={e => this.handleFocusVisible(e)}
+                    onBlur={e => this.handleBlur(e)}
                 />
             </div>
         );