Skip to content

Commit

Permalink
refactor: enhance tab navigation and accessibility features (#763)
Browse files Browse the repository at this point in the history
* refactor: enhance tab navigation and accessibility features

* chore: adjust some logic

* chore: code clean

* fix: enhance accessibility by adding role="tab" to outer div

* test: add test case for keyboard operation

* fix: fix some test case

* chore: adjust field name

* refactor: remove unnecessary global event listener

* chore: code fix

* feat: enhance tab navigation with Backspace support

* feat: improve delete keyboard operation

* chore: adjust some logic

* feat: improve delete logic

* chore: adjust some logic

* fix: add delete logic

* test: add test case

* chore: add a11y attr

* test: update snapshot

* feat: add a11y support for screen reader

* chore: adjust some logic

* chore: adjust field names
  • Loading branch information
aojunhao123 authored Dec 17, 2024
1 parent 0538f10 commit c51d83c
Show file tree
Hide file tree
Showing 8 changed files with 491 additions and 46 deletions.
46 changes: 26 additions & 20 deletions assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,32 @@
@effect-duration: 0.3s;

.@{tabs-prefix-cls} {
border: 1px solid gray;
font-size: 14px;
overflow: hidden;
font-size: 14px;
border: 1px solid gray;

// ========================== Navigation ==========================
&-nav {
position: relative;
display: flex;
flex: none;
position: relative;

&-measure,
&-wrap {
transform: translate(0);
position: relative;
display: inline-block;
display: flex;
flex: auto;
white-space: nowrap;
overflow: hidden;
display: flex;
white-space: nowrap;
transform: translate(0);

&-ping-left::before,
&-ping-right::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
content: '';
}
&-ping-left::before {
left: 0;
Expand All @@ -48,10 +48,10 @@

&-ping-top::before,
&-ping-bottom::after {
content: '';
position: absolute;
left: 0;
right: 0;
left: 0;
content: '';
}
&-ping-top::before {
top: 0;
Expand All @@ -64,8 +64,8 @@
}

&-list {
display: flex;
position: relative;
display: flex;
transition: transform 0.3s;
}

Expand All @@ -81,36 +81,39 @@
}

&-more {
border: 1px solid blue;
background: rgba(255, 0, 0, 0.1);
border: 1px solid blue;
}
&-add {
border: 1px solid green;
background: rgba(0, 255, 0, 0.1);
border: 1px solid green;
}
}

&-tab {
border: 0;
position: relative;
display: flex;
align-items: center;
margin: 0;
font-weight: lighter;
font-size: 20px;
background: rgba(255, 255, 255, 0.5);
margin: 0;
display: flex;
border: 0;
outline: none;
cursor: pointer;
position: relative;
font-weight: lighter;
align-items: center;

&-btn,
&-remove {
border: 0;
background: transparent;
border: 0;
}

&-btn {
font-weight: inherit;
line-height: 32px;
&:focus {
outline: none;
}
}

&-remove {
Expand All @@ -120,9 +123,12 @@
}

&-active {
// padding-left: 30px;
font-weight: bolder;
}

&-focus {
outline: 1px auto #1677ff;
}
}

&-ink-bar {
Expand Down
26 changes: 25 additions & 1 deletion docs/examples/basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,31 @@ export default () => {
disabled: true,
icon: <span>🐼</span>,
},
{
label: 'Yo',
key: 'yo',
children: 'Yo!',
icon: <span>👋</span>,
},
]);
const [direction, setDirection] = React.useState<'ltr' | 'rtl'>('ltr');

if (destroy) {
return null;
}

const onTabClick = (key: string) => {
console.log('key', key);
};

return (
<React.StrictMode>
<Tabs tabBarExtraContent="extra" items={items} />
<Tabs
tabBarExtraContent="extra"
onTabClick={onTabClick}
direction={direction}
items={items}
/>
<button
type="button"
onClick={() => {
Expand All @@ -56,6 +72,14 @@ export default () => {
>
Destroy
</button>
<button
type="button"
onClick={() => {
setDirection(direction === 'ltr' ? 'rtl' : 'ltr');
}}
>
{direction === 'ltr' ? 'rtl' : 'ltr'}
</button>
</React.StrictMode>
);
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@rc-component/trigger": "^2.0.0",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/classnames": "^2.2.10",
"@types/enzyme": "^3.10.5",
"@types/jest": "^29.4.0",
Expand Down
48 changes: 38 additions & 10 deletions src/TabNavList/TabNode.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import classNames from 'classnames';
import KeyCode from 'rc-util/lib/KeyCode';
import * as React from 'react';
import type { EditableConfig, Tab } from '../interface';
import { genDataNodeKey, getRemovable } from '../util';
Expand All @@ -9,14 +8,21 @@ export interface TabNodeProps {
prefixCls: string;
tab: Tab;
active: boolean;
focus: boolean;
closable?: boolean;
editable?: EditableConfig;
onClick?: (e: React.MouseEvent | React.KeyboardEvent) => void;
onResize?: (width: number, height: number, left: number, top: number) => void;
renderWrapper?: (node: React.ReactElement) => React.ReactElement;
removeAriaLabel?: string;
tabCount: number;
currentPosition: number;
removeIcon?: React.ReactNode;
onKeyDown: React.KeyboardEventHandler;
onMouseDown: React.MouseEventHandler;
onMouseUp: React.MouseEventHandler;
onFocus: React.FocusEventHandler;
onBlur: React.FocusEventHandler;
style?: React.CSSProperties;
}

Expand All @@ -25,14 +31,21 @@ const TabNode: React.FC<TabNodeProps> = props => {
prefixCls,
id,
active,
focus,
tab: { key, label, disabled, closeIcon, icon },
closable,
renderWrapper,
removeAriaLabel,
editable,
onClick,
onFocus,
onBlur,
onKeyDown,
onMouseDown,
onMouseUp,
style,
tabCount,
currentPosition,
} = props;
const tabPrefix = `${prefixCls}-tab`;

Expand All @@ -56,40 +69,55 @@ const TabNode: React.FC<TabNodeProps> = props => {
[label, icon],
);

const btnRef = React.useRef<HTMLDivElement>(null);

React.useEffect(() => {
if (focus && btnRef.current) {
btnRef.current.focus();
}
}, [focus]);

const node: React.ReactElement = (
<div
key={key}
// ref={ref}
data-node-key={genDataNodeKey(key)}
className={classNames(tabPrefix, {
[`${tabPrefix}-with-remove`]: removable,
[`${tabPrefix}-active`]: active,
[`${tabPrefix}-disabled`]: disabled,
[`${tabPrefix}-focus`]: focus,
})}
style={style}
onClick={onInternalClick}
>
{/* Primary Tab Button */}
<div
ref={btnRef}
role="tab"
aria-selected={active}
id={id && `${id}-tab-${key}`}
className={`${tabPrefix}-btn`}
aria-controls={id && `${id}-panel-${key}`}
aria-disabled={disabled}
tabIndex={disabled ? null : 0}
tabIndex={disabled ? null : active ? 0 : -1}
onClick={e => {
e.stopPropagation();
onInternalClick(e);
}}
onKeyDown={e => {
if ([KeyCode.SPACE, KeyCode.ENTER].includes(e.which)) {
e.preventDefault();
onInternalClick(e);
}
}}
onKeyDown={onKeyDown}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onFocus={onFocus}
onBlur={onBlur}
>
{focus && (
<div
aria-live="polite"
style={{ width: 0, height: 0, position: 'absolute', overflow: 'hidden', opacity: 0 }}
>
{`Tab ${currentPosition} of ${tabCount}`}
</div>
)}
{icon && <span className={`${tabPrefix}-icon`}>{icon}</span>}
{label && labelNode}
</div>
Expand All @@ -99,7 +127,7 @@ const TabNode: React.FC<TabNodeProps> = props => {
<button
type="button"
aria-label={removeAriaLabel || 'remove'}
tabIndex={0}
tabIndex={active ? 0 : -1}
className={`${tabPrefix}-remove`}
onClick={e => {
e.stopPropagation();
Expand Down
Loading

0 comments on commit c51d83c

Please sign in to comment.