Kaynağa Gözat

feat(a11y): navigation aria #205

走鹃 3 yıl önce
ebeveyn
işleme
45eba3eb92

+ 11 - 0
packages/semi-foundation/navigation/itemFoundation.ts

@@ -1,5 +1,6 @@
 /* argus-disable unPkgSensitiveInfo */
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import isEnterPress from '../utils/isEnterPress';
 
 export interface ItemProps {
     text?: any;
@@ -37,6 +38,7 @@ export interface ItemAdapter<P = Record<string, any>, S = Record<string, any>> e
     notifyMouseLeave(e: any): void;
     getIsCollapsed(): boolean;
     getSelected(): boolean;
+    getIsOpen(): boolean;
 }
 
 export default class ItemFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<ItemAdapter<P, S>, P, S> {
@@ -91,4 +93,13 @@ export default class ItemFoundation<P = Record<string, any>, S = Record<string,
         }
         this._adapter.notifyClick({ itemKey, text, domEvent: e });
     }
+
+    /**
+     * A11y: simulate item click
+     */
+    handleKeyPress(e: any) {
+        if (isEnterPress(e)) {
+            this.handleClick(e);
+        }
+    }
 }

+ 12 - 0
packages/semi-foundation/navigation/subNavFoundation.ts

@@ -1,4 +1,5 @@
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import isEnterPress from '../utils/isEnterPress';
 
 const addKeys = function addKeys(originKeys: (string | number)[] = [], ...willAddKeys: (string | number)[]) {
     const keySet = new Set(originKeys);
@@ -120,4 +121,15 @@ export default class SubNavFoundation<P = Record<string, any>, S = Record<string
         this._adapter.notifyGlobalOpenChange(cbVal);
         this._adapter.notifyGlobalOnClick(cbVal);
     }
+
+    /**
+     * A11y: simulate sub nav click
+     * @param e 
+     * @param titleRef 
+     */
+    handleKeyPress(e: any, titleRef: any) {
+        if (isEnterPress(e)) {
+            this.handleClick(e, titleRef);
+        }
+    }
 }

+ 15 - 0
packages/semi-ui/navigation/Item.tsx

@@ -76,6 +76,7 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
         disabled: false,
     };
 
+    foundation: ItemFoundation;
     constructor(props: NavItemProps) {
         super(props);
         this.state = {
@@ -108,6 +109,8 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
             getIsCollapsed: () => this.props.isCollapsed || Boolean(this.context && this.context.isCollapsed) || false,
             getSelected: () =>
                 Boolean(this.context && this.context.selectedKeys && this.context.selectedKeys.includes(this.props.itemKey)),
+            getIsOpen: () =>
+                Boolean(this.context && this.context.openKeys && this.context.openKeys.includes(this.props.itemKey)),
         };
     }
 
@@ -159,6 +162,7 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
     };
 
     handleClick = (e: React.MouseEvent) => this.foundation.handleClick(e);
+    handleKeyPress = (e: React.KeyboardEvent) => this.foundation.handleKeyPress(e);
 
     render() {
         const {
@@ -249,15 +253,26 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
                 [`${clsPrefix}-collapsed`]: isCollapsed,
                 [`${clsPrefix}-disabled`]: disabled,
             });
+            const ariaProps = {
+                'aria-disabled': disabled,
+            };
+            if (isSubNav) {
+                const isOpen = this.adapter.getIsOpen();
+                ariaProps['aria-expanded'] = isOpen;
+            }
 
             itemDom = (
                 <li
+                    role="menuitem"
+                    tabIndex={-1}
+                    {...ariaProps}
                     style={style}
                     ref={this.setItemRef}
                     className={popoverItemCls}
                     onClick={this.handleClick}
                     onMouseEnter={onMouseEnter}
                     onMouseLeave={onMouseLeave}
+                    onKeyPress={this.handleKeyPress}
                 >
                     {itemChildren}
                 </li>

+ 13 - 1
packages/semi-ui/navigation/SubNav.tsx

@@ -115,6 +115,7 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
 
     titleRef: React.RefObject<HTMLDivElement>;
     itemRef: React.RefObject<HTMLLIElement>;
+    foundation: SubNavFoundation;
     constructor(props: SubNavProps) {
         super(props);
         this.state = {
@@ -171,6 +172,10 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
         this.foundation.handleClick(e && e.nativeEvent, this.titleRef && this.titleRef.current);
     };
 
+    handleKeyPress = (e: React.KeyboardEvent) => {
+        this.foundation.handleKeyPress(e && e.nativeEvent, this.titleRef && this.titleRef.current);
+    }
+
     handleDropdownVisible = (visible: boolean) => this.foundation.handleDropdownVisibleChange(visible);
 
     renderIcon(icon: React.ReactNode, pos: string, withTransition?: boolean, isToggleIcon = false, key: number | string = 0) {
@@ -239,7 +244,14 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
         }
 
         const titleDiv = (
-            <div ref={this.setTitleRef as any} className={titleCls} onClick={this.handleClick}>
+            <div
+                role="menuitem"
+                tabIndex={-1}
+                ref={this.setTitleRef as any} 
+                className={titleCls} 
+                onClick={this.handleClick}
+                onKeyPress={this.handleKeyPress}
+            >
                 <div className={`${prefixCls}-item-inner`}>
                     {placeholderIcons}
                     {this.context.toggleIconPosition === strings.TOGGLE_ICON_LEFT && this.renderIcon(toggleIconType, strings.ICON_POS_RIGHT, withTransition, true, 'key-toggle-position-left')}

+ 1 - 1
packages/semi-ui/navigation/index.tsx

@@ -408,7 +408,7 @@ class Nav extends BaseComponent<NavProps, NavState> {
                                 <div className={headerListOuterCls}>
                                     {headers}
                                     <div style={bodyStyle} className={`${prefixCls}-list-wrapper`}>
-                                        <ul className={`${prefixCls}-list`}>
+                                        <ul role="menu" aria-orientation={mode} className={`${prefixCls}-list`}>
                                             {this.adapter.getCache('itemElems')}
                                             {children}
                                         </ul>