Skip to content

Commit 6e0849a

Browse files
authored
refactor: rc-menu with accessibility (#385)
* start up * more classNames * add openKeys * chore: Back of onClick * feat: Motion back * active key support * pass active keys * fix: mode pass logic * support menu group * docs: Fix definition * disabled should not click * chore: Add loop check of path * chore: Back of nest selection * wrap item prop as warning * feat: support onSelect & onDeselect event * revert useless onSelect & support itemIcon * all rtl inlineIndent * hover event * popup data * back of defaultMotions * fix: Not lose active when hover disabled region * feat: use rc-overflow * fix: disabled should not trigger children active * chore: Cache inline open keys * fix cache logic * inline mode no need pass active on subMenu * chore: inline handle motion logic * fix: inline open logic * chore: Update comment * fix open logic * fix icon * update test * test: first test case * fix: clean up timer logic * test: More test case * Divider * test: update snapshot * test: Back of snapshot * test: clean up * test: fix dir * test: Update snapshot * test: Update snapshot * test: back of fragment test * test: role * test: selection & active * fix: open keys * test: More test case * feat: Native focus logic * move to correct key * fix aria link * menu item select * fix: no active for invisible content * trigger open * feat: Go to sub list * support nest level * feat: Auto focus link * inline use special operation * test: Update snapshot * test: back of key code * fix: Not focus if activeKey not changed * fix: active logic * chore: Use activeKey as start element instead * comment * test: keyboard test * test: defaultActiveFirst * test: more MenuItem test * test: fix rest props * test: menuItem test * test: Sub menu part test * test: ltr & rtl * test: Back of all test case * docs: Update docs * test: warning test * test: keycode coverage * test: click disabled coverage * test: selectable coverage * test: full coverage * chore: add data-* * chore: clean up * chore: Split context * chore: Use record context * render use another contexr * rm usePathData * fix arrow logic * patch match support overflow * update snapshot * test: Fix test case * test: Fix key test * test: Final test case * test: Coverage * fix: open logci * fix: accessibility for focus * chore: clean up
1 parent 127e17e commit 6e0849a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3591
-4058
lines changed

.prettierrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"semi": true,
44
"singleQuote": true,
55
"tabWidth": 2,
6-
"trailingComma": "all"
6+
"trailingComma": "all",
7+
"arrowParens": "avoid"
78
}

assets/menu.less

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
@menuPrefixCls: ~'rc-menu';
2+
3+
.@{menuPrefixCls} {
4+
&:focus-visible {
5+
box-shadow: 0 0 5px green;
6+
}
7+
8+
&-horizontal {
9+
display: flex;
10+
flex-wrap: nowrap;
11+
}
12+
13+
&-submenu {
14+
&-hidden {
15+
display: none;
16+
}
17+
}
18+
19+
&-overflow-item {
20+
flex: none;
21+
}
22+
}

docs/demo/debug.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## debug
2+
3+
<code src="../examples/debug.tsx">

docs/examples/antd-switch.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ const Demo = () => {
2828
<CommonMenu
2929
mode="inline"
3030
openKeys={openKeys}
31-
collapsedWidth={80}
3231
onOpenChange={keys => {
3332
console.error('Open Keys Changed:', keys);
3433
setOpenKey(keys);

docs/examples/antd.tsx

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,39 @@
11
/* eslint-disable no-console, react/require-default-props, no-param-reassign */
22

33
import React from 'react';
4-
import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu';
4+
import type { CSSMotionProps } from 'rc-motion';
5+
import Menu, { SubMenu, Item as MenuItem, Divider, MenuProps } from '../../src';
56
import '../../assets/index.less';
67

78
function handleClick(info) {
89
console.log(`clicked ${info.key}`);
910
console.log(info);
1011
}
1112

12-
const collapseNode = () => ({ height: 0 });
13-
const expandNode = node => ({ height: node.scrollHeight });
13+
const collapseNode = () => {
14+
return { height: 0 };
15+
};
16+
const expandNode = node => {
17+
return { height: node.scrollHeight };
18+
};
19+
20+
const horizontalMotion: CSSMotionProps = {
21+
motionName: 'rc-menu-open-slide-up',
22+
motionAppear: true,
23+
motionEnter: true,
24+
motionLeave: true,
25+
};
1426

15-
export const inlineMotion = {
27+
const verticalMotion: CSSMotionProps = {
28+
motionName: 'rc-menu-open-zoom',
29+
motionAppear: true,
30+
motionEnter: true,
31+
motionLeave: true,
32+
};
33+
34+
const inlineMotion: CSSMotionProps = {
1635
motionName: 'rc-menu-collapse',
36+
motionAppear: true,
1737
onAppearStart: collapseNode,
1838
onAppearActive: expandNode,
1939
onEnterStart: collapseNode,
@@ -22,6 +42,12 @@ export const inlineMotion = {
2242
onLeaveActive: collapseNode,
2343
};
2444

45+
const motionMap: Record<MenuProps['mode'], CSSMotionProps> = {
46+
horizontal: horizontalMotion,
47+
inline: inlineMotion,
48+
vertical: verticalMotion,
49+
};
50+
2551
const nestSubMenu = (
2652
<SubMenu
2753
title={<span className="submenu-title-wrapper">offset sub menu 2</span>}
@@ -95,8 +121,21 @@ const children2 = [
95121

96122
const customizeIndicator = <span>Add More Items</span>;
97123

98-
export class CommonMenu extends React.Component {
99-
state = {
124+
interface CommonMenuProps extends MenuProps {
125+
triggerSubMenuAction?: MenuProps['triggerSubMenuAction'];
126+
updateChildrenAndOverflowedIndicator?: boolean;
127+
}
128+
129+
interface CommonMenuState {
130+
children: React.ReactNode;
131+
overflowedIndicator: React.ReactNode;
132+
}
133+
134+
export class CommonMenu extends React.Component<
135+
CommonMenuProps,
136+
CommonMenuState
137+
> {
138+
state: CommonMenuState = {
100139
children: children1,
101140
overflowedIndicator: undefined,
102141
};
@@ -149,21 +188,23 @@ function Demo() {
149188
<CommonMenu
150189
mode="horizontal"
151190
// use openTransition for antd
152-
openAnimation="slide-up"
191+
defaultMotions={motionMap}
153192
/>
154193
);
155194

156195
const horizontalMenu2 = (
157196
<CommonMenu
158197
mode="horizontal"
159198
// use openTransition for antd
160-
openAnimation="slide-up"
199+
defaultMotions={motionMap}
161200
triggerSubMenuAction="click"
162201
updateChildrenAndOverflowedIndicator
163202
/>
164203
);
165204

166-
const verticalMenu = <CommonMenu mode="vertical" openAnimation="zoom" />;
205+
const verticalMenu = (
206+
<CommonMenu mode="vertical" defaultMotions={motionMap} />
207+
);
167208

168209
const inlineMenu = (
169210
<CommonMenu mode="inline" defaultOpenKeys={['1']} motion={inlineMotion} />

docs/examples/custom-icon.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint-disable no-console, no-param-reassign */
22
import * as React from 'react';
3-
import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu';
3+
import Menu, { SubMenu, Item as MenuItem, Divider } from '../../src';
44
import '../../assets/index.less';
55

6-
const getSvgIcon = (style = {}, text) => {
6+
const getSvgIcon = (style = {}, text?: React.ReactNode) => {
77
if (text) {
88
return <i style={style}>{text}</i>;
99
}

docs/examples/debug.tsx

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/* eslint no-console:0 */
2+
3+
import React from 'react';
4+
import type { CSSMotionProps } from 'rc-motion';
5+
import Menu, { ItemGroup as MenuItemGroup } from '../../src';
6+
import type { MenuProps } from '../../src';
7+
import '../../assets/index.less';
8+
import '../../assets/menu.less';
9+
import type { MenuInfo } from '@/interface';
10+
11+
const collapseNode = () => {
12+
return { height: 0 };
13+
};
14+
const expandNode = node => {
15+
return { height: node.scrollHeight };
16+
};
17+
18+
const horizontalMotion: CSSMotionProps = {
19+
motionName: 'rc-menu-open-slide-up',
20+
motionAppear: true,
21+
motionEnter: true,
22+
motionLeave: true,
23+
};
24+
25+
const verticalMotion: CSSMotionProps = {
26+
motionName: 'rc-menu-open-zoom',
27+
motionAppear: true,
28+
motionEnter: true,
29+
motionLeave: true,
30+
};
31+
32+
const inlineMotion: CSSMotionProps = {
33+
motionName: 'rc-menu-collapse',
34+
motionAppear: true,
35+
onAppearStart: collapseNode,
36+
onAppearActive: expandNode,
37+
onEnterStart: collapseNode,
38+
onEnterActive: expandNode,
39+
onLeaveStart: expandNode,
40+
onLeaveActive: collapseNode,
41+
};
42+
43+
const motionMap: Record<MenuProps['mode'], CSSMotionProps> = {
44+
horizontal: horizontalMotion,
45+
inline: inlineMotion,
46+
vertical: verticalMotion,
47+
};
48+
49+
export default () => {
50+
const [mode, setMode] = React.useState<MenuProps['mode']>('horizontal');
51+
const [narrow, setNarrow] = React.useState(false);
52+
const [inlineCollapsed, setInlineCollapsed] = React.useState(false);
53+
const [forceRender, setForceRender] = React.useState(false);
54+
const [openKeys, setOpenKeys] = React.useState<string[]>([]);
55+
56+
const onRootClick = (info: MenuInfo) => {
57+
console.log('Root Menu Item Click:', info);
58+
};
59+
60+
const onSubMenuClick = (info: MenuInfo) => {
61+
console.log('Sub Menu Item Click:', info);
62+
};
63+
64+
const onClick = (info: MenuInfo) => {
65+
console.log('Menu Item Click:', info);
66+
};
67+
68+
return (
69+
<>
70+
<div>
71+
<select value={mode} onChange={e => setMode(e.target.value as any)}>
72+
<option value="inline">Inline</option>
73+
<option value="vertical">Vertical</option>
74+
<option value="horizontal">Horizontal</option>
75+
</select>
76+
77+
{/* Narrow */}
78+
<button
79+
onClick={() => {
80+
setNarrow(!narrow);
81+
}}
82+
>
83+
Narrow: {String(narrow)}
84+
</button>
85+
86+
{/* InlineCollapsed */}
87+
<button
88+
onClick={() => {
89+
setInlineCollapsed(!inlineCollapsed);
90+
}}
91+
>
92+
Inline Collapsed: {String(inlineCollapsed)}
93+
</button>
94+
95+
{/* forceRender */}
96+
<button
97+
onClick={() => {
98+
setForceRender(!forceRender);
99+
}}
100+
>
101+
Force Render: {String(forceRender)}
102+
</button>
103+
</div>
104+
105+
<div style={{ width: narrow ? 350 : undefined }}>
106+
<Menu
107+
// direction="rtl"
108+
forceSubMenuRender={forceRender}
109+
mode={mode}
110+
style={{ width: mode === 'horizontal' ? undefined : 256 }}
111+
onClick={onRootClick}
112+
defaultMotions={motionMap}
113+
inlineCollapsed={inlineCollapsed}
114+
// openKeys={openKeys}
115+
onOpenChange={newOpenKeys => setOpenKeys(newOpenKeys)}
116+
>
117+
<Menu.Item key="mail">
118+
<a href="http://www.taobao.com">Navigation One</a>
119+
</Menu.Item>
120+
<Menu.Item key="next" onClick={onClick}>
121+
Next Item
122+
</Menu.Item>
123+
<Menu.SubMenu title="Sub Menu" key="sub" onClick={onSubMenuClick}>
124+
<Menu.Item key="sub1" onClick={onClick}>
125+
Sub Item 1
126+
</Menu.Item>
127+
<Menu.Item key="sub2">Sub Item 2</Menu.Item>
128+
129+
<Menu.SubMenu title="Nest Menu" key="nest">
130+
<MenuItemGroup title="group 1" key="grp1">
131+
<Menu.Item key="21">2</Menu.Item>
132+
<Menu.Item key="22">3</Menu.Item>
133+
</MenuItemGroup>
134+
<MenuItemGroup title="group 2" key="grp2">
135+
<Menu.Item key="31">4</Menu.Item>
136+
<Menu.Item key="32">5</Menu.Item>
137+
</MenuItemGroup>
138+
</Menu.SubMenu>
139+
</Menu.SubMenu>
140+
<Menu.Item key="disabled" disabled>
141+
Disabled Item
142+
</Menu.Item>
143+
144+
<Menu.SubMenu
145+
title="Disabled Sub Menu"
146+
key="disabled-sub"
147+
onClick={onSubMenuClick}
148+
disabled
149+
>
150+
<Menu.Item key="dis-sub1" onClick={onClick}>
151+
Disabled Sub Item 1
152+
</Menu.Item>
153+
</Menu.SubMenu>
154+
</Menu>
155+
</div>
156+
</>
157+
);
158+
};

0 commit comments

Comments
 (0)