,
+ },
+ {
+ label: 'Navigation Two',
+ key: 'app',
+ icon:
,
+ disabled: true,
+ },
+ {
+ label: 'Navigation Three - Submenu',
+ key: 'SubMenu',
+ icon:
,
+ children: [
+ {
+ type: 'group',
+ label: 'Item 1',
+ children: [
+ {
+ label: 'Option 1',
+ key: 'setting:1',
+ },
+ {
+ label: 'Option 2',
+ key: 'setting:2',
+ },
+ ],
+ },
+ {
+ type: 'group',
+ label: 'Item 2',
+ children: [
+ {
+ label: 'Option 3',
+ key: 'setting:3',
+ },
+ {
+ label: 'Option 4',
+ key: 'setting:4',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ label: (
+
+ Navigation Four - Link
+
+ ),
+ key: 'alipay',
+ },
+];
-class App extends React.Component {
- state = {
- current: 'mail',
- };
+const App = () => {
+ const [current, setCurrent] = React.useState('mail');
- handleClick = e => {
+ const onClick: MenuProps['onClick'] = e => {
console.log('click ', e);
- this.setState({ current: e.key });
+ setCurrent(e.key);
};
- render() {
- const { current } = this.state;
- return (
-
- );
- }
-}
+ return
;
+};
ReactDOM.render(
, mountNode);
```
diff --git a/components/menu/demo/inline-collapsed.md b/components/menu/demo/inline-collapsed.md
index 4f94e2f85b..01c20505ef 100644
--- a/components/menu/demo/inline-collapsed.md
+++ b/components/menu/demo/inline-collapsed.md
@@ -17,8 +17,8 @@ Inline menu could be collapsed.
Here is [a complete demo](/components/layout/#components-layout-demo-side) with sider layout.
-```jsx
-import { Menu, Button } from 'antd';
+```tsx
+import { Menu, Button, MenuProps } from 'antd';
import {
AppstoreOutlined,
MenuUnfoldOutlined,
@@ -29,60 +29,67 @@ import {
MailOutlined,
} from '@ant-design/icons';
-const { SubMenu } = Menu;
+type MenuItem = Required
['items'][number];
-class App extends React.Component {
- state = {
- collapsed: false,
- };
-
- toggleCollapsed = () => {
- this.setState({
- collapsed: !this.state.collapsed,
- });
- };
-
- render() {
- return (
-
-
-
-
- );
- }
+function getItem(
+ label: React.ReactNode,
+ key: React.Key,
+ icon?: React.ReactNode,
+ children?: MenuItem[],
+ type?: 'group',
+): MenuItem {
+ return {
+ key,
+ icon,
+ children,
+ label,
+ type,
+ } as MenuItem;
}
+const items: MenuItem[] = [
+ getItem('Option 1', '1', ),
+ getItem('Option 2', '2', ),
+ getItem('Option 3', '3', ),
+
+ getItem('Navigation One', 'sub1', , [
+ getItem('Option 5', '5'),
+ getItem('Option 6', '6'),
+ getItem('Option 7', '7'),
+ getItem('Option 8', '8'),
+ ]),
+
+ getItem('Navigation Two', 'sub2', , [
+ getItem('Option 9', '9'),
+ getItem('Option 10', '10'),
+
+ getItem('Submenu', 'sub3', null, [getItem('Option 11', '11'), getItem('Option 12', '12')]),
+ ]),
+];
+
+const App = () => {
+ const [collapsed, setCollapsed] = React.useState(false);
+
+ const toggleCollapsed = () => {
+ setCollapsed(!collapsed);
+ };
+
+ return (
+
+
+
+
+ );
+};
+
ReactDOM.render(, mountNode);
```
diff --git a/components/menu/demo/inline.md b/components/menu/demo/inline.md
index bac7d600d2..5e0be6df0a 100755
--- a/components/menu/demo/inline.md
+++ b/components/menu/demo/inline.md
@@ -13,54 +13,64 @@ title:
Vertical menu with inline submenus.
-```jsx
-import { Menu } from 'antd';
+```tsx
+import { Menu, MenuProps } from 'antd';
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons';
-const { SubMenu } = Menu;
+type MenuItem = Required['items'][number];
-class Sider extends React.Component {
- handleClick = e => {
+function getItem(
+ label: React.ReactNode,
+ key: React.Key,
+ icon?: React.ReactNode,
+ children?: MenuItem[],
+ type?: 'group',
+): MenuItem {
+ return {
+ key,
+ icon,
+ children,
+ label,
+ type,
+ } as MenuItem;
+}
+
+const items: MenuProps['items'] = [
+ getItem('Navigation One', 'sub1', , [
+ getItem('Item 1', 'g1', null, [getItem('Option 1', '1'), getItem('Option 2', '2')], 'group'),
+ getItem('Item 2', 'g2', null, [getItem('Option 3', '3'), getItem('Option 4', '4')], 'group'),
+ ]),
+
+ getItem('Navigation Two', 'sub2', , [
+ getItem('Option 5', '5'),
+ getItem('Option 6', '6'),
+ getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
+ ]),
+
+ getItem('Navigation Three', 'sub4', , [
+ getItem('Option 9', '9'),
+ getItem('Option 10', '10'),
+ getItem('Option 11', '11'),
+ getItem('Option 12', '12'),
+ ]),
+];
+
+const Sider = () => {
+ const onClick: MenuProps['onClick'] = e => {
console.log('click ', e);
};
- render() {
- return (
-
- );
- }
-}
+ return (
+
+ );
+};
ReactDOM.render(, mountNode);
```
diff --git a/components/menu/demo/sider-current.md b/components/menu/demo/sider-current.md
index 372ca04782..0cadaac806 100755
--- a/components/menu/demo/sider-current.md
+++ b/components/menu/demo/sider-current.md
@@ -13,11 +13,47 @@ title:
Click the menu and you will see that all the other menus gets collapsed to keep the entire menu compact.
-```jsx
-import { Menu } from 'antd';
+```tsx
+import { Menu, MenuProps } from 'antd';
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons';
-const { SubMenu } = Menu;
+type MenuItem = Required['items'][number];
+
+function getItem(
+ label: React.ReactNode,
+ key: React.Key,
+ icon?: React.ReactNode,
+ children?: MenuItem[],
+ type?: 'group',
+): MenuItem {
+ return {
+ key,
+ icon,
+ children,
+ label,
+ type,
+ } as MenuItem;
+}
+
+const items: MenuItem[] = [
+ getItem('Navigation One', 'sub1', , [
+ getItem('Option 1', '1'),
+ getItem('Option 2', '2'),
+ getItem('Option 3', '3'),
+ getItem('Option 4', '4'),
+ ]),
+ getItem('Navigation Two', 'sub2', , [
+ getItem('Option 5', '5'),
+ getItem('Option 6', '6'),
+ getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
+ ]),
+ getItem('Navigation Three', 'sub4', , [
+ getItem('Option 9', '9'),
+ getItem('Option 10', '10'),
+ getItem('Option 11', '11'),
+ getItem('Option 12', '12'),
+ ]),
+];
// submenu keys of first level
const rootSubmenuKeys = ['sub1', 'sub2', 'sub4'];
@@ -25,9 +61,9 @@ const rootSubmenuKeys = ['sub1', 'sub2', 'sub4'];
const Sider = () => {
const [openKeys, setOpenKeys] = React.useState(['sub1']);
- const onOpenChange = keys => {
+ const onOpenChange: MenuProps['onOpenChange'] = keys => {
const latestOpenKey = keys.find(key => openKeys.indexOf(key) === -1);
- if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
+ if (rootSubmenuKeys.indexOf(latestOpenKey!) === -1) {
setOpenKeys(keys);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
@@ -35,28 +71,13 @@ const Sider = () => {
};
return (
-
+
);
};
diff --git a/components/menu/demo/style-debug.md b/components/menu/demo/style-debug.md
index 0002211814..e36ee568dc 100644
--- a/components/menu/demo/style-debug.md
+++ b/components/menu/demo/style-debug.md
@@ -19,91 +19,92 @@ import * as React from 'react';
import { Menu, MenuProps, Switch } from 'antd';
import { MailOutlined, AppstoreOutlined } from '@ant-design/icons';
-const { SubMenu } = Menu;
+type MenuItem = Required['items'][number];
-interface DemoState {
- theme: 'light' | 'dark';
- current: string;
+function getItem(
+ label: React.ReactNode,
+ key?: React.Key | null,
+ icon?: React.ReactNode,
+ children?: MenuItem[],
+): MenuItem {
+ return {
+ key,
+ icon,
+ children,
+ label,
+ } as MenuItem;
}
-class Demo extends React.Component<{}, DemoState> {
- state: DemoState = {
- theme: 'dark',
- current: '1',
+const items: MenuItem[] = [
+ getItem('Navigation One Long Long Long Long', 'sub1', , [
+ getItem('Option 1', '1'),
+ getItem('Option 2', '2'),
+ getItem('Option 3', '3'),
+ getItem('Option 4', '4'),
+ ]),
+ getItem('Navigation Two', 'sub2', , [
+ getItem('Option 5', '5'),
+ getItem('Option 6', '6'),
+ getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
+ ]),
+ getItem('Option 11', '11'),
+ getItem('Option 12', '12'),
+];
+
+const Demo = () => {
+ const [theme, setTheme] = React.useState<'dark' | 'light'>('dark');
+ const [current, setCurrent] = React.useState('1');
+
+ const changeTheme = (value: boolean) => {
+ setTheme(value ? 'dark' : 'light');
};
- changeTheme = (value: boolean) => {
- this.setState({
- theme: value ? 'dark' : 'light',
- });
- };
-
- handleClick: MenuProps['onClick'] = e => {
+ const onClick: MenuProps['onClick'] = e => {
console.log('click ', e);
- this.setState({
- current: e.key,
- });
+ setCurrent(e.key);
};
- render() {
- return (
- <>
-
-
-
-
- >
- );
- }
-}
+ return (
+ <>
+
+
+
+
+ items={items}
+ />
>
);
};
diff --git a/components/menu/demo/theme.md b/components/menu/demo/theme.md
index 2880585b48..7bab0c37f4 100755
--- a/components/menu/demo/theme.md
+++ b/components/menu/demo/theme.md
@@ -13,75 +13,85 @@ title:
There are two built-in themes: `light` and `dark`. The default value is `light`.
-```jsx
-import { Menu, Switch } from 'antd';
+```tsx
+import { Menu, Switch, MenuProps } from 'antd';
import { MailOutlined, AppstoreOutlined, SettingOutlined } from '@ant-design/icons';
-const { SubMenu } = Menu;
+type MenuItem = Required['items'][number];
-class Sider extends React.Component {
- state = {
- theme: 'dark',
- current: '1',
- };
-
- changeTheme = value => {
- this.setState({
- theme: value ? 'dark' : 'light',
- });
- };
-
- handleClick = e => {
- console.log('click ', e);
- this.setState({
- current: e.key,
- });
- };
-
- render() {
- return (
- <>
-
-
-
-
- } title="Navigation One">
- Option 1
- Option 2
- Option 3
- Option 4
-
- } title="Navigation Two">
- Option 5
- Option 6
-
- Option 7
- Option 8
-
-
- } title="Navigation Three">
- Option 9
- Option 10
- Option 11
- Option 12
-
-
- >
- );
- }
+function getItem(
+ label: React.ReactNode,
+ key?: React.Key | null,
+ icon?: React.ReactNode,
+ children?: MenuItem[],
+ type?: 'group',
+): MenuItem {
+ return {
+ key,
+ icon,
+ children,
+ label,
+ type,
+ } as MenuItem;
}
+const items: MenuItem[] = [
+ getItem('Navigation One', 'sub1', , [
+ getItem('Option 1', '1'),
+ getItem('Option 2', '2'),
+ getItem('Option 3', '3'),
+ getItem('Option 4', '4'),
+ ]),
+
+ getItem('Navigation Two', 'sub2', , [
+ getItem('Option 5', '5'),
+ getItem('Option 6', '6'),
+ getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
+ ]),
+
+ getItem('Navigation Three', 'sub4', , [
+ getItem('Option 9', '9'),
+ getItem('Option 10', '10'),
+ getItem('Option 11', '11'),
+ getItem('Option 12', '12'),
+ ]),
+];
+
+const Sider = () => {
+ const [theme, setTheme] = React.useState<'dark' | 'light'>('dark');
+ const [current, setCurrent] = React.useState('1');
+
+ const changeTheme = (value: boolean) => {
+ setTheme(value ? 'dark' : 'light');
+ };
+
+ const onClick: MenuProps['onClick'] = e => {
+ console.log('click ', e);
+ setCurrent(e.key);
+ };
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
ReactDOM.render(, mountNode);
```
diff --git a/components/menu/demo/vertical.md b/components/menu/demo/vertical.md
index 13bc1fb9ea..b9c126e516 100755
--- a/components/menu/demo/vertical.md
+++ b/components/menu/demo/vertical.md
@@ -13,43 +13,54 @@ title:
Submenus open as pop-ups.
-```jsx
-import { Menu } from 'antd';
+```tsx
+import { Menu, MenuProps } from 'antd';
import { MailOutlined, AppstoreOutlined, SettingOutlined } from '@ant-design/icons';
-const { SubMenu } = Menu;
+type MenuItem = Required['items'][number];
-function handleClick(e) {
- console.log('click', e);
+function getItem(
+ label: React.ReactNode,
+ key?: React.Key | null,
+ icon?: React.ReactNode,
+ children?: MenuItem[],
+ type?: 'group',
+): MenuItem {
+ return {
+ key,
+ icon,
+ children,
+ label,
+ type,
+ } as MenuItem;
}
+const items: MenuItem[] = [
+ getItem('Navigation One', 'sub1', , [
+ getItem('Item 1', null, null, [getItem('Option 1', '1'), getItem('Option 2', '2')], 'group'),
+ getItem('Item 2', null, null, [getItem('Option 3', '3'), getItem('Option 4', '4')], 'group'),
+ ]),
+
+ getItem('Navigation Two', 'sub2', , [
+ getItem('Option 5', '5'),
+ getItem('Option 6', '6'),
+ getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]),
+ ]),
+
+ getItem('Navigation Three', 'sub4', , [
+ getItem('Option 9', '9'),
+ getItem('Option 10', '10'),
+ getItem('Option 11', '11'),
+ getItem('Option 12', '12'),
+ ]),
+];
+
+const onClick: MenuProps['onClick'] = e => {
+ console.log('click', e);
+};
+
ReactDOM.render(
-
- } title="Navigation One">
-
- Option 1
- Option 2
-
-
- Option 3
- Option 4
-
-
- } title="Navigation Two">
- Option 5
- Option 6
-
- Option 7
- Option 8
-
-
- } title="Navigation Three">
- Option 9
- Option 10
- Option 11
- Option 12
-
- ,
+ ,
mountNode,
);
```
diff --git a/components/menu/hooks/useItems.tsx b/components/menu/hooks/useItems.tsx
new file mode 100644
index 0000000000..5b33d8236f
--- /dev/null
+++ b/components/menu/hooks/useItems.tsx
@@ -0,0 +1,93 @@
+import * as React from 'react';
+import { ItemGroup } from 'rc-menu';
+import type {
+ MenuItemType as RcMenuItemType,
+ MenuDividerType as RcMenuDividerType,
+ SubMenuType as RcSubMenuType,
+ MenuItemGroupType as RcMenuItemGroupType,
+} from 'rc-menu/lib/interface';
+import SubMenu from '../SubMenu';
+import MenuDivider from '../MenuDivider';
+import MenuItem from '../MenuItem';
+
+interface MenuItemType extends RcMenuItemType {
+ danger?: boolean;
+ icon?: React.ReactNode;
+ title?: string;
+}
+
+interface SubMenuType extends Omit {
+ icon?: React.ReactNode;
+ theme?: 'dark' | 'light';
+ children: ItemType[];
+}
+
+interface MenuItemGroupType extends Omit {
+ children?: MenuItemType[];
+ key?: React.Key;
+}
+
+interface MenuDividerType extends RcMenuDividerType {
+ dashed?: boolean;
+ key?: React.Key;
+}
+
+export type ItemType = MenuItemType | SubMenuType | MenuItemGroupType | MenuDividerType | null;
+
+function convertItemsToNodes(list: ItemType[]) {
+ return (list || [])
+ .map((opt, index) => {
+ if (opt && typeof opt === 'object') {
+ const { label, children, key, type, ...restProps } = opt as any;
+ const mergedKey = key ?? `tmp-${index}`;
+
+ // MenuItemGroup & SubMenuItem
+ if (children || type === 'group') {
+ if (type === 'group') {
+ // Group
+ return (
+
+ {convertItemsToNodes(children)}
+
+ );
+ }
+
+ // Sub Menu
+ return (
+
+ {convertItemsToNodes(children)}
+
+ );
+ }
+
+ // MenuItem & Divider
+ if (type === 'divider') {
+ return ;
+ }
+
+ return (
+
+ );
+ }
+
+ return null;
+ })
+ .filter(opt => opt);
+}
+
+// FIXME: Move logic here in v5
+/**
+ * We simply convert `items` to ReactNode for reuse origin component logic. But we need move all the
+ * logic from component into this hooks when in v5
+ */
+export default function useItems(items?: ItemType[]) {
+ return React.useMemo(() => {
+ if (!items) {
+ return items;
+ }
+
+ return convertItemsToNodes(items);
+ }, [items]);
+}
diff --git a/components/menu/index.en-US.md b/components/menu/index.en-US.md
index e40c8f658d..dc9d218600 100644
--- a/components/menu/index.en-US.md
+++ b/components/menu/index.en-US.md
@@ -22,12 +22,14 @@ More layouts with navigation: [Layout](/components/layout).
## API
```jsx
-
- Menu
-
- SubMenuItem
-
-
+const items = [
+ { label: 'Menu' },
+ {
+ label: 'SubMenu',
+ children: [{ label: 'SubMenuItem' }],
+ },
+];
+;
```
### Menu
@@ -40,6 +42,7 @@ More layouts with navigation: [Layout](/components/layout).
| forceSubMenuRender | Render submenu into DOM before it becomes visible | boolean | false | |
| inlineCollapsed | Specifies the collapsed status when menu is inline mode | boolean | - | |
| inlineIndent | Indent (in pixels) of inline menu items on each level | number | 24 | |
+| items | Menu item content | [ItemType\[\]](#ItemType) | - | 4.20.0 |
| mode | Type of menu | `vertical` \| `horizontal` \| `inline` | `vertical` | |
| multiple | Allows selection of multiple items | boolean | false | |
| openKeys | Array with the keys of currently opened sub-menus | string\[] | - | |
@@ -58,14 +61,19 @@ More layouts with navigation: [Layout](/components/layout).
> More options in [rc-menu](https://github.com/react-component/menu#api)
-### Menu.Item
+### ItemType
+
+> type ItemType = [MenuItemType](#MenuItemType) | [SubMenuType](#SubMenuType) | [MenuItemGroupType](#MenuItemGroupType) | [MenuDividerType](#MenuDividerType);
+
+#### MenuItemType
| Param | Description | Type | Default value | Version |
| -------- | ------------------------------------ | --------- | ------------- | ------- |
-| danger | Display the danger style | boolean | false | 4.3.0 |
+| danger | Display the danger style | boolean | false | |
| disabled | Whether menu item is disabled | boolean | false | |
-| icon | The icon of the menu item | ReactNode | - | 4.2.0 |
+| icon | The icon of the menu item | ReactNode | - | |
| key | Unique ID of the menu item | string | - | |
+| label | Menu label | ReactNode | - | |
| title | Set display title for collapsed item | string | - | |
> Note: `icon` is a newly added prop in `4.2.0`. For previous versions, please use the following method to define the icon.
@@ -87,34 +95,51 @@ More layouts with navigation: [Layout](/components/layout).
>
> ```
-### Menu.SubMenu
+#### SubMenuType
| Param | Description | Type | Default value | Version |
| --- | --- | --- | --- | --- | --- |
-| children | Sub-menus or sub-menu items | Array<MenuItem \| SubMenu> | - | |
+| children | Sub-menus or sub-menu items | [ItemType\[\]](#ItemType) | - | |
| disabled | Whether sub-menu is disabled | boolean | false | |
-| icon | Icon of sub menu | ReactNode | - | 4.2.0 |
+| icon | Icon of sub menu | ReactNode | - | |
| key | Unique ID of the sub-menu | string | - | |
+| label | Menu label | ReactNode | - | |
| popupClassName | Sub-menu class name, not working when `mode="inline"` | string | - | |
| popupOffset | Sub-menu offset, not working when `mode="inline"` | \[number, number] | - | |
| title | Title of sub menu | ReactNode | - | |
-| theme | Color theme of the SubMenu (inherits from Menu by default) | | `light` \| `dark` | - | 4.19.0 |
+| theme | Color theme of the SubMenu (inherits from Menu by default) | | `light` \| `dark` | - | |
| onTitleClick | Callback executed when the sub-menu title is clicked | function({ key, domEvent }) | - | |
-### Menu.ItemGroup
+#### MenuItemGroupType
+
+Define `type` as `group` to make as group:
+
+```ts
+const groupItem = {
+ type: 'group', // Must have
+ label: 'My Group',
+ chidlren: [],
+};
+```
| Param | Description | Type | Default value | Version |
| -------- | ---------------------- | ----------- | ------------- | ------- |
| children | Sub-menu items | MenuItem\[] | - | |
-| title | The title of the group | ReactNode | - | |
+| label | The title of the group | ReactNode | - | |
-### Menu.Divider
+#### MenuDividerType
-Divider line in between menu items, only used in vertical popup Menu or Dropdown Menu.
+Divider line in between menu items, only used in vertical popup Menu or Dropdown Menu. Need define the `type` as `divider`:
+
+```ts
+const dividerItem = {
+ type: 'divider', // Must have
+};
+```
| Param | Description | Type | Default value | Version |
| ------ | ---------------------- | ------- | ------------- | ------- |
-| dashed | Whether line is dashed | boolean | false | 4.17.0 |
+| dashed | Whether line is dashed | boolean | false | |
## FAQ
diff --git a/components/menu/index.tsx b/components/menu/index.tsx
index f02197a7ef..d4c4b74bee 100644
--- a/components/menu/index.tsx
+++ b/components/menu/index.tsx
@@ -12,6 +12,8 @@ import collapseMotion from '../_util/motion';
import { cloneElement } from '../_util/reactNode';
import MenuContext, { MenuTheme } from './MenuContext';
import MenuDivider from './MenuDivider';
+import type { ItemType } from './hooks/useItems';
+import useItems from './hooks/useItems';
export { MenuDividerProps } from './MenuDivider';
@@ -19,7 +21,7 @@ export { MenuItemGroupProps } from 'rc-menu';
export type MenuMode = 'vertical' | 'vertical-left' | 'vertical-right' | 'horizontal' | 'inline';
-export interface MenuProps extends RcMenuProps {
+export interface MenuProps extends Omit {
theme?: MenuTheme;
inlineIndent?: number;
@@ -29,6 +31,8 @@ export interface MenuProps extends RcMenuProps {
* for removing.
*/
_internalDisableMenuItemTitleTooltip?: boolean;
+
+ items?: ItemType[];
}
type InternalMenuProps = MenuProps &
@@ -49,11 +53,16 @@ function InternalMenu(props: InternalMenuProps) {
_internalDisableMenuItemTitleTooltip,
inlineCollapsed,
siderCollapsed,
+ items,
+ children,
...restProps
} = props;
const passedProps = omit(restProps, ['collapsedWidth']);
+ // ========================= Items ===========================
+ const mergedChildren = useItems(items) || children;
+
// ======================== Warning ==========================
devWarning(
!('inlineCollapsed' in props && props.mode !== 'inline'),
@@ -67,6 +76,12 @@ function InternalMenu(props: InternalMenuProps) {
'`inlineCollapsed` not control Menu under Sider. Should set `collapsed` on Sider instead.',
);
+ devWarning(
+ !!items && !children,
+ 'Menu',
+ '`children` will be removed in next major version. Please use `items` instead.',
+ );
+
// ======================== Collapsed ========================
// Inline Collapsed
const mergedInlineCollapsed = React.useMemo(() => {
@@ -114,7 +129,9 @@ function InternalMenu(props: InternalMenuProps) {
expandIcon={cloneElement(expandIcon, {
className: `${prefixCls}-submenu-expand-icon`,
})}
- />
+ >
+ {mergedChildren}
+
);
}
diff --git a/components/menu/index.zh-CN.md b/components/menu/index.zh-CN.md
index b85a613d8f..7d3de58c3b 100644
--- a/components/menu/index.zh-CN.md
+++ b/components/menu/index.zh-CN.md
@@ -23,12 +23,15 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3XZcjGpvK/Menu.svg
## API
```jsx
-
- 菜单项
-
- 子菜单项
-
-
+const items = [
+ { label: '菜单项' },
+ {
+ label: '子菜单',
+ children: [{ label: '子菜单项' }],
+ },
+];
+
+;
```
### Menu
@@ -41,6 +44,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3XZcjGpvK/Menu.svg
| forceSubMenuRender | 在子菜单展示之前就渲染进 DOM | boolean | false | |
| inlineCollapsed | inline 时菜单是否收起状态 | boolean | - | |
| inlineIndent | inline 模式的菜单缩进宽度 | number | 24 | |
+| items | 菜单内容 | [ItemType\[\]](#ItemType) | - | 4.20.0 |
| mode | 菜单类型,现在支持垂直、水平、和内嵌模式三种 | `vertical` \| `horizontal` \| `inline` | `vertical` | |
| multiple | 是否允许多选 | boolean | false | |
| openKeys | 当前展开的 SubMenu 菜单项 key 数组 | string\[] | - | |
@@ -59,15 +63,20 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3XZcjGpvK/Menu.svg
> 更多属性查看 [rc-menu](https://github.com/react-component/menu#api)
-### Menu.Item
+### ItemType
-| 参数 | 说明 | 类型 | 默认值 | 版本 |
-| -------- | ------------------------ | --------- | ------ | ----- |
-| danger | 展示错误状态样式 | boolean | false | 4.3.0 |
-| disabled | 是否禁用 | boolean | false | |
-| icon | 菜单图标 | ReactNode | - | 4.2.0 |
-| key | item 的唯一标志 | string | - | |
-| title | 设置收缩时展示的悬浮标题 | string | - | |
+> type ItemType = [MenuItemType](#MenuItemType) | [SubMenuType](#SubMenuType) | [MenuItemGroupType](#MenuItemGroupType) | [MenuDividerType](#MenuDividerType);
+
+#### MenuItemType
+
+| 参数 | 说明 | 类型 | 默认值 | 版本 |
+| -------- | ------------------------ | --------- | ------ | ---- |
+| danger | 展示错误状态样式 | boolean | false | |
+| disabled | 是否禁用 | boolean | false | |
+| icon | 菜单图标 | ReactNode | - | |
+| key | item 的唯一标志 | string | - | |
+| label | 菜单项标题 | string | - | |
+| title | 设置收缩时展示的悬浮标题 | string | - | |
> 注意:`icon` 是 `4.2.0` 新增的属性,之前的版本请使用下面的方式定义图标。
>
@@ -88,34 +97,50 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3XZcjGpvK/Menu.svg
>
> ```
-### Menu.SubMenu
+#### SubMenuType
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | --- |
-| children | 子菜单的菜单项 | Array<MenuItem \| SubMenu> | - | |
+| children | 子菜单的菜单项 | [ItemType\[\]](#ItemType) | - | |
| disabled | 是否禁用 | boolean | false | |
-| icon | 菜单图标 | ReactNode | - | 4.2.0 |
+| icon | 菜单图标 | ReactNode | - | |
| key | 唯一标志 | string | - | |
+| label | 菜单项标题 | ReactNode | - | |
| popupClassName | 子菜单样式,`mode="inline"` 时无效 | string | - | |
| popupOffset | 子菜单偏移量,`mode="inline"` 时无效 | \[number, number] | - | |
-| title | 子菜单项值 | ReactNode | - | |
| onTitleClick | 点击子菜单标题 | function({ key, domEvent }) | - | |
-| theme | 设置子菜单的主题,默认从 Menu 上继承 | | `light` \| `dark` | - | 4.19.0 |
+| theme | 设置子菜单的主题,默认从 Menu 上继承 | | `light` \| `dark` | - | |
-### Menu.ItemGroup
+#### MenuItemGroupType
-| 参数 | 说明 | 类型 | 默认值 | 版本 |
-| -------- | ------------ | ----------- | ------ | ---- |
-| children | 分组的菜单项 | MenuItem\[] | - | |
-| title | 分组标题 | ReactNode | - | |
+定义类型为 `group` 时,会作为分组处理:
-### Menu.Divider
+```ts
+const groupItem = {
+ type: 'group', // Must have
+ label: 'My Group',
+ chidlren: [],
+};
+```
-菜单项分割线,只用在弹出菜单内。
+| 参数 | 说明 | 类型 | 默认值 | 版本 |
+| -------- | ------------ | --------------------------------- | ------ | ---- |
+| children | 分组的菜单项 | [MenuItemType\[\]](#MenuItemType) | - | |
+| label | 分组标题 | ReactNode | - | |
-| 参数 | 说明 | 类型 | 默认值 | 版本 |
-| ------ | -------- | ------- | ------ | ------ |
-| dashed | 是否虚线 | boolean | false | 4.17.0 |
+#### MenuDividerType
+
+菜单项分割线,只用在弹出菜单内,需要定义类型为 `divider`:
+
+```ts
+const dividerItem = {
+ type: 'divider', // Must have
+};
+```
+
+| 参数 | 说明 | 类型 | 默认值 | 版本 |
+| ------ | -------- | ------- | ------ | ---- |
+| dashed | 是否虚线 | boolean | false | |
## FAQ
diff --git a/components/page-header/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/page-header/__tests__/__snapshots__/demo-extend.test.ts.snap
index 0bbaec1ded..9b2dcf810a 100644
--- a/components/page-header/__tests__/__snapshots__/demo-extend.test.ts.snap
+++ b/components/page-header/__tests__/__snapshots__/demo-extend.test.ts.snap
@@ -491,56 +491,58 @@ exports[`renders ./components/page-header/demo/breadcrumb.md extend context corr