diff --git a/components/breadcrumb/Breadcrumb.tsx b/components/breadcrumb/Breadcrumb.tsx index 8c3068aa6a..393644c788 100755 --- a/components/breadcrumb/Breadcrumb.tsx +++ b/components/breadcrumb/Breadcrumb.tsx @@ -97,13 +97,12 @@ const Breadcrumb: BreadcrumbInterface = ({ let overlay; if (route.children && route.children.length) { overlay = ( - - {route.children.map(child => ( - - {itemRender(child, params, routes, addChildPath(paths, child.path, params))} - - ))} - + ({ + key: child.path || child.breadcrumbName, + label: itemRender(child, params, routes, addChildPath(paths, child.path, params)), + }))} + /> ); } @@ -144,9 +143,7 @@ const Breadcrumb: BreadcrumbInterface = ({ return ( ); }; diff --git a/components/breadcrumb/demo/overlay.md b/components/breadcrumb/demo/overlay.md index 11f839c0e5..c6283a2ad4 100644 --- a/components/breadcrumb/demo/overlay.md +++ b/components/breadcrumb/demo/overlay.md @@ -17,23 +17,31 @@ Breadcrumbs support drop down menu. import { Breadcrumb, Menu } from 'antd'; const menu = ( - - - - General - - - - - Layout - - - - - Navigation - - - + + General + + ), + }, + { + label: ( + + Layout + + ), + }, + { + label: ( + + Navigation + + ), + }, + ]} + /> ); ReactDOM.render( diff --git a/components/button/demo/multiple.md b/components/button/demo/multiple.md index 0c5c8c21cd..45da2f4690 100644 --- a/components/button/demo/multiple.md +++ b/components/button/demo/multiple.md @@ -13,19 +13,31 @@ title: If you need several buttons, we recommend that you use 1 primary button + n secondary buttons, and if there are more than three operations, you can group some of them into [Dropdown.Button](/components/dropdown/#components-dropdown-demo-dropdown-button). -```jsx -import { Button, Menu, Dropdown } from 'antd'; +```tsx +import { Button, Menu, Dropdown, MenuProps } from 'antd'; -function handleMenuClick(e) { +const onMenuClick: MenuProps['onClick'] = e => { console.log('click', e); -} +}; const menu = ( - - 1st item - 2nd item - 3rd item - + ); ReactDOM.render( diff --git a/components/config-provider/demo/theme.md b/components/config-provider/demo/theme.md index b16dd1ef11..163f62e03e 100644 --- a/components/config-provider/demo/theme.md +++ b/components/config-provider/demo/theme.md @@ -61,6 +61,35 @@ import { const SplitSpace = props => } size={4} {...props} />; +const menuItems = [ + { + key: 'mail', + icon: , + label: 'Mail', + }, + { + key: 'SubMenu', + icon: , + label: 'Submenu', + children: [ + { + type: 'group', + label: 'Item 1', + children: [ + { + key: 'setting:1', + label: 'Option 1', + }, + { + key: 'setting:2', + label: 'Option 2', + }, + ], + }, + ], + }, +]; + const inputProps = { style: { width: 128 }, }; @@ -281,10 +310,17 @@ const FormSizeDemo = () => { {/* Dropdown */} - 1st menu item - a danger item - + } > e.preventDefault()}> @@ -299,60 +335,25 @@ const FormSizeDemo = () => { {/* Menu - horizontal */} - - }> - Mail - - } title="Submenu"> - - Option 1 - Option 2 - - - + - - }> - Mail - - } title="Submenu"> - - Option 1 - Option 2 - - - + {/* Menu - vertical */} - - }> - Mail - - } title="Submenu"> - - Option 1 - Option 2 - - - + - - }> - Mail - - } title="Submenu"> - - Option 1 - Option 2 - - - + diff --git a/components/dropdown/demo/arrow-center.md b/components/dropdown/demo/arrow-center.md index fd4834fbb7..d792042891 100644 --- a/components/dropdown/demo/arrow-center.md +++ b/components/dropdown/demo/arrow-center.md @@ -17,23 +17,31 @@ By specifying `arrow` prop with `{ pointAtCenter: true }`, the arrow will point import { Menu, Dropdown, Button } from 'antd'; const menu = ( - - - - 1st menu item - - - - - 2nd menu item - - - - - 3rd menu item - - - + + 1st menu item + + ), + }, + { + label: ( + + 2nd menu item + + ), + }, + { + label: ( + + 3rd menu item + + ), + }, + ]} + /> ); ReactDOM.render( diff --git a/components/dropdown/demo/arrow.md b/components/dropdown/demo/arrow.md index b4c3880e7f..9d17b3dbbb 100644 --- a/components/dropdown/demo/arrow.md +++ b/components/dropdown/demo/arrow.md @@ -17,23 +17,31 @@ You could display an arrow. import { Menu, Dropdown, Button } from 'antd'; const menu = ( - - - - 1st menu item - - - - - 2nd menu item - - - - - 3rd menu item - - - + + 1st menu item + + ), + }, + { + label: ( + + 2nd menu item + + ), + }, + { + label: ( + + 3rd menu item + + ), + }, + ]} + /> ); ReactDOM.render( diff --git a/components/dropdown/demo/basic.md b/components/dropdown/demo/basic.md index 29232c7cd1..21666afaab 100644 --- a/components/dropdown/demo/basic.md +++ b/components/dropdown/demo/basic.md @@ -18,24 +18,38 @@ import { Menu, Dropdown } from 'antd'; import { DownOutlined } from '@ant-design/icons'; const menu = ( - - - - 1st menu item - - - } disabled> - - 2nd menu item (disabled) - - - - - 3rd menu item (disabled) - - - a danger item - + + 1st menu item + + ), + }, + { + label: ( + + 2nd menu item (disabled) + + ), + icon: , + disabled: true, + }, + { + label: ( + + 3rd menu item (disabled) + + ), + disabled: true, + }, + { + danger: true, + label: 'a danger item', + }, + ]} + /> ); ReactDOM.render( diff --git a/components/dropdown/demo/context-menu.md b/components/dropdown/demo/context-menu.md index 2b81de264e..0c543cdb0c 100644 --- a/components/dropdown/demo/context-menu.md +++ b/components/dropdown/demo/context-menu.md @@ -17,11 +17,22 @@ The default trigger mode is `hover`, you can change it to `contextMenu`. import { Menu, Dropdown } from 'antd'; const menu = ( - - 1st menu item - 2nd menu item - 3rd menu item - + ); ReactDOM.render( diff --git a/components/dropdown/demo/dropdown-button.md b/components/dropdown/demo/dropdown-button.md index 21e406aa78..0412978a34 100644 --- a/components/dropdown/demo/dropdown-button.md +++ b/components/dropdown/demo/dropdown-button.md @@ -28,17 +28,26 @@ function handleMenuClick(e) { } const menu = ( - - }> - 1st menu item - - }> - 2nd menu item - - }> - 3rd menu item - - + , + }, + { + label: '2nd menu item', + key: '2', + icon: , + }, + { + label: '3rd menu item', + key: '3', + icon: , + }, + ]} + /> ); ReactDOM.render( diff --git a/components/dropdown/demo/event.md b/components/dropdown/demo/event.md index 06610231eb..8f49f72a9d 100644 --- a/components/dropdown/demo/event.md +++ b/components/dropdown/demo/event.md @@ -22,11 +22,23 @@ const onClick = ({ key }) => { }; const menu = ( - - 1st menu item - 2nd menu item - 3rd menu item - + ); ReactDOM.render( diff --git a/components/dropdown/demo/item.md b/components/dropdown/demo/item.md index 78cee90186..6b60b72a53 100644 --- a/components/dropdown/demo/item.md +++ b/components/dropdown/demo/item.md @@ -18,22 +18,34 @@ import { Menu, Dropdown } from 'antd'; import { DownOutlined } from '@ant-design/icons'; const menu = ( - - - - 1st menu item - - - - - 2nd menu item - - - - - 3rd menu item(disabled) - - + + 1st menu item + + ), + key: '0', + }, + { + label: ( + + 2nd menu item + + ), + key: '1', + }, + { + type: 'divider', + }, + { + label: '3rd menu item(disabled)', + key: '3', + disabled: true, + }, + ]} + /> ); ReactDOM.render( diff --git a/components/dropdown/demo/loading.md b/components/dropdown/demo/loading.md index f77a996eae..3629c757df 100644 --- a/components/dropdown/demo/loading.md +++ b/components/dropdown/demo/loading.md @@ -18,9 +18,14 @@ import { Menu, Dropdown, Space } from 'antd'; import { DownOutlined } from '@ant-design/icons'; const menu = ( - - Submit and continue - + ); class App extends React.Component { state = { diff --git a/components/dropdown/demo/menu-full.md b/components/dropdown/demo/menu-full.md index 4f7e020ac3..3d78f7fab6 100644 --- a/components/dropdown/demo/menu-full.md +++ b/components/dropdown/demo/menu-full.md @@ -18,44 +18,56 @@ This demo was created for debugging Menu styles inside Dropdown. [#19150](https://github.com/ant-design/ant-design/pull/19150) -```jsx -import { Menu, Dropdown } from 'antd'; +```tsx +import { Menu, Dropdown, MenuProps } from 'antd'; import { MailOutlined, AppstoreOutlined, SettingOutlined, DownOutlined } from '@ant-design/icons'; -const { SubMenu } = Menu; +type MenuItem = Required['items'][number]; -const menu = ( - - - Option 0 - Option 0 - - } 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, + icon?: React.ReactNode, + children?: MenuItem[], + type?: 'group', +): MenuItem { + return { + key, + icon, + children, + label, + type, + } as MenuItem; +} + +const items: MenuItem[] = [ + getItem( + 'Item Group', + 'group', + null, + [getItem('Option 0', '01'), getItem('Option 0', '02')], + 'group', + ), + 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'), + ]), + // Not crash + null as any, +]; + +const menu = ; ReactDOM.render( diff --git a/components/dropdown/demo/overlay-visible.md b/components/dropdown/demo/overlay-visible.md index ca788a5008..37826c2ac6 100644 --- a/components/dropdown/demo/overlay-visible.md +++ b/components/dropdown/demo/overlay-visible.md @@ -34,11 +34,23 @@ class OverlayVisible extends React.Component { render() { const menu = ( - - Clicking me will not close the menu. - Clicking me will not close the menu also. - Clicking me will close the menu. - + ); return ( - - - 1st menu item - - - - - 2nd menu item - - - - - 3rd menu item - - - + + 1st menu item + + ), + }, + { + label: ( + + 2nd menu item + + ), + }, + { + label: ( + + 3rd menu item + + ), + }, + ]} + /> ); ReactDOM.render( diff --git a/components/dropdown/demo/sub-menu.md b/components/dropdown/demo/sub-menu.md index 35dafc443d..75e1e3b6a9 100644 --- a/components/dropdown/demo/sub-menu.md +++ b/components/dropdown/demo/sub-menu.md @@ -20,20 +20,45 @@ import { DownOutlined } from '@ant-design/icons'; const { SubMenu } = Menu; const menu = ( - - - 1st menu item - 2nd menu item - - - 3rd menu item - 4th menu item - - - 5d menu item - 6th menu item - - + ); ReactDOM.render( diff --git a/components/dropdown/demo/trigger.md b/components/dropdown/demo/trigger.md index 192f253aed..b727e6af64 100644 --- a/components/dropdown/demo/trigger.md +++ b/components/dropdown/demo/trigger.md @@ -13,21 +13,30 @@ title: The default trigger mode is `hover`, you can change it to `click`. -```jsx +```tsx import { Menu, Dropdown } from 'antd'; import { DownOutlined } from '@ant-design/icons'; const menu = ( - - - 1st menu item - - - 2nd menu item - - - 3rd menu item - + 1st menu item, + key: '0', + }, + { + label: 2nd menu item, + key: '1', + }, + { + type: 'divider', + }, + { + label: '3rd menu item', + key: '3', + }, + ]} + /> ); ReactDOM.render( diff --git a/components/layout/demo/custom-trigger-debug.md b/components/layout/demo/custom-trigger-debug.md index 163efa7705..27248de6e4 100644 --- a/components/layout/demo/custom-trigger-debug.md +++ b/components/layout/demo/custom-trigger-debug.md @@ -10,8 +10,8 @@ debug: true 修改内容前,请尝试此 Demo 查看样式是否抖动。 -```jsx -import { Layout, Menu } from 'antd'; +```tsx +import { Layout, Menu, MenuProps } from 'antd'; import { TeamOutlined, UserOutlined, @@ -23,7 +23,57 @@ import { } from '@ant-design/icons'; const { Header, Sider, Content } = Layout; -const { SubMenu } = Menu; + +const items: MenuProps['items'] = [ + { + key: '1', + icon: , + label: 'Option 1', + }, + { + key: '2', + icon: , + label: 'Option 2', + }, + { + key: 'sub1', + icon: , + label: 'User', + children: [ + { + key: '3', + label: 'Tom', + }, + { + key: '4', + label: 'Bill', + }, + { + key: '5', + label: 'Alex', + }, + ], + }, + { + key: 'sub2', + icon: , + label: 'Team', + children: [ + { + key: '6', + label: 'Team 1', + }, + { + key: '7', + label: 'Team 2', + }, + ], + }, + { + key: '9', + icon: , + }, +]; class SiderDemo extends React.Component { state = { @@ -41,24 +91,13 @@ class SiderDemo extends React.Component {
- - }> - Option 1 - - }> - Option 2 - - } title="User"> - Tom - Bill - Alex - - } title="Team"> - Team 1 - Team 2 - - } /> - +
diff --git a/components/layout/demo/custom-trigger.md b/components/layout/demo/custom-trigger.md index 315fe28e22..073c6dc60a 100644 --- a/components/layout/demo/custom-trigger.md +++ b/components/layout/demo/custom-trigger.md @@ -41,17 +41,28 @@ class SiderDemo extends React.Component {
- - }> - nav 1 - - }> - nav 2 - - }> - nav 3 - - + , + label: 'nav 1', + }, + { + key: '2', + icon: , + label: 'nav 2', + }, + { + key: '3', + icon: , + label: 'nav 3', + }, + ]} + />
diff --git a/components/layout/demo/fixed-sider.md b/components/layout/demo/fixed-sider.md index 9966f6671c..97e2dd027b 100644 --- a/components/layout/demo/fixed-sider.md +++ b/components/layout/demo/fixed-sider.md @@ -14,8 +14,8 @@ title: When dealing with long content, a fixed sider can provide a better user experience. -```jsx -import { Layout, Menu } from 'antd'; +```tsx +import { Layout, Menu, MenuProps } from 'antd'; import { AppstoreOutlined, BarChartOutlined, @@ -29,6 +29,21 @@ import { const { Header, Content, Footer, Sider } = Layout; +const items: MenuProps['items'] = [ + UserOutlined, + VideoCameraOutlined, + UploadOutlined, + BarChartOutlined, + CloudOutlined, + AppstoreOutlined, + TeamOutlined, + ShopOutlined, +].map((icon, index) => ({ + key: String(index + 1), + icon: React.createElement(icon), + label: `nav ${index + 1}`, +})); + ReactDOM.render(
- - }> - nav 1 - - }> - nav 2 - - }> - nav 3 - - }> - nav 4 - - }> - nav 5 - - }> - nav 6 - - }> - nav 7 - - }> - nav 8 - - +
diff --git a/components/layout/demo/fixed.md b/components/layout/demo/fixed.md index 1abbe7f777..d7337d21a6 100644 --- a/components/layout/demo/fixed.md +++ b/components/layout/demo/fixed.md @@ -14,7 +14,7 @@ title: Fixed Header is generally used to fix the top navigation to facilitate page switching. -```jsx +```tsx import { Layout, Menu, Breadcrumb } from 'antd'; const { Header, Content, Footer } = Layout; @@ -23,11 +23,15 @@ ReactDOM.render(
- - nav 1 - nav 2 - nav 3 - + ({ + key: String(index + 1), + label: `nav ${index + 1}`, + }))} + />
diff --git a/components/layout/demo/responsive.md b/components/layout/demo/responsive.md index 124e92efc5..e754b45a66 100644 --- a/components/layout/demo/responsive.md +++ b/components/layout/demo/responsive.md @@ -36,20 +36,18 @@ ReactDOM.render( }} >
- - }> - nav 1 - - }> - nav 2 - - }> - nav 3 - - }> - nav 4 - - + ({ + key: String(index + 1), + icon: React.createElement(icon), + label: `nav ${index + 1}`, + }), + )} + />
diff --git a/components/layout/demo/side.md b/components/layout/demo/side.md index 2172a3a353..fff58d24a3 100644 --- a/components/layout/demo/side.md +++ b/components/layout/demo/side.md @@ -22,8 +22,8 @@ Generally, the mainnav is placed on the left side of the page, and the secondary The level of the aside navigation is scalable. The first, second, and third level navigations could be present more fluently and relevantly, and aside navigation can be fixed, allowing the user to quickly switch and spot the current position, improving the user experience. However, this navigation occupies some horizontal space of the contents. -```jsx -import { Layout, Menu, Breadcrumb } from 'antd'; +```tsx +import { Layout, Menu, Breadcrumb, MenuProps } from 'antd'; import { DesktopOutlined, PieChartOutlined, @@ -33,14 +33,41 @@ import { } from '@ant-design/icons'; const { Header, Content, Footer, Sider } = Layout; -const { SubMenu } = Menu; + +type MenuItem = Required['items'][number]; + +function getItem( + label: React.ReactNode, + key: React.Key, + icon?: React.ReactNode, + children?: MenuItem[], +): MenuItem { + return { + key, + icon, + children, + label, + } as MenuItem; +} + +const items: MenuItem[] = [ + getItem('Option 1', '1', ), + getItem('Option 2', '2', ), + getItem('User', 'sub1', , [ + getItem('Tom', '3'), + getItem('Bill', '4'), + getItem('Alex', '5'), + ]), + getItem('Team', 'sub2', , [getItem('Team 1', '6'), getItem('Team 2', '8')]), + getItem('Files', '9', ), +]; class SiderDemo extends React.Component { state = { collapsed: false, }; - onCollapse = collapsed => { + onCollapse = (collapsed: boolean) => { console.log(collapsed); this.setState({ collapsed }); }; @@ -51,26 +78,7 @@ class SiderDemo extends React.Component {
- - }> - Option 1 - - }> - Option 2 - - } title="User"> - Tom - Bill - Alex - - } title="Team"> - Team 1 - Team 2 - - }> - Files - - +
diff --git a/components/layout/demo/top-side-2.md b/components/layout/demo/top-side-2.md index 999930c56c..a3d2a03f47 100644 --- a/components/layout/demo/top-side-2.md +++ b/components/layout/demo/top-side-2.md @@ -13,22 +13,42 @@ title: Both the top navigation and the sidebar, commonly used in application site. -```jsx -import { Layout, Menu, Breadcrumb } from 'antd'; +```tsx +import { Layout, Menu, Breadcrumb, MenuProps } from 'antd'; import { UserOutlined, LaptopOutlined, NotificationOutlined } from '@ant-design/icons'; -const { SubMenu } = Menu; const { Header, Content, Sider } = Layout; +const items1: MenuProps['items'] = ['1', '2', '3'].map(key => ({ + key, + label: `nav ${key}`, +})); + +const items2: MenuProps['items'] = [UserOutlined, LaptopOutlined, NotificationOutlined].map( + (icon, index) => { + const key = String(index + 1); + + return { + key: `sub${key}`, + icon: React.createElement(icon), + label: `subnav ${key}`, + + children: new Array(4).fill(null).map((_, j) => { + const subKey = index * 4 + j + 1; + return { + key: subKey, + label: `option${subKey}`, + }; + }), + }; + }, +); + ReactDOM.render(
- - nav 1 - nav 2 - nav 3 - +
@@ -37,26 +57,8 @@ ReactDOM.render( defaultSelectedKeys={['1']} defaultOpenKeys={['sub1']} style={{ height: '100%', borderRight: 0 }} - > - } title="subnav 1"> - option1 - option2 - option3 - option4 - - } title="subnav 2"> - option5 - option6 - option7 - option8 - - } title="subnav 3"> - option9 - option10 - option11 - option12 - -
+ items={items2} + /> diff --git a/components/layout/demo/top-side.md b/components/layout/demo/top-side.md index a5f6a4894d..320314caf4 100644 --- a/components/layout/demo/top-side.md +++ b/components/layout/demo/top-side.md @@ -13,22 +13,42 @@ title: Both the top navigation and the sidebar, commonly used in documentation site. -```jsx -import { Layout, Menu, Breadcrumb } from 'antd'; +```tsx +import { Layout, Menu, Breadcrumb, MenuProps } from 'antd'; import { UserOutlined, LaptopOutlined, NotificationOutlined } from '@ant-design/icons'; -const { SubMenu } = Menu; const { Header, Content, Footer, Sider } = Layout; +const items1: MenuProps['items'] = ['1', '2', '3'].map(key => ({ + key, + label: `nav ${key}`, +})); + +const items2: MenuProps['items'] = [UserOutlined, LaptopOutlined, NotificationOutlined].map( + (icon, index) => { + const key = String(index + 1); + + return { + key: `sub${key}`, + icon: React.createElement(icon), + label: `subnav ${key}`, + + children: new Array(4).fill(null).map((_, j) => { + const subKey = index * 4 + j + 1; + return { + key: subKey, + label: `option${subKey}`, + }; + }), + }; + }, +); + ReactDOM.render(
- - nav 1 - nav 2 - nav 3 - +
@@ -43,26 +63,8 @@ ReactDOM.render( defaultSelectedKeys={['1']} defaultOpenKeys={['sub1']} style={{ height: '100%' }} - > - } title="subnav 1"> - option1 - option2 - option3 - option4 - - } title="subnav 2"> - option5 - option6 - option7 - option8 - - } title="subnav 3"> - option9 - option10 - option11 - option12 - -
+ items={items2} + /> Content diff --git a/components/layout/demo/top.md b/components/layout/demo/top.md index 90531c9012..cc016516f2 100644 --- a/components/layout/demo/top.md +++ b/components/layout/demo/top.md @@ -28,12 +28,18 @@ ReactDOM.render(
- - {new Array(15).fill(null).map((_, index) => { + { const key = index + 1; - return {`nav ${key}`}; + return { + key, + label: `nav ${key}`, + }; })} - + />
diff --git a/components/menu/demo/horizontal.md b/components/menu/demo/horizontal.md index 313348fb32..1f494fd84f 100755 --- a/components/menu/demo/horizontal.md +++ b/components/menu/demo/horizontal.md @@ -13,51 +13,77 @@ title: Horizontal top navigation menu. -```jsx -import { Menu } from 'antd'; +```tsx +import { Menu, MenuProps } from 'antd'; import { MailOutlined, AppstoreOutlined, SettingOutlined } from '@ant-design/icons'; -const { SubMenu } = Menu; +const items: MenuProps['items'] = [ + { + label: 'Navigation One', + key: 'mail', + icon: , + }, + { + 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 ( - - }> - Navigation One - - }> - Navigation Two - - } title="Navigation Three - Submenu"> - - Option 1 - Option 2 - - - Option 3 - Option 4 - - - - - Navigation Four - Link - - - - ); - } -} + 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 ( -
- - - }> - Option 1 - - }> - Option 2 - - }> - Option 3 - - } title="Navigation One"> - Option 5 - Option 6 - Option 7 - Option 8 - - } title="Navigation Two"> - Option 9 - Option 10 - - Option 11 - Option 12 - - - -
- ); - } +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 ( - - } 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 - - - ); - } -} + 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 ( - - } 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 - - + ); }; 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 ( - <> - -
-
- - React.cloneElement(node, { - style: { - ...node.props.style, - textDecoration: 'underline', - }, - }) - } - // Test only. Remove in future. - _internalRenderSubMenuItem={node => - React.cloneElement(node, { - style: { - ...node.props.style, - background: 'rgba(255,255,255,0.3)', - }, - }) - } - // Test only. Remove in future. - _internalDisableMenuItemTitleTooltip - > - } title="Navigation One Long Long Long Long"> - Option 1 - Option 2 - Option 3 - Option 4 - - } title="Navigation Two"> - Option 5 - Option 6 - - Option 7 - Option 8 - - - Option 11 - Option 12 - - - ); - } -} + return ( + <> + +
+
+ + React.cloneElement(node, { + style: { + ...node.props.style, + textDecoration: 'underline', + }, + }) + } + // Test only. Remove in future. + _internalRenderSubMenuItem={node => + React.cloneElement(node, { + style: { + ...node.props.style, + background: 'rgba(255,255,255,0.3)', + }, + }) + } + // Test only. Remove in future. + _internalDisableMenuItemTitleTooltip + /> + + ); +}; ReactDOM.render(, mountNode); ``` diff --git a/components/menu/demo/submenu-theme.md b/components/menu/demo/submenu-theme.md index e44fb4f7e7..4f517288c5 100755 --- a/components/menu/demo/submenu-theme.md +++ b/components/menu/demo/submenu-theme.md @@ -13,24 +13,52 @@ title: The Sub-menu will inherit the theme of `Menu`, but you can override this at the `SubMenu` level via the `theme` prop. -```jsx -import { Menu, Switch } from 'antd'; +```tsx +import { Menu, Switch, MenuProps } from 'antd'; import { MailOutlined } from '@ant-design/icons'; -const { SubMenu } = Menu; +type MenuItem = Required['items'][number]; + +function getItem( + label: React.ReactNode, + key?: React.Key | null, + icon?: React.ReactNode, + children?: MenuItem[], + theme?: 'light' | 'dark', +): MenuItem { + return { + key, + icon, + children, + label, + theme, + } as MenuItem; +} const SubMenuTheme = () => { - const [theme, setTheme] = React.useState('light'); + const [theme, setTheme] = React.useState<'light' | 'dark'>('light'); const [current, setCurrent] = React.useState('1'); - const changeTheme = value => { + const changeTheme = (value: boolean) => { setTheme(value ? 'dark' : 'light'); }; - const handleClick = e => { + const onClick: MenuProps['onClick'] = e => { setCurrent(e.key); }; + const items: MenuItem[] = [ + getItem( + 'Navigation One', + 'sub1', + , + [getItem('Option 1', '1'), getItem('Option 2', '2'), getItem('Option 3', '3')], + theme, + ), + getItem('Option 5', '5'), + getItem('Option 6', '6'), + ]; + return ( <> {

- } title="Navigation One" theme={theme}> - Option 1 - Option 2 - Option 3 - - Option 5 - Option 6 - + items={items} + /> ); }; diff --git a/components/menu/demo/switch-mode.md b/components/menu/demo/switch-mode.md index 09ad107654..c1bec48f23 100755 --- a/components/menu/demo/switch-mode.md +++ b/components/menu/demo/switch-mode.md @@ -13,8 +13,8 @@ title: Show the dynamic switching mode (between `inline` and `vertical`). -```jsx -import { Menu, Switch, Divider } from 'antd'; +```tsx +import { Menu, Switch, Divider, MenuProps } from 'antd'; import { MailOutlined, CalendarOutlined, @@ -23,17 +23,54 @@ import { LinkOutlined, } from '@ant-design/icons'; -const { SubMenu } = Menu; +type MenuItem = Required['items'][number]; + +function getItem( + label: React.ReactNode, + key?: React.Key | null, + icon?: React.ReactNode, + children?: MenuItem[], +): MenuItem { + return { + key, + icon, + children, + label, + } as MenuItem; +} + +const items: MenuItem[] = [ + getItem('Navigation One', '1', ), + getItem('Navigation Two', '2', ), + getItem('Navigation Two', 'sub1', , [ + getItem('Option 3', '3'), + getItem('Option 4', '4'), + getItem('Submenu', 'sub1-2', null, [getItem('Option 5', '5'), getItem('Option 6', '6')]), + ]), + getItem('Navigation Three', 'sub2', , [ + getItem('Option 7', '7'), + getItem('Option 8', '8'), + getItem('Option 9', '9'), + getItem('Option 10', '10'), + ]), + getItem( + + Ant Design + , + 'link', + , + ), +]; const Demo = () => { - const [mode, setMode] = React.useState('inline'); - const [theme, setTheme] = React.useState('light'); + const [mode, setMode] = React.useState<'inline' | 'vertical'>('inline'); + const [theme, setTheme] = React.useState<'dark' | 'light'>('light'); - const changeMode = value => { + const changeMode = (value: boolean) => { setMode(value ? 'vertical' : 'inline'); }; - const changeTheme = value => { + const changeTheme = (value: boolean) => { setTheme(value ? 'dark' : 'light'); }; @@ -50,33 +87,8 @@ const Demo = () => { defaultOpenKeys={['sub1']} mode={mode} theme={theme} - > - }> - Navigation One - - }> - Navigation Two - - } title="Navigation Two"> - Option 3 - Option 4 - - Option 5 - Option 6 - - - } title="Navigation Three"> - Option 7 - Option 8 - Option 9 - Option 10 - - }> - - Ant Design - - -
+ 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 ( + + {label} + + ); + } + + 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/demo/content.md b/components/page-header/demo/content.md index 75e49e6b56..688c8ee8ac 100644 --- a/components/page-header/demo/content.md +++ b/components/page-header/demo/content.md @@ -20,23 +20,31 @@ import { MoreOutlined } from '@ant-design/icons'; const { Paragraph } = Typography; const menu = ( - - - - 1st menu item - - - - - 2nd menu item - - - - - 3rd menu item - - - + + 1st menu item + + ), + }, + { + label: ( + + 2nd menu item + + ), + }, + { + label: ( + + 3rd menu item + + ), + }, + ]} + /> ); const DropdownMenu = () => ( diff --git a/components/table/demo/nest-table-border-debug.md b/components/table/demo/nest-table-border-debug.md index a285bf6aaf..fa29b9f95e 100644 --- a/components/table/demo/nest-table-border-debug.md +++ b/components/table/demo/nest-table-border-debug.md @@ -18,12 +18,7 @@ To see if bordered style applied to other tables. import { Table, Badge, Menu, Dropdown, Switch, Form, Space } from 'antd'; import { DownOutlined } from '@ant-design/icons'; -const menu = ( - - Action 1 - Action 2 - -); +const menu = ; function NestedTable() { const createExpandedRowRender = bordered => () => { diff --git a/components/table/demo/nested-table.md b/components/table/demo/nested-table.md index 66ed9a07a6..94defb9d21 100644 --- a/components/table/demo/nested-table.md +++ b/components/table/demo/nested-table.md @@ -17,12 +17,7 @@ Showing more detailed info of every row. import { Table, Badge, Menu, Dropdown, Space } from 'antd'; import { DownOutlined } from '@ant-design/icons'; -const menu = ( - - Action 1 - Action 2 - -); +const menu = ; function NestedTable() { const expandedRowRender = () => { diff --git a/components/table/hooks/useFilter/FilterDropdown.tsx b/components/table/hooks/useFilter/FilterDropdown.tsx index 4db31f7011..09923f3a5e 100644 --- a/components/table/hooks/useFilter/FilterDropdown.tsx +++ b/components/table/hooks/useFilter/FilterDropdown.tsx @@ -4,6 +4,7 @@ import isEqual from 'lodash/isEqual'; import FilterFilled from '@ant-design/icons/FilterFilled'; import Button from '../../../button'; import Menu from '../../../menu'; +import type { MenuProps } from '../../../menu'; import Tree from '../../../tree'; import type { DataNode, EventDataNode } from '../../../tree'; import Checkbox from '../../../checkbox'; @@ -55,42 +56,42 @@ function renderFilterItems({ filterMultiple: boolean; searchValue: string; filterSearch: FilterSearchType; -}) { +}): Required['items'] { return filters.map((filter, index) => { const key = String(filter.value); if (filter.children) { - return ( - - {renderFilterItems({ - filters: filter.children, - prefixCls, - filteredKeys, - filterMultiple, - searchValue, - filterSearch, - })} - - ); + return { + key: key || index, + label: filter.text, + popupClassName: `${prefixCls}-dropdown-submenu`, + children: renderFilterItems({ + filters: filter.children, + prefixCls, + filteredKeys, + filterMultiple, + searchValue, + filterSearch, + }), + }; } const Component = filterMultiple ? Checkbox : Radio; - const item = ( - - - {filter.text} - - ); + const item = { + key: filter.value !== undefined ? key : index, + label: ( + <> + + {filter.text} + + ), + }; if (searchValue.trim()) { if (typeof filterSearch === 'function') { - return filterSearch(searchValue, filter) ? item : undefined; + return filterSearch(searchValue, filter) ? item : null; } - return searchValueMatched(searchValue, filter.text) ? item : undefined; + return searchValueMatched(searchValue, filter.text) ? item : null; } return item; }); @@ -381,8 +382,7 @@ function FilterDropdown(props: FilterDropdownProps) { getPopupContainer={getPopupContainer} openKeys={openKeys} onOpenChange={onOpenChange} - > - {renderFilterItems({ + items={renderFilterItems({ filters: column.filters || [], filterSearch, prefixCls, @@ -390,7 +390,7 @@ function FilterDropdown(props: FilterDropdownProps) { filterMultiple, searchValue, })} - + /> ); }; diff --git a/components/table/hooks/useSelection.tsx b/components/table/hooks/useSelection.tsx index 582a9b989c..c8d0e2f75b 100644 --- a/components/table/hooks/useSelection.tsx +++ b/components/table/hooks/useSelection.tsx @@ -405,21 +405,20 @@ export default function useSelection( let customizeSelections: React.ReactNode; if (mergedSelections) { const menu = ( - - {mergedSelections.map((selection, index) => { + { const { key, text, onSelect: onSelectionClick } = selection; - return ( - { - onSelectionClick?.(recordKeys); - }} - > - {text} - - ); + + return { + key: key || index, + onClick: () => { + onSelectionClick?.(recordKeys); + }, + label: text, + }; })} - + /> ); customizeSelections = (
diff --git a/components/transfer/list.tsx b/components/transfer/list.tsx index 9f05b620f0..6d0c5377db 100644 --- a/components/transfer/list.tsx +++ b/components/transfer/list.tsx @@ -365,88 +365,85 @@ export default class TransferList< let menu: React.ReactElement | null = null; if (showRemove) { - menu = ( - - {/* Remove Current Page */} - {pagination && ( - { + const items = [ + /* Remove Current Page */ + pagination + ? { + key: 'removeCurrent', + onClick: () => { const pageKeys = getEnabledItemKeys( (this.defaultListBodyRef.current?.getItems() || []).map(entity => entity.item), ); onItemRemove?.(pageKeys); - }} - > - {removeCurrent} - - )} + }, + label: removeCurrent, + } + : null, - {/* Remove All */} - { - onItemRemove?.(getEnabledItemKeys(filteredItems)); - }} - > - {removeAll} - - - ); + /* Remove All */ + { + key: 'removeAll', + onClick: () => { + onItemRemove?.(getEnabledItemKeys(filteredItems)); + }, + label: removeAll, + }, + ].filter(item => item); + + menu = ; } else { - menu = ( - - { - const keys = getEnabledItemKeys(filteredItems); - onItemSelectAll(keys, keys.length !== checkedKeys.length); - }} - > - {selectAll} - - {pagination && ( - { + const items = [ + { + key: 'selectAll', + onClick: () => { + const keys = getEnabledItemKeys(filteredItems); + onItemSelectAll(keys, keys.length !== checkedKeys.length); + }, + label: selectAll, + }, + pagination + ? { + key: 'selectCurrent', + onClick: () => { const pageItems = this.defaultListBodyRef.current?.getItems() || []; onItemSelectAll(getEnabledItemKeys(pageItems.map(entity => entity.item)), true); - }} - > - {selectCurrent} - - )} - { - let availableKeys: string[]; - if (pagination) { - availableKeys = getEnabledItemKeys( - (this.defaultListBodyRef.current?.getItems() || []).map(entity => entity.item), - ); + }, + label: selectCurrent, + } + : null, + + { + key: 'selectInvert', + onClick: () => { + let availableKeys: string[]; + if (pagination) { + availableKeys = getEnabledItemKeys( + (this.defaultListBodyRef.current?.getItems() || []).map(entity => entity.item), + ); + } else { + availableKeys = getEnabledItemKeys(filteredItems); + } + + const checkedKeySet = new Set(checkedKeys); + const newCheckedKeys: string[] = []; + const newUnCheckedKeys: string[] = []; + + availableKeys.forEach(key => { + if (checkedKeySet.has(key)) { + newUnCheckedKeys.push(key); } else { - availableKeys = getEnabledItemKeys(filteredItems); + newCheckedKeys.push(key); } + }); - const checkedKeySet = new Set(checkedKeys); - const newCheckedKeys: string[] = []; - const newUnCheckedKeys: string[] = []; + onItemSelectAll(newCheckedKeys, true); + onItemSelectAll(newUnCheckedKeys, false); + }, + label: selectInvert, + }, + ]; - availableKeys.forEach(key => { - if (checkedKeySet.has(key)) { - newUnCheckedKeys.push(key); - } else { - newCheckedKeys.push(key); - } - }); - - onItemSelectAll(newCheckedKeys, true); - onItemSelectAll(newUnCheckedKeys, false); - }} - > - {selectInvert} - - - ); + menu = ; } const dropdown = ( diff --git a/package.json b/package.json index b920a229b1..1d20440c46 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "rc-input": "~0.0.1-alpha.5", "rc-input-number": "~7.3.0", "rc-mentions": "~1.6.1", - "rc-menu": "~9.3.2", + "rc-menu": "~9.5.1", "rc-motion": "^2.4.4", "rc-notification": "~4.5.7", "rc-pagination": "~3.1.9",