Skip to content

Commit 838027e

Browse files
EmilyyyLiu刘欢claudegemini-code-assist[bot]
authored
feat: add itemData to onSelect and onClick callback (#864)
* feat: add info.item to onSelect and onClick callback - Add info.item to MenuInfo interface to expose menu item config (label, icon, disabled, extra, etc.) - Support both items config and children mode - Add unit tests for info.item in onSelect and onClick - Update README documentation Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat: update MenuItem type to MenuItemInfo for onSelect and onClick callbacks * Update src/utils/nodeUtil.tsx Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * feat: update MenuItemInfo type and refactor info item handling in nodeUtil * fix: update key in info.item to use mergedKey in convertItemsToNodes * feat: update event payload to use itemData instead of info.item in MenuItem * feat: update MenuItem test to include itemData handling in onSelect and onClick * chore: trigger CI re-run --------- Co-authored-by: 刘欢 <lh01217311@antgroup.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 0d0f407 commit 838027e

5 files changed

Lines changed: 112 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,13 @@ ReactDOM.render(
135135
</tr>
136136
<tr>
137137
<td>onSelect</td>
138-
<td>function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[]})</td>
138+
<td>function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[],ItemData:ItemData})</td>
139139
<th></th>
140140
<td>called when select a menu item</td>
141141
</tr>
142142
<tr>
143143
<td>onClick</td>
144-
<td>function({key:String, item:ReactComponent, domEvent:Event, keyPath: String[]})</td>
144+
<td>function({key:String, item:ReactComponent, domEvent:Event, keyPath: String[], ItemData:ItemData})</td>
145145
<th></th>
146146
<td>called when click a menu item</td>
147147
</tr>

src/MenuItem.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import PrivateContext from './context/PrivateContext';
99
import useActive from './hooks/useActive';
1010
import useDirectionStyle from './hooks/useDirectionStyle';
1111
import Icon from './Icon';
12-
import type { MenuInfo, MenuItemType } from './interface';
12+
import type { MenuInfo, ItemData, MenuItemType } from './interface';
1313
import { warnItemProp } from './utils/warnUtil';
1414

1515
export interface MenuItemProps
@@ -29,6 +29,9 @@ export interface MenuItemProps
2929

3030
/** @deprecated No place to use this. Should remove */
3131
attribute?: Record<string, string>;
32+
33+
/** @private Origin item config from items prop */
34+
itemData?: ItemData;
3235
}
3336

3437
// Since Menu event provide the `info.item` which point to the MenuItem node instance.
@@ -74,6 +77,7 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<
7477
disabled,
7578
itemIcon,
7679
children,
80+
itemData: propsItemData,
7781

7882
// Aria
7983
role,
@@ -130,12 +134,21 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<
130134
const getEventInfo = (
131135
e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
132136
): MenuInfo => {
137+
// If propsInfo exists (items mode), use it; otherwise build from props (children mode)
138+
const itemData: ItemData = propsItemData || {
139+
key: eventKey || '',
140+
label: children,
141+
itemIcon,
142+
extra: props.extra,
143+
};
144+
133145
return {
134146
key: eventKey,
135147
// Note: For legacy code is reversed which not like other antd component
136148
keyPath: [...connectedKeys].reverse(),
137149
item: legacyMenuItemRef.current,
138150
domEvent: e,
151+
itemData: propsItemData || itemData,
139152
};
140153
};
141154

src/interface.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ export interface MenuItemType extends ItemSharedProps {
6363
onClick?: MenuClickEventHandler;
6464
}
6565

66+
/** Info item type passed to onSelect/onClick callbacks, excluding event handlers */
67+
export type ItemData = {
68+
label?: React.ReactNode;
69+
itemIcon?: RenderIconType;
70+
extra?: React.ReactNode;
71+
key: React.Key;
72+
};
73+
6674
export interface MenuItemGroupType extends ItemSharedProps {
6775
type: 'group';
6876

@@ -99,6 +107,7 @@ export interface MenuInfo {
99107
/** @deprecated This will not support in future. You should avoid to use this */
100108
item: React.ReactInstance;
101109
domEvent: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>;
110+
itemData: ItemData;
102111
}
103112

104113
export interface MenuTitleInfo {

src/utils/nodeUtil.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ function convertItemsToNodes(
5151
const hasExtra = !!extra || extra === 0;
5252

5353
return (
54-
<MergedMenuItem key={mergedKey} {...restProps} extra={extra}>
54+
<MergedMenuItem
55+
key={mergedKey}
56+
{...restProps}
57+
extra={extra}
58+
itemData={{ label, key: mergedKey, itemIcon: restProps?.itemIcon, extra }}
59+
>
5560
{hasExtra ? (
5661
<>
5762
<span className={`${prefixCls}-item-label`}>{label}</span>

tests/MenuItem.spec.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,87 @@ describe('MenuItem', () => {
150150
});
151151
});
152152

153+
describe('itemData in event', () => {
154+
it('should pass itemData in onSelect and onClick with children', () => {
155+
const onSelect = jest.fn();
156+
const onClick = jest.fn();
157+
const { container } = render(
158+
<Menu onSelect={onSelect} onClick={onClick} selectable>
159+
<MenuItem key="1">Menu Item</MenuItem>
160+
</Menu>,
161+
);
162+
163+
fireEvent.click(container.querySelector('.rc-menu-item')!);
164+
expect(onSelect).toHaveBeenCalledWith(
165+
expect.objectContaining({
166+
key: '1',
167+
itemData: expect.objectContaining({
168+
key: '1',
169+
label: 'Menu Item',
170+
}),
171+
}),
172+
);
173+
expect(onClick).toHaveBeenCalledWith(
174+
expect.objectContaining({
175+
key: '1',
176+
itemData: expect.objectContaining({
177+
key: '1',
178+
label: 'Menu Item',
179+
}),
180+
}),
181+
);
182+
});
183+
184+
it('should only pass defined itemData properties in onSelect and onClick', () => {
185+
const onSelect = jest.fn();
186+
const onClick = jest.fn();
187+
const { container } = render(
188+
<Menu
189+
onSelect={onSelect}
190+
onClick={onClick}
191+
selectable
192+
items={[{ key: '1', label: 'Menu Item', foo: '123' }] as any}
193+
/>,
194+
);
195+
196+
fireEvent.click(container.querySelector('.rc-menu-item')!);
197+
expect(onSelect).toHaveBeenCalledWith(
198+
expect.objectContaining({
199+
key: '1',
200+
itemData: expect.objectContaining({
201+
key: '1',
202+
label: 'Menu Item',
203+
}),
204+
}),
205+
);
206+
expect(onSelect).toHaveBeenCalledWith(
207+
expect.objectContaining({
208+
key: '1',
209+
itemData: expect.not.objectContaining({
210+
foo: '123',
211+
}),
212+
}),
213+
);
214+
expect(onClick).toHaveBeenCalledWith(
215+
expect.objectContaining({
216+
key: '1',
217+
itemData: expect.objectContaining({
218+
key: '1',
219+
label: 'Menu Item',
220+
}),
221+
}),
222+
);
223+
expect(onClick).toHaveBeenCalledWith(
224+
expect.objectContaining({
225+
key: '1',
226+
itemData: expect.not.objectContaining({
227+
foo: '123',
228+
}),
229+
}),
230+
);
231+
});
232+
});
233+
153234
describe('overwrite default role', () => {
154235
it('should set role to none if null', () => {
155236
const { container } = render(

0 commit comments

Comments
 (0)