Selaa lähdekoodia

feat: add tabs a11y

linyan 3 vuotta sitten
vanhempi
sitoutus
e9f6fbceac

+ 3 - 1
packages/semi-foundation/tabs/foundation.ts

@@ -1,5 +1,6 @@
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
-import { noop } from 'lodash';
+import { get, noop } from 'lodash';
+import keyCode, { ENTER_KEY } from './../utils/keyCode';
 
 export interface TabsAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
     collectPane: () => void;
@@ -72,6 +73,7 @@ class TabsFoundation<P = Record<string, any>, S = Record<string, any>> extends B
     handleTabDelete(tabKey: string): void {
         this._adapter.notifyTabDelete(tabKey);
     }
+
 }
 
 export default TabsFoundation;

+ 85 - 0
packages/semi-foundation/tabs/itemFoundation.ts

@@ -0,0 +1,85 @@
+import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import { get } from 'lodash';
+import keyCode, { ENTER_KEY } from './../utils/keyCode';
+
+export interface TabsItemAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
+    notifyClick: (item: any, e: any) => void;
+}
+
+export default class TabsItemFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<TabsItemAdapter<P, S>, P, S> {
+
+    constructor(adapter: TabsItemAdapter<P, S>) {
+        super({ ...adapter });
+    }
+
+    // handleNewActiveKey(activeKey: string): void {
+    //     const { activeKey: stateActiveKey } = this.getStates();
+    //     if (stateActiveKey !== activeKey) {
+    //         this._adapter.setNewActiveKey(activeKey);
+    //     }
+    // }
+
+    handleKeyDown(event: any): void {
+        console.log("key", event);
+        const key = get(event, 'key');
+        console.log("key", key, key === keyCode.LEFT);
+
+        switch (key) {
+            case "ArrowLeft":
+            case 'ArrowRight':
+                this.determineOrientation(event);
+                break;
+            // case keys.delete:
+            //     determineDeletable(event);
+            //     break;
+            case ENTER_KEY:
+            // case keys.space:
+                // activateTab(event.target);
+                // break;
+        }
+    }
+
+    determineOrientation(event: any): void {
+        const { tabPosition } = this.getProps();
+        const key = get(event, 'key');
+        const isVertical = tabPosition === 'left';
+        console.log("key", key);
+
+        if (isVertical) {
+            if (key === keyCode.UP || key === keyCode.DOWN) {
+                event.preventDefault();
+                this.switchTabOnArrowPress(event);
+            }
+        } else {
+            if (key === keyCode.LEFT || key === keyCode.RIGHT) {
+                this.switchTabOnArrowPress(event);
+            }
+        }
+    }
+
+    switchTabOnArrowPress(event: any): void {
+        const key = get(event, 'key');
+        const tabs = document.querySelectorAll('[role="tab"]');
+
+        const direction = {
+            37: -1,
+            38: -1,
+            39: 1,
+            40: 1,
+        };
+
+        if (direction[key]) {
+            const target = event.target;
+            if (target.index !== undefined) {
+                if (tabs[target.index + direction[key]]) {
+                    // tabs[target.index + direction[key]].focus();
+                } else if (key === keyCode.LEFT || key === keyCode.UP) {
+                    // focusLastTab();
+                } else if (key === keyCode.RIGHT || key == keyCode.DOWN) {
+                    // focusFirstTab();
+                }
+            }
+        }
+    }
+
+}

+ 23 - 1
packages/semi-ui/tabs/TabBar.tsx

@@ -10,6 +10,9 @@ import { TabBarProps, PlainTab } from './interface';
 import { isEmpty } from 'lodash';
 import { IconChevronRight, IconChevronLeft, IconClose } from '@douyinfe/semi-icons';
 import { getUuidv4 } from '@douyinfe/semi-foundation/utils/uuid';
+import TabsItemFoundation, { TabsItemAdapter } from '@douyinfe/semi-foundation/tabs/itemFoundation';
+import BaseComponent from '../_base/baseComponent';
+
 
 export interface TabBarState {
     endInd: number;
@@ -22,7 +25,7 @@ export interface OverflowItem extends PlainTab {
     active: boolean;
 }
 
-class TabBar extends React.Component<TabBarProps, TabBarState> {
+class TabBar extends BaseComponent<TabBarProps, TabBarState> {
     static propTypes = {
         activeKey: PropTypes.string,
         className: PropTypes.string,
@@ -39,6 +42,17 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
     };
 
     uuid: string;
+    foundation: TabsItemFoundation;
+    // foundation: TabsFoundation;
+    
+    get adapter(): TabsItemAdapter<TabBarProps, TabBarState>  {
+        return {
+            ...super.adapter,
+            notifyClick: (activeKey: string, event: MouseEvent<HTMLDivElement>): void => {
+                this.props.onTabClick(activeKey, event);
+            },
+        };
+    }
 
     constructor(props: TabBarProps) {
         super(props);
@@ -47,6 +61,7 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
             rePosKey: 0,
             startInd: 0,
         };
+        this.foundation = new TabsItemFoundation(this.adapter);
         this.uuid = getUuidv4();
     }
 
@@ -88,6 +103,10 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
         }
     };
 
+    handleKeyDown = (event: React.KeyboardEvent) => {
+        this.foundation.handleKeyDown(event);
+    }
+
     renderTabItem = (panel: PlainTab): ReactNode => {
         const { size, type, deleteTabItem } = this.props;
         const panelIcon = panel.icon ? this.renderIcon(panel.icon) : null;
@@ -113,6 +132,9 @@ class TabBar extends React.Component<TabBarProps, TabBarState> {
                 aria-controls={`semiTabPanel${key}`}
                 aria-disabled={panel.disabled ? 'true' : 'false'}
                 aria-selected={isSelected ? 'true' : 'false'}
+                // tabIndex={isSelected ? 0 : -1}
+                tabIndex={0}
+                onKeyDown={this.handleKeyDown}
                 {...events}
                 className={className}
                 key={this._getItemKey(key)}

+ 1 - 0
packages/semi-ui/tabs/TabPane.tsx

@@ -91,6 +91,7 @@ class TabPane extends PureComponent<TabPaneProps> {
             <div
                 ref={this.ref}
                 role="tabpanel"
+                tabIndex={0}
                 id={`semiTabPanel${itemKey}`}
                 aria-labelledby={`semiTab${itemKey}`}
                 className={classNames}