Browse Source

chore: improved support for generics MenuItemType and MenuItemProps in the Menu component (#42240)

* chore: improved support for generics MenuItemType and MenuItemProps in the Menu component

* docs: update codesandbox link (#42238)

* chore: improved support for generics MenuItemType and MenuItemProps in the Menu component

* chore: add typing test to Menu

---------

Co-authored-by: yangyuan <yangyuan@yangyuandeMacBook-Pro.local>
Co-authored-by: afc163 <afc163@gmail.com>
pull/42245/head
yangyuanxx 2 years ago
committed by GitHub
parent
commit
a60d55ef10
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      components/menu/MenuItem.tsx
  2. 70
      components/menu/__tests__/type.test.tsx
  3. 17
      components/menu/hooks/useItems.tsx
  4. 23
      components/menu/index.tsx

19
components/menu/MenuItem.tsx

@ -18,7 +18,24 @@ export interface MenuItemProps extends Omit<RcMenuItemProps, 'title'> {
title?: React.ReactNode;
}
const MenuItem: React.FC<MenuItemProps> = (props) => {
type MenuItemComponent = React.FC<MenuItemProps>;
type RestArgs<T> = T extends (arg: any, ...args: infer P) => any ? P : never;
type GenericProps<T = unknown> = T extends infer U extends MenuItemProps
? unknown extends U
? MenuItemProps
: U
: MenuItemProps;
interface GenericComponent extends Omit<MenuItemComponent, ''> {
<T extends MenuItemProps>(
props: GenericProps<T>,
...args: RestArgs<MenuItemComponent>
): ReturnType<MenuItemComponent>;
}
const MenuItem: GenericComponent = (props) => {
const { className, children, icon, title, danger } = props;
const {
prefixCls,

70
components/menu/__tests__/type.test.tsx

@ -1,5 +1,6 @@
import React from 'react';
import Menu from '..';
import Menu, { type MenuItemProps } from '..';
import type { MenuItemType } from '../hooks/useItems';
describe('Menu.typescript', () => {
it('Menu.items', () => {
@ -37,4 +38,71 @@ describe('Menu.typescript', () => {
expect(menu).toBeTruthy();
});
it('Menu.items should accept custom item type', () => {
interface CustomItemType extends MenuItemType {
'data-x': number;
}
const menu = (
<Menu<CustomItemType>
items={[
{ key: 'item', title: 'Item', 'data-x': 0 },
{
key: 'submenu',
theme: 'light',
children: [
{ key: 'submenu-item', title: 'SubmenuItem', 'data-x': 0 },
{ key: 'submenu-submenu', theme: 'light', children: [], 'data-x': 0 },
{ key: 'submenu-divider', type: 'divider' },
{ key: 'submenu-group', type: 'group' },
null,
],
},
{
key: 'group',
type: 'group',
children: [
{ key: 'group-item', label: 'GroupItem', 'data-x': 0 },
{ key: 'group-submenu', theme: 'light', children: [], 'data-x': 0 },
{ key: 'group-divider', type: 'divider' },
{ key: 'group-group', type: 'group' },
null,
],
},
{ key: 'divider', type: 'divider' },
null,
]}
/>
);
expect(menu).toBeTruthy();
});
it('MenuItem.props should accept custom props', () => {
interface CustomItemProps extends MenuItemProps {
'data-x': number;
}
const menu = (
<Menu>
<Menu.Item<CustomItemProps> key="item" title="Item" data-x={0} />
<Menu.SubMenu key="submenu" theme="light">
<Menu.Item<CustomItemProps> key="submenu-item" title="SubmenuItem" data-x={0} />
<Menu.SubMenu key="submenu-submenu" theme="light" />
<Menu.Divider key="submenu-divider" />
<Menu.ItemGroup key="submenu-group" />
</Menu.SubMenu>
<Menu.ItemGroup key="group">
<Menu.Item<CustomItemProps> key="group-item" title="GroupItem" data-x={0} />
<Menu.SubMenu key="group-submenu" theme="light" />
<Menu.Divider key="group-divider" />
<Menu.ItemGroup key="group-group" />
</Menu.ItemGroup>
<Menu.Divider key="divider" />
</Menu>
);
expect(menu).toBeTruthy();
});
});

17
components/menu/hooks/useItems.tsx

@ -16,14 +16,16 @@ export interface MenuItemType extends RcMenuItemType {
title?: string;
}
export interface SubMenuType extends Omit<RcSubMenuType, 'children'> {
export interface SubMenuType<T extends MenuItemType = MenuItemType>
extends Omit<RcSubMenuType, 'children'> {
icon?: React.ReactNode;
theme?: 'dark' | 'light';
children: ItemType[];
children: ItemType<T>[];
}
export interface MenuItemGroupType extends Omit<RcMenuItemGroupType, 'children'> {
children?: ItemType[];
export interface MenuItemGroupType<T extends MenuItemType = MenuItemType>
extends Omit<RcMenuItemGroupType, 'children'> {
children?: ItemType<T>[];
key?: React.Key;
}
@ -32,7 +34,12 @@ export interface MenuDividerType extends RcMenuDividerType {
key?: React.Key;
}
export type ItemType = MenuItemType | SubMenuType | MenuItemGroupType | MenuDividerType | null;
export type ItemType<T extends MenuItemType = MenuItemType> =
| T
| SubMenuType<T>
| MenuItemGroupType<T>
| MenuDividerType
| null;
function convertItemsToNodes(list: ItemType[]) {
return (list || [])

23
components/menu/index.tsx

@ -9,6 +9,7 @@ import type { MenuTheme } from './MenuContext';
import MenuDivider from './MenuDivider';
import Item, { type MenuItemProps } from './MenuItem';
import SubMenu, { type SubMenuProps } from './SubMenu';
import type { ItemType, MenuItemType } from './hooks/useItems';
export type { MenuItemGroupProps } from 'rc-menu';
export type { MenuDividerProps } from './MenuDivider';
@ -19,15 +20,29 @@ export type MenuRef = {
focus: (options?: FocusOptions) => void;
};
type CompoundedComponent = React.ForwardRefExoticComponent<
MenuProps & React.RefAttributes<MenuRef>
> & {
type ComponentProps = MenuProps & React.RefAttributes<MenuRef>;
type GenericItemType<T = unknown> = T extends infer U extends MenuItemType
? unknown extends U
? ItemType
: ItemType<U>
: ItemType;
type GenericComponentProps<T = unknown> = Omit<ComponentProps, 'items'> & {
items?: GenericItemType<T>[];
};
type CompoundedComponent = React.ForwardRefExoticComponent<GenericComponentProps> & {
Item: typeof Item;
SubMenu: typeof SubMenu;
Divider: typeof MenuDivider;
ItemGroup: typeof ItemGroup;
};
interface GenericComponent extends Omit<CompoundedComponent, ''> {
<T extends MenuItemType>(props: GenericComponentProps<T>): ReturnType<CompoundedComponent>;
}
const Menu = forwardRef<MenuRef, MenuProps>((props, ref) => {
const menuRef = useRef<RcMenuRef>(null);
const context = React.useContext(SiderContext);
@ -39,7 +54,7 @@ const Menu = forwardRef<MenuRef, MenuProps>((props, ref) => {
},
}));
return <InternalMenu ref={menuRef} {...props} {...context} />;
}) as CompoundedComponent;
}) as GenericComponent;
Menu.Item = Item;
Menu.SubMenu = SubMenu;

Loading…
Cancel
Save