From 7184ed58c50f5d0728c6336ad0f6389aab9dfbf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Thu, 14 May 2026 10:19:39 +0800 Subject: [PATCH 1/5] 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 --- README.md | 4 +-- src/MenuItem.tsx | 14 ++++++++ src/interface.ts | 1 + src/utils/nodeUtil.tsx | 2 +- tests/MenuItem.spec.tsx | 73 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ba1c37b5..6d7d588a 100644 --- a/README.md +++ b/README.md @@ -135,13 +135,13 @@ ReactDOM.render( onSelect - function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[]}) + function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[], info:{item:MenuItemType}}) called when select a menu item onClick - function({key:String, item:ReactComponent, domEvent:Event, keyPath: String[]}) + function({key:String, item:ReactComponent, domEvent:Event, keyPath: String[], info:{item:MenuItemType}}) called when click a menu item diff --git a/src/MenuItem.tsx b/src/MenuItem.tsx index 3405fae6..fd50327e 100644 --- a/src/MenuItem.tsx +++ b/src/MenuItem.tsx @@ -32,6 +32,9 @@ export interface MenuItemProps /** @deprecated No place to use this. Should remove */ attribute?: Record; + + /** @private Origin item config from items prop */ + info?: { item: MenuItemType }; } // Since Menu event provide the `info.item` which point to the MenuItem node instance. @@ -77,6 +80,7 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref< disabled, itemIcon, children, + info: propsInfo, // Aria role, @@ -133,12 +137,22 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref< const getEventInfo = ( e: React.MouseEvent | React.KeyboardEvent, ): MenuInfo => { + // If propsInfo exists (items mode), use it; otherwise build from props (children mode) + const infoItem: MenuItemType = propsInfo?.item || { + key: eventKey || '', + label: children, + disabled, + itemIcon, + extra: props.extra, + }; + return { key: eventKey, // Note: For legacy code is reversed which not like other antd component keyPath: [...connectedKeys].reverse(), item: legacyMenuItemRef.current, domEvent: e, + info: propsInfo || { item: infoItem }, }; }; diff --git a/src/interface.ts b/src/interface.ts index 683f6d87..573cf2ce 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -99,6 +99,7 @@ export interface MenuInfo { /** @deprecated This will not support in future. You should avoid to use this */ item: React.ReactInstance; domEvent: React.MouseEvent | React.KeyboardEvent; + info: { item: MenuItemType }; } export interface MenuTitleInfo { diff --git a/src/utils/nodeUtil.tsx b/src/utils/nodeUtil.tsx index aef40010..770bafac 100644 --- a/src/utils/nodeUtil.tsx +++ b/src/utils/nodeUtil.tsx @@ -51,7 +51,7 @@ function convertItemsToNodes( const hasExtra = !!extra || extra === 0; return ( - + {hasExtra ? ( <> {label} diff --git a/tests/MenuItem.spec.tsx b/tests/MenuItem.spec.tsx index 7e7e2177..c9486416 100644 --- a/tests/MenuItem.spec.tsx +++ b/tests/MenuItem.spec.tsx @@ -150,6 +150,79 @@ describe('MenuItem', () => { }); }); + describe('info.item in event', () => { + it('should pass info.item in onSelect and onClick with children', () => { + const onSelect = jest.fn(); + const onClick = jest.fn(); + const { container } = render( + + Menu Item + , + ); + + fireEvent.click(container.querySelector('.rc-menu-item')!); + expect(onSelect).toHaveBeenCalledWith( + expect.objectContaining({ + key: '1', + info: expect.objectContaining({ + item: expect.objectContaining({ + key: '1', + label: 'Menu Item', + }), + }), + }), + ); + expect(onClick).toHaveBeenCalledWith( + expect.objectContaining({ + key: '1', + info: expect.objectContaining({ + item: expect.objectContaining({ + key: '1', + label: 'Menu Item', + }), + }), + }), + ); + }); + + it('should pass info.item in onSelect and onClick with items', () => { + const onSelect = jest.fn(); + const onClick = jest.fn(); + const { container } = render( + , + ); + + fireEvent.click(container.querySelector('.rc-menu-item')!); + expect(onSelect).toHaveBeenCalledWith( + expect.objectContaining({ + key: '1', + info: expect.objectContaining({ + item: expect.objectContaining({ + key: '1', + label: 'Menu Item', + }), + }), + }), + ); + expect(onClick).toHaveBeenCalledWith( + expect.objectContaining({ + key: '1', + info: expect.objectContaining({ + item: expect.objectContaining({ + key: '1', + label: 'Menu Item', + }), + }), + }), + ); + }); + }); + describe('overwrite default role', () => { it('should set role to none if null', () => { const { container } = render( From e98a8520e595de734a7b479c2079a3fedbdfd8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Thu, 14 May 2026 11:29:59 +0800 Subject: [PATCH 2/5] feat: update MenuItem type to MenuItemInfo for onSelect and onClick callbacks --- README.md | 4 ++-- src/MenuItem.tsx | 6 +++--- src/interface.ts | 8 +++++++- src/utils/nodeUtil.tsx | 8 +++++++- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6d7d588a..965659c9 100644 --- a/README.md +++ b/README.md @@ -135,13 +135,13 @@ ReactDOM.render( onSelect - function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[], info:{item:MenuItemType}}) + function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[], info:{item:MenuItemInfo}}) called when select a menu item onClick - function({key:String, item:ReactComponent, domEvent:Event, keyPath: String[], info:{item:MenuItemType}}) + function({key:String, item:ReactComponent, domEvent:Event, keyPath: String[], info:{item:MenuItemInfo}}) called when click a menu item diff --git a/src/MenuItem.tsx b/src/MenuItem.tsx index fd50327e..ba2a4158 100644 --- a/src/MenuItem.tsx +++ b/src/MenuItem.tsx @@ -12,7 +12,7 @@ import PrivateContext from './context/PrivateContext'; import useActive from './hooks/useActive'; import useDirectionStyle from './hooks/useDirectionStyle'; import Icon from './Icon'; -import type { MenuInfo, MenuItemType } from './interface'; +import type { MenuInfo, MenuItemInfo, MenuItemType } from './interface'; import { warnItemProp } from './utils/warnUtil'; export interface MenuItemProps @@ -34,7 +34,7 @@ export interface MenuItemProps attribute?: Record; /** @private Origin item config from items prop */ - info?: { item: MenuItemType }; + info?: { item: MenuItemInfo }; } // Since Menu event provide the `info.item` which point to the MenuItem node instance. @@ -138,7 +138,7 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref< e: React.MouseEvent | React.KeyboardEvent, ): MenuInfo => { // If propsInfo exists (items mode), use it; otherwise build from props (children mode) - const infoItem: MenuItemType = propsInfo?.item || { + const infoItem: MenuItemInfo = propsInfo?.item || { key: eventKey || '', label: children, disabled, diff --git a/src/interface.ts b/src/interface.ts index 573cf2ce..ecc09de5 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -63,6 +63,12 @@ export interface MenuItemType extends ItemSharedProps { onClick?: MenuClickEventHandler; } +/** Info item type passed to onSelect/onClick callbacks, excluding event handlers */ +export type MenuItemInfo = Omit< + MenuItemType, + 'onMouseEnter' | 'onMouseLeave' | 'onClick' | 'className' | 'style' | 'ref' +>; + export interface MenuItemGroupType extends ItemSharedProps { type: 'group'; @@ -99,7 +105,7 @@ export interface MenuInfo { /** @deprecated This will not support in future. You should avoid to use this */ item: React.ReactInstance; domEvent: React.MouseEvent | React.KeyboardEvent; - info: { item: MenuItemType }; + info: { item: MenuItemInfo }; } export interface MenuTitleInfo { diff --git a/src/utils/nodeUtil.tsx b/src/utils/nodeUtil.tsx index 770bafac..24344872 100644 --- a/src/utils/nodeUtil.tsx +++ b/src/utils/nodeUtil.tsx @@ -5,6 +5,7 @@ import MenuItem from '../MenuItem'; import MenuItemGroup from '../MenuItemGroup'; import SubMenu from '../SubMenu'; import { parseChildren } from './commonUtil'; +import { omit } from '@rc-component/util'; function convertItemsToNodes( list: ItemType[], @@ -51,7 +52,12 @@ function convertItemsToNodes( const hasExtra = !!extra || extra === 0; return ( - + {hasExtra ? ( <> {label} From bb48d9bc8375bead375bfc0180d4b39b1379a7a5 Mon Sep 17 00:00:00 2001 From: EmilyyyLiu <100924403+EmilyyyLiu@users.noreply.github.com> Date: Thu, 14 May 2026 11:39:11 +0800 Subject: [PATCH 3/5] Update src/utils/nodeUtil.tsx Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/utils/nodeUtil.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/nodeUtil.tsx b/src/utils/nodeUtil.tsx index 24344872..5ca6d693 100644 --- a/src/utils/nodeUtil.tsx +++ b/src/utils/nodeUtil.tsx @@ -5,7 +5,7 @@ import MenuItem from '../MenuItem'; import MenuItemGroup from '../MenuItemGroup'; import SubMenu from '../SubMenu'; import { parseChildren } from './commonUtil'; -import { omit } from '@rc-component/util'; +import omit from '@rc-component/util/lib/omit'; function convertItemsToNodes( list: ItemType[], From 5cb6207dbc1eedb9a90f7b879ff4109feafae075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Thu, 14 May 2026 11:54:43 +0800 Subject: [PATCH 4/5] feat: update MenuItemInfo type and refactor info item handling in nodeUtil --- src/MenuItem.tsx | 1 - src/interface.ts | 10 ++++++---- src/utils/nodeUtil.tsx | 3 +-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/MenuItem.tsx b/src/MenuItem.tsx index ba2a4158..024d755c 100644 --- a/src/MenuItem.tsx +++ b/src/MenuItem.tsx @@ -141,7 +141,6 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref< const infoItem: MenuItemInfo = propsInfo?.item || { key: eventKey || '', label: children, - disabled, itemIcon, extra: props.extra, }; diff --git a/src/interface.ts b/src/interface.ts index ecc09de5..ac7a62ac 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -64,10 +64,12 @@ export interface MenuItemType extends ItemSharedProps { } /** Info item type passed to onSelect/onClick callbacks, excluding event handlers */ -export type MenuItemInfo = Omit< - MenuItemType, - 'onMouseEnter' | 'onMouseLeave' | 'onClick' | 'className' | 'style' | 'ref' ->; +export type MenuItemInfo = { + label?: React.ReactNode; + itemIcon?: RenderIconType; + extra?: React.ReactNode; + key: React.Key; +}; export interface MenuItemGroupType extends ItemSharedProps { type: 'group'; diff --git a/src/utils/nodeUtil.tsx b/src/utils/nodeUtil.tsx index 5ca6d693..bae8fec6 100644 --- a/src/utils/nodeUtil.tsx +++ b/src/utils/nodeUtil.tsx @@ -5,7 +5,6 @@ import MenuItem from '../MenuItem'; import MenuItemGroup from '../MenuItemGroup'; import SubMenu from '../SubMenu'; import { parseChildren } from './commonUtil'; -import omit from '@rc-component/util/lib/omit'; function convertItemsToNodes( list: ItemType[], @@ -56,7 +55,7 @@ function convertItemsToNodes( key={mergedKey} {...restProps} extra={extra} - info={{ item: omit(opt, ['className', 'style']) }} + info={{ item: { label, key, itemIcon: restProps?.itemIcon, extra } }} > {hasExtra ? ( <> From 3a4e7088e183529dcfa4a170c1fb068c1c04fea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Thu, 14 May 2026 13:35:01 +0800 Subject: [PATCH 5/5] fix: update key in info.item to use mergedKey in convertItemsToNodes --- src/utils/nodeUtil.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/nodeUtil.tsx b/src/utils/nodeUtil.tsx index bae8fec6..8307f851 100644 --- a/src/utils/nodeUtil.tsx +++ b/src/utils/nodeUtil.tsx @@ -55,7 +55,7 @@ function convertItemsToNodes( key={mergedKey} {...restProps} extra={extra} - info={{ item: { label, key, itemIcon: restProps?.itemIcon, extra } }} + info={{ item: { label, key: mergedKey, itemIcon: restProps?.itemIcon, extra } }} > {hasExtra ? ( <>