diff --git a/README.md b/README.md index ba1c37b5..965659c9 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:MenuItemInfo}}) 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:MenuItemInfo}}) called when click a menu item diff --git a/src/MenuItem.tsx b/src/MenuItem.tsx index 3405fae6..024d755c 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 @@ -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: MenuItemInfo }; } // 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,21 @@ 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: MenuItemInfo = propsInfo?.item || { + key: eventKey || '', + label: children, + 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..ac7a62ac 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -63,6 +63,14 @@ export interface MenuItemType extends ItemSharedProps { onClick?: MenuClickEventHandler; } +/** Info item type passed to onSelect/onClick callbacks, excluding event handlers */ +export type MenuItemInfo = { + label?: React.ReactNode; + itemIcon?: RenderIconType; + extra?: React.ReactNode; + key: React.Key; +}; + export interface MenuItemGroupType extends ItemSharedProps { type: 'group'; @@ -99,6 +107,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: MenuItemInfo }; } export interface MenuTitleInfo { diff --git a/src/utils/nodeUtil.tsx b/src/utils/nodeUtil.tsx index aef40010..8307f851 100644 --- a/src/utils/nodeUtil.tsx +++ b/src/utils/nodeUtil.tsx @@ -51,7 +51,12 @@ 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(