Skip to content

Commit 3bdcfec

Browse files
Zyf665MadCcc
andauthored
feat: add popupRender prop to support custom popup content (#747)
* add popupRender for subMenu * rename file * modify test cases and optimize renderPopupContent method * modify test cases And popupContent code * fix type assertion for popupRender item prop * chore: fix tsc * chore: fix lint --------- Co-authored-by: MadCcc <[email protected]>
1 parent d37a782 commit 3bdcfec

25 files changed

+918
-876
lines changed

.eslintrc.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ module.exports = {
1212
'@typescript-eslint/no-explicit-any': 0,
1313
'jsx-a11y/label-has-associated-control': 0,
1414
'jsx-a11y/label-has-for': 0,
15-
'@typescript-eslint/no-empty-interface': "off",
15+
'@typescript-eslint/no-empty-interface': 0,
16+
'@typescript-eslint/consistent-indexed-object-style': 0,
17+
'@typescript-eslint/switch-exhaustiveness-check': 0,
18+
'@typescript-eslint/no-parameter-properties': 0,
19+
'@typescript-eslint/no-throw-literal': 0,
20+
'@typescript-eslint/type-annotation-spacing': 0,
21+
'@typescript-eslint/ban-types': 0,
1622
},
1723
};

docs/demo/customPopupRender.md

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

docs/examples/antd.tsx

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -56,29 +56,17 @@ const nestSubMenu = (
5656
>
5757
<MenuItem key="4-1">inner inner</MenuItem>
5858
<Divider />
59-
<SubMenu
60-
key="4-2"
61-
title={<span className="submenu-title-wrapper">sub menu 1</span>}
62-
>
63-
<SubMenu
64-
title={<span className="submenu-title-wrapper">sub 4-2-0</span>}
65-
key="4-2-0"
66-
>
59+
<SubMenu key="4-2" title={<span className="submenu-title-wrapper">sub menu 1</span>}>
60+
<SubMenu title={<span className="submenu-title-wrapper">sub 4-2-0</span>} key="4-2-0">
6761
<MenuItem key="4-2-0-1">inner inner</MenuItem>
6862
<MenuItem key="4-2-0-2">inner inner2</MenuItem>
6963
</SubMenu>
7064
<MenuItem key="4-2-1">inn</MenuItem>
71-
<SubMenu
72-
title={<span className="submenu-title-wrapper">sub menu 4</span>}
73-
key="4-2-2"
74-
>
65+
<SubMenu title={<span className="submenu-title-wrapper">sub menu 4</span>} key="4-2-2">
7566
<MenuItem key="4-2-2-1">inner inner</MenuItem>
7667
<MenuItem key="4-2-2-2">inner inner2</MenuItem>
7768
</SubMenu>
78-
<SubMenu
79-
title={<span className="submenu-title-wrapper">sub menu 3</span>}
80-
key="4-2-3"
81-
>
69+
<SubMenu title={<span className="submenu-title-wrapper">sub menu 3</span>} key="4-2-3">
8270
<MenuItem key="4-2-3-1">inner inner</MenuItem>
8371
<MenuItem key="4-2-3-2">inner inner2</MenuItem>
8472
</SubMenu>
@@ -91,10 +79,7 @@ function onOpenChange(value) {
9179
}
9280

9381
const children1 = [
94-
<SubMenu
95-
title={<span className="submenu-title-wrapper">sub menu</span>}
96-
key="1"
97-
>
82+
<SubMenu title={<span className="submenu-title-wrapper">sub menu</span>} key="1">
9883
<MenuItem key="1-1">0-1</MenuItem>
9984
<MenuItem key="1-2">0-2</MenuItem>
10085
</SubMenu>,
@@ -108,10 +93,7 @@ const children1 = [
10893
];
10994

11095
const children2 = [
111-
<SubMenu
112-
title={<span className="submenu-title-wrapper">sub menu</span>}
113-
key="1"
114-
>
96+
<SubMenu title={<span className="submenu-title-wrapper">sub menu</span>} key="1">
11597
<MenuItem key="1-1">0-1</MenuItem>
11698
<MenuItem key="1-2">0-2</MenuItem>
11799
</SubMenu>,
@@ -131,10 +113,7 @@ interface CommonMenuState {
131113
overflowedIndicator: React.ReactNode;
132114
}
133115

134-
export class CommonMenu extends React.Component<
135-
CommonMenuProps,
136-
CommonMenuState
137-
> {
116+
export class CommonMenu extends React.Component<CommonMenuProps, CommonMenuState> {
138117
state: CommonMenuState = {
139118
children: children1,
140119
overflowedIndicator: undefined,
@@ -148,8 +127,7 @@ export class CommonMenu extends React.Component<
148127

149128
toggleOverflowedIndicator = () => {
150129
this.setState(({ overflowedIndicator }) => ({
151-
overflowedIndicator:
152-
overflowedIndicator === undefined ? customizeIndicator : undefined,
130+
overflowedIndicator: overflowedIndicator === undefined ? customizeIndicator : undefined,
153131
}));
154132
};
155133

@@ -202,13 +180,9 @@ function Demo() {
202180
/>
203181
);
204182

205-
const verticalMenu = (
206-
<CommonMenu mode="vertical" defaultMotions={motionMap} />
207-
);
183+
const verticalMenu = <CommonMenu mode="vertical" defaultMotions={motionMap} />;
208184

209-
const inlineMenu = (
210-
<CommonMenu mode="inline" defaultOpenKeys={['1']} motion={inlineMotion} />
211-
);
185+
const inlineMenu = <CommonMenu mode="inline" defaultOpenKeys={['1']} motion={inlineMotion} />;
212186

213187
return (
214188
<div style={{ margin: 20 }}>

docs/examples/customPopupRender.less

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
.navigation-popup {
2+
padding: 24px;
3+
min-width: 480px;
4+
background: #fff;
5+
border-radius: 8px;
6+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
7+
8+
.navigation-grid {
9+
display: grid;
10+
grid-template-columns: repeat(2, 1fr);
11+
gap: 16px;
12+
}
13+
14+
.navigation-menu-item {
15+
padding: 0;
16+
margin: 0;
17+
18+
a {
19+
display: block;
20+
padding: 16px;
21+
text-decoration: none;
22+
color: inherit;
23+
border-radius: 6px;
24+
transition: all 0.3s;
25+
26+
h3 {
27+
margin: 0 0 8px;
28+
font-size: 16px;
29+
font-weight: 500;
30+
}
31+
32+
p {
33+
margin: 0;
34+
color: rgba(0, 0, 0, 0.45);
35+
font-size: 14px;
36+
line-height: 1.5;
37+
}
38+
39+
&:hover {
40+
background: rgba(0, 0, 0, 0.02);
41+
}
42+
}
43+
}
44+
}
45+
46+
.panel-popup {
47+
padding: 16px;
48+
min-width: 240px;
49+
background: #fff;
50+
border-radius: 8px;
51+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
52+
53+
.panel-header {
54+
padding: 0 8px 12px;
55+
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
56+
margin-bottom: 8px;
57+
58+
h4 {
59+
margin: 0;
60+
font-size: 14px;
61+
color: rgba(0, 0, 0, 0.45);
62+
}
63+
}
64+
65+
.panel-content {
66+
.rc-menu-item {
67+
padding: 8px 12px;
68+
margin: 0;
69+
border-radius: 4px;
70+
71+
&:hover {
72+
background: rgba(0, 0, 0, 0.02);
73+
}
74+
}
75+
}
76+
}

docs/examples/customPopupRender.tsx

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import React from 'react';
2+
import Menu, { SubMenu, Item as MenuItem } from '../../src';
3+
import type { ReactElement } from 'react';
4+
import './customPopupRender.less';
5+
6+
const NavigationDemo = () => {
7+
const menuItems = [
8+
{
9+
key: 'home',
10+
label: 'Home',
11+
},
12+
{
13+
key: 'features',
14+
label: 'Features',
15+
children: [
16+
{
17+
key: 'getting-started',
18+
label: (
19+
<a href="/docs">
20+
<h3>Getting Started</h3>
21+
<p>Quick start guide and learn the basics.</p>
22+
</a>
23+
),
24+
},
25+
{
26+
key: 'components',
27+
label: (
28+
<a href="/components">
29+
<h3>Components</h3>
30+
<p>Explore our component library.</p>
31+
</a>
32+
),
33+
},
34+
{
35+
key: 'templates',
36+
label: (
37+
<a href="/templates">
38+
<h3>Templates</h3>
39+
<p>Ready-to-use template designs.</p>
40+
</a>
41+
),
42+
},
43+
],
44+
},
45+
{
46+
key: 'resources',
47+
label: 'Resources',
48+
children: [
49+
{
50+
key: 'blog',
51+
label: (
52+
<a href="/blog">
53+
<h3>Blog</h3>
54+
<p>Latest updates and articles.</p>
55+
</a>
56+
),
57+
},
58+
{
59+
key: 'community',
60+
label: (
61+
<a href="/community">
62+
<h3>Community</h3>
63+
<p>Join our developer community.</p>
64+
</a>
65+
),
66+
},
67+
],
68+
},
69+
];
70+
const popupRender = (node: ReactElement) => (
71+
<div className="navigation-popup">
72+
<div className="navigation-grid">
73+
{React.Children.map(node.props.children.props.children, child => (
74+
<div className="navigation-item">
75+
{React.cloneElement(child, {
76+
className: `${child.props.className || ''} navigation-menu-item`,
77+
})}
78+
</div>
79+
))}
80+
</div>
81+
</div>
82+
);
83+
84+
return <Menu mode="horizontal" popupRender={popupRender} items={menuItems} />;
85+
};
86+
87+
const MixedPanelDemo = () => {
88+
const totalPopupRender = (node: ReactElement, info: { item: any; keys: string[] }) => {
89+
const isSecondLevel = info.keys.length == 2;
90+
if (isSecondLevel) {
91+
return (
92+
<div className="navigation-popup">
93+
<div className="navigation-grid">
94+
{React.Children.map(node.props.children.props.children, child => (
95+
<div className="navigation-item">
96+
{React.cloneElement(child, {
97+
className: `${child.props.className || ''} navigation-menu-item`,
98+
})}
99+
</div>
100+
))}
101+
</div>
102+
</div>
103+
);
104+
}
105+
return node;
106+
};
107+
const singlePopupRender = (node: ReactElement, info: { item: any; keys: string[] }) => {
108+
const isSecondLevel = info.keys.length == 2;
109+
if (isSecondLevel) {
110+
return (
111+
<div className="panel-popup">
112+
<div className="panel-header">
113+
<h4>{info.item.title}</h4>
114+
</div>
115+
<div className="panel-content">{node}</div>
116+
</div>
117+
);
118+
}
119+
return node;
120+
};
121+
return (
122+
<Menu mode="horizontal" popupRender={totalPopupRender}>
123+
<MenuItem key="home">Home</MenuItem>
124+
<SubMenu key="products" title="Products">
125+
<MenuItem key="product-a">Product A</MenuItem>
126+
<MenuItem key="product-b">Product B</MenuItem>
127+
<SubMenu key="more-products" title="More Products">
128+
<MenuItem key="product-c">
129+
<a href="/product-c">
130+
<h3>Product C</h3>
131+
<p>Description for Product C.</p>
132+
</a>
133+
</MenuItem>
134+
<MenuItem key="product-d">
135+
<a href="/product-d">
136+
<h3>Product D</h3>
137+
<p>Description for Product D.</p>
138+
</a>
139+
</MenuItem>
140+
</SubMenu>
141+
</SubMenu>
142+
<SubMenu key="solutions" title="Solutions">
143+
<MenuItem key="enterprise">Enterprise</MenuItem>
144+
<MenuItem key="personal">Personal</MenuItem>
145+
<SubMenu key="industry" title="Industry" popupRender={singlePopupRender}>
146+
<MenuItem key="healthcare">Healthcare</MenuItem>
147+
<MenuItem key="education">Education</MenuItem>
148+
</SubMenu>
149+
</SubMenu>
150+
</Menu>
151+
);
152+
};
153+
154+
const Demo = () => {
155+
return (
156+
<div>
157+
<h3>NavigationDemo</h3>
158+
<NavigationDemo />
159+
<h3>MixedPanelDemo</h3>
160+
<MixedPanelDemo />
161+
</div>
162+
);
163+
};
164+
export default Demo;

docs/examples/inlineCollapsed.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@ const App = () => {
77
return (
88
<>
99
<label>
10-
<input
11-
type="checkbox"
12-
checked={collapsed}
13-
onChange={e => setCollapsed(e.target.checked)}
14-
/>
10+
<input type="checkbox" checked={collapsed} onChange={e => setCollapsed(e.target.checked)} />
1511
inlineCollapsed: {collapsed.toString()}
1612
</label>
1713
<Menu

docs/examples/menuItemGroup.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
/* eslint no-console:0 */
22

33
import React from 'react';
4-
import Menu, {
5-
Item as MenuItem,
6-
ItemGroup as MenuItemGroup,
7-
} from '@rc-component/menu';
4+
import Menu, { Item as MenuItem, ItemGroup as MenuItemGroup } from '@rc-component/menu';
85

96
import '../../assets/index.less';
107

118
export default () => (
129
<div>
1310
<h2>menu item group</h2>
14-
<Menu
15-
style={{ margin: 20, width: 300 }}
16-
onClick={() => console.log('click')}
17-
>
11+
<Menu style={{ margin: 20, width: 300 }} onClick={() => console.log('click')}>
1812
<MenuItemGroup title="group 1" key="2">
1913
<MenuItem key="21">2</MenuItem>
2014
<MenuItem key="22">3</MenuItem>

0 commit comments

Comments
 (0)