Skip to content

Commit d45697e

Browse files
committed
feat: Support custom rendering
1 parent c1b586d commit d45697e

File tree

4 files changed

+94
-27
lines changed

4 files changed

+94
-27
lines changed

docs/examples/items.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,32 @@ import '../../assets/index.less';
66

77
export default () => (
88
<Menu
9+
itemsRender={(originNode, item) => {
10+
if (item.type === 'item') {
11+
return (
12+
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
13+
{originNode}
14+
</a>
15+
);
16+
}
17+
return originNode;
18+
}}
919
items={[
1020
{
1121
// MenuItem
1222
label: 'Top Menu Item',
1323
key: 'top',
1424
extra: '⌘B',
1525
},
26+
{
27+
key: 'ToOriginNode',
28+
type: 'item',
29+
label: 'Navigation Two',
30+
},
31+
{
32+
key: 'ToOriginNode1',
33+
label: 'SubMenu',
34+
},
1635
{
1736
// MenuGroup
1837
type: 'group',

src/Menu.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import type {
3030
PopupRender,
3131
} from './interface';
3232
import MenuItem from './MenuItem';
33-
import SubMenu, { SemanticName } from './SubMenu';
33+
import type { SemanticName } from './SubMenu';
34+
import SubMenu from './SubMenu';
3435
import { parseItems } from './utils/nodeUtil';
3536
import { warnItemProp } from './utils/warnUtil';
3637

@@ -61,6 +62,8 @@ export interface MenuProps
6162
/** @deprecated Please use `items` instead */
6263
children?: React.ReactNode;
6364

65+
itemsRender?: (originalNode: React.ReactNode, item: NonNullable<ItemType>) => React.ReactNode;
66+
6467
disabled?: boolean;
6568
/** @private Disable auto overflow. Pls note the prop name may refactor since we do not final decided. */
6669
disabledOverflow?: boolean;
@@ -242,6 +245,8 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => {
242245
_internalComponents,
243246

244247
popupRender,
248+
249+
itemsRender,
245250
...restProps
246251
} = props as LegacyMenuProps;
247252

@@ -250,10 +255,10 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => {
250255
measureChildList: React.ReactElement[],
251256
] = React.useMemo(
252257
() => [
253-
parseItems(children, items, EMPTY_LIST, _internalComponents, prefixCls),
254-
parseItems(children, items, EMPTY_LIST, {}, prefixCls),
258+
parseItems(children, items, EMPTY_LIST, _internalComponents, prefixCls, itemsRender),
259+
parseItems(children, items, EMPTY_LIST, {}, prefixCls, itemsRender),
255260
],
256-
[children, items, _internalComponents],
261+
[children, items, _internalComponents, prefixCls, itemsRender],
257262
);
258263

259264
const [mounted, setMounted] = React.useState(false);

src/utils/nodeUtil.tsx

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ function convertItemsToNodes(
1010
list: ItemType[],
1111
components: Required<Components>,
1212
prefixCls?: string,
13+
itemsRender?: (originNode: React.ReactNode, item: NonNullable<ItemType>) => React.ReactNode,
1314
) {
1415
const {
1516
item: MergedMenuItem,
@@ -24,38 +25,44 @@ function convertItemsToNodes(
2425
const { label, children, key, type, extra, ...restProps } = opt as any;
2526
const mergedKey = key ?? `tmp-${index}`;
2627

27-
// MenuItemGroup & SubMenuItem
28+
let originNode: React.ReactNode = null;
29+
30+
// MenuItemGroup & SubMenu
2831
if (children || type === 'group') {
2932
if (type === 'group') {
30-
// Group
31-
return (
33+
originNode = (
3234
<MergedMenuItemGroup key={mergedKey} {...restProps} title={label}>
33-
{convertItemsToNodes(children, components, prefixCls)}
35+
{convertItemsToNodes(children, components, prefixCls, itemsRender)}
3436
</MergedMenuItemGroup>
3537
);
38+
} else {
39+
originNode = (
40+
<MergedSubMenu key={mergedKey} {...restProps} title={label}>
41+
{convertItemsToNodes(children, components, prefixCls, itemsRender)}
42+
</MergedSubMenu>
43+
);
3644
}
37-
38-
// Sub Menu
39-
return (
40-
<MergedSubMenu key={mergedKey} {...restProps} title={label}>
41-
{convertItemsToNodes(children, components, prefixCls)}
42-
</MergedSubMenu>
45+
}
46+
// Divider
47+
else if (type === 'divider') {
48+
originNode = <MergedDivider key={mergedKey} {...restProps} />;
49+
}
50+
// MenuItem
51+
else {
52+
originNode = (
53+
<MergedMenuItem key={mergedKey} {...restProps} extra={extra}>
54+
{label}
55+
{(!!extra || extra === 0) && (
56+
<span className={`${prefixCls}-item-extra`}>{extra}</span>
57+
)}
58+
</MergedMenuItem>
4359
);
4460
}
4561

46-
// MenuItem & Divider
47-
if (type === 'divider') {
48-
return <MergedDivider key={mergedKey} {...restProps} />;
62+
if (typeof itemsRender === 'function') {
63+
return itemsRender(originNode, opt);
4964
}
50-
51-
return (
52-
<MergedMenuItem key={mergedKey} {...restProps} extra={extra}>
53-
{label}
54-
{(!!extra || extra === 0) && (
55-
<span className={`${prefixCls}-item-extra`}>{extra}</span>
56-
)}
57-
</MergedMenuItem>
58-
);
65+
return originNode;
5966
}
6067

6168
return null;
@@ -69,6 +76,7 @@ export function parseItems(
6976
keyPath: string[],
7077
components: Components,
7178
prefixCls?: string,
79+
itemsRender?: (originNode: React.ReactNode, item: NonNullable<ItemType>) => React.ReactNode,
7280
) {
7381
let childNodes = children;
7482

@@ -81,7 +89,7 @@ export function parseItems(
8189
};
8290

8391
if (items) {
84-
childNodes = convertItemsToNodes(items, mergedComponents, prefixCls);
92+
childNodes = convertItemsToNodes(items, mergedComponents, prefixCls, itemsRender);
8593
}
8694

8795
return parseChildren(childNodes, keyPath);

tests/MenuItem.spec.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,5 +228,40 @@ describe('MenuItem', () => {
228228

229229
expect(container.querySelector('li')).toMatchSnapshot();
230230
});
231+
232+
it('should wrap originNode with custom render', () => {
233+
const { container } = render(
234+
<Menu
235+
itemsRender={(originNode, item) => {
236+
if (item.type === 'item') {
237+
return (
238+
<a href="https://ant.design" target="_blank" rel="noopener noreferrer">
239+
{originNode}
240+
</a>
241+
);
242+
}
243+
return originNode;
244+
}}
245+
items={[
246+
{
247+
key: 'mail',
248+
type: 'item',
249+
label: 'Navigation One',
250+
},
251+
{
252+
key: 'app',
253+
label: 'Navigation Two',
254+
},
255+
{
256+
key: 'upload',
257+
label: 'Upload File',
258+
},
259+
]}
260+
/>,
261+
);
262+
263+
const link = container.querySelector('a');
264+
expect(link).toHaveAttribute('href', 'https://ant.design');
265+
});
231266
});
232267
});

0 commit comments

Comments
 (0)