Skip to content

Commit 281afbb

Browse files
committed
Support item custom rendering
1 parent 743803f commit 281afbb

File tree

8 files changed

+71
-48
lines changed

8 files changed

+71
-48
lines changed

src/Divider.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,25 @@ import type { MenuDividerType } from './interface';
66

77
export type DividerProps = Omit<MenuDividerType, 'type'>;
88

9-
export default function Divider({ className, style }: DividerProps) {
9+
export default function Divider({ className, style, itemRender }: DividerProps) {
1010
const { prefixCls } = React.useContext(MenuContext);
1111
const measure = useMeasure();
1212

1313
if (measure) {
1414
return null;
1515
}
1616

17-
return (
17+
const renderNode = (
1818
<li
1919
role="separator"
2020
className={classNames(`${prefixCls}-item-divider`, className)}
2121
style={style}
2222
/>
2323
);
24+
25+
if (typeof itemRender === 'function') {
26+
return itemRender(renderNode);
27+
}
28+
29+
return renderNode;
2430
}

src/Menu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ export interface MenuProps
6262
/** @deprecated Please use `items` instead */
6363
children?: React.ReactNode;
6464

65-
itemRender?: (originalNode: React.ReactNode, item?: NonNullable<ItemType>) => React.ReactElement;
66-
6765
disabled?: boolean;
6866
/** @private Disable auto overflow. Pls note the prop name may refactor since we do not final decided. */
6967
disabledOverflow?: boolean;
@@ -160,6 +158,8 @@ export interface MenuProps
160158
_internalComponents?: Components;
161159

162160
popupRender?: PopupRender;
161+
162+
itemRender?: (originNode: React.ReactNode, item: NonNullable<ItemType>) => React.ReactNode;
163163
}
164164

165165
interface LegacyMenuProps extends MenuProps {

src/MenuItem.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<
8989

9090
onFocus,
9191

92+
itemRender,
93+
9294
...restProps
9395
} = props;
9496

@@ -238,6 +240,10 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<
238240
</LegacyMenuItem>
239241
);
240242

243+
if (typeof itemRender === 'function') {
244+
renderNode = itemRender(renderNode);
245+
}
246+
241247
if (_internalRenderMenuItem) {
242248
renderNode = _internalRenderMenuItem(renderNode, props, { selected });
243249
}

src/MenuItemGroup.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const InternalMenuItemGroup = React.forwardRef<HTMLLIElement, MenuItemGroupProps
5252
});
5353

5454
const MenuItemGroup = React.forwardRef<HTMLLIElement, MenuItemGroupProps>((props, ref) => {
55-
const { eventKey, children } = props;
55+
const { eventKey, children, itemRender } = props;
5656
const connectedKeyPath = useFullPath(eventKey);
5757
const childList: React.ReactElement[] = parseChildren(children, connectedKeyPath);
5858

@@ -63,7 +63,7 @@ const MenuItemGroup = React.forwardRef<HTMLLIElement, MenuItemGroupProps>((props
6363

6464
return (
6565
<InternalMenuItemGroup ref={ref} {...omit(props, ['warnKey'])}>
66-
{childList}
66+
{typeof itemRender === 'function' ? itemRender(childList) : childList}
6767
</InternalMenuItemGroup>
6868
);
6969
});

src/SubMenu/index.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ const InternalSubMenu = React.forwardRef<HTMLLIElement, SubMenuProps>((props, re
384384
});
385385

386386
const SubMenu = React.forwardRef<HTMLLIElement, SubMenuProps>((props, ref) => {
387-
const { eventKey, children } = props;
387+
const { eventKey, children, itemRender } = props;
388388

389389
const connectedKeyPath = useFullPath(eventKey);
390390
const childList: React.ReactElement[] = parseChildren(children, connectedKeyPath);
@@ -406,12 +406,15 @@ const SubMenu = React.forwardRef<HTMLLIElement, SubMenuProps>((props, ref) => {
406406
let renderNode: React.ReactNode;
407407

408408
// ======================== Render ========================
409+
410+
const childListNode = typeof itemRender === 'function' ? itemRender(childList) : childList;
411+
409412
if (measure) {
410-
renderNode = childList;
413+
renderNode = childListNode;
411414
} else {
412415
renderNode = (
413416
<InternalSubMenu ref={ref} {...props}>
414-
{childList}
417+
{childListNode}
415418
</InternalSubMenu>
416419
);
417420
}

src/interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface ItemSharedProps {
66
ref?: React.Ref<HTMLLIElement | null>;
77
style?: React.CSSProperties;
88
className?: string;
9+
itemRender?: (originNode: React.ReactNode) => React.ReactNode;
910
}
1011

1112
export interface SubMenuType extends ItemSharedProps {

src/utils/commonUtil.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import toArray from '@rc-component/util/lib/Children/toArray';
22
import * as React from 'react';
33

4-
export function parseChildren(
5-
children: React.ReactNode | undefined,
6-
keyPath: string[],
7-
itemRender?: (originNode: React.ReactNode) => React.ReactElement,
8-
) {
4+
export function parseChildren(children: React.ReactNode | undefined, keyPath: string[]) {
95
return toArray(children).map((child, index) => {
106
if (React.isValidElement(child)) {
117
const { key } = child;
@@ -17,14 +13,15 @@ export function parseChildren(
1713
eventKey = `tmp_key-${[...keyPath, index].join('-')}`;
1814
}
1915

20-
const cloneProps = { key: eventKey, eventKey } as any;
16+
const cloneProps = {
17+
key: eventKey,
18+
eventKey,
19+
} as any;
2120

2221
if (process.env.NODE_ENV !== 'production' && emptyKey) {
2322
cloneProps.warnKey = true;
2423
}
25-
if (typeof itemRender === 'function') {
26-
return itemRender(React.cloneElement(child, cloneProps));
27-
}
24+
2825
return React.cloneElement(child, cloneProps);
2926
}
3027

src/utils/nodeUtil.tsx

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,48 +21,58 @@ function convertItemsToNodes(
2121

2222
return (list || [])
2323
.map((opt, index) => {
24+
const renderNodeWrapper = node => {
25+
return typeof itemRender === 'function' ? itemRender(node, opt as any) : node;
26+
};
2427
if (opt && typeof opt === 'object') {
2528
const { label, children, key, type, extra, ...restProps } = opt as any;
2629
const mergedKey = key ?? `tmp-${index}`;
2730

28-
let originNode: React.ReactNode = null;
29-
30-
// MenuItemGroup & SubMenu
31+
// MenuItemGroup & SubMenuItem
3132
if (children || type === 'group') {
3233
if (type === 'group') {
33-
originNode = (
34-
<MergedMenuItemGroup key={mergedKey} {...restProps} title={label}>
35-
{convertItemsToNodes(children, components, prefixCls, itemRender)}
34+
// Group
35+
return (
36+
<MergedMenuItemGroup
37+
key={mergedKey}
38+
{...restProps}
39+
itemRender={renderNodeWrapper}
40+
title={label}
41+
>
42+
{convertItemsToNodes(children, components, prefixCls)}
3643
</MergedMenuItemGroup>
3744
);
38-
} else {
39-
originNode = (
40-
<MergedSubMenu key={mergedKey} {...restProps} title={label}>
41-
{convertItemsToNodes(children, components, prefixCls, itemRender)}
42-
</MergedSubMenu>
43-
);
4445
}
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>
46+
47+
// Sub Menu
48+
return (
49+
<MergedSubMenu
50+
key={mergedKey}
51+
{...restProps}
52+
itemRender={renderNodeWrapper}
53+
title={label}
54+
>
55+
{convertItemsToNodes(children, components, prefixCls)}
56+
</MergedSubMenu>
5957
);
6058
}
6159

62-
if (typeof itemRender === 'function') {
63-
return itemRender(originNode, opt);
60+
// MenuItem & Divider
61+
if (type === 'divider') {
62+
return <MergedDivider key={mergedKey} {...restProps} itemRender={renderNodeWrapper} />;
6463
}
65-
return originNode;
64+
65+
return (
66+
<MergedMenuItem
67+
key={mergedKey}
68+
{...restProps}
69+
extra={extra}
70+
itemRender={renderNodeWrapper}
71+
>
72+
{label}
73+
{(!!extra || extra === 0) && <span className={`${prefixCls}-item-extra`}>{extra}</span>}
74+
</MergedMenuItem>
75+
);
6676
}
6777

6878
return null;
@@ -92,5 +102,5 @@ export function parseItems(
92102
childNodes = convertItemsToNodes(items, mergedComponents, prefixCls, itemRender);
93103
}
94104

95-
return parseChildren(childNodes, keyPath, itemRender);
105+
return parseChildren(childNodes, keyPath);
96106
}

0 commit comments

Comments
 (0)