diff --git a/docs/demo/fragment.md b/docs/demo/fragment.md deleted file mode 100644 index 8d04c249..00000000 --- a/docs/demo/fragment.md +++ /dev/null @@ -1,3 +0,0 @@ -## fragment - -<code src="../examples/fragment.tsx"></code> diff --git a/docs/examples/antd.tsx b/docs/examples/antd.tsx index f5bd0bba..7796cd24 100644 --- a/docs/examples/antd.tsx +++ b/docs/examples/antd.tsx @@ -2,7 +2,9 @@ import React from 'react'; import type { CSSMotionProps } from 'rc-motion'; -import Menu, { SubMenu, Item as MenuItem, Divider, MenuProps } from '../../src'; +import Menu from 'rc-menu'; +import type { ItemType } from '@/interface'; +import type { MenuProps } from 'rc-menu'; import '../../assets/index.less'; function handleClick(info) { @@ -48,75 +50,142 @@ const motionMap: Record<MenuProps['mode'], CSSMotionProps> = { vertical: verticalMotion, }; -const nestSubMenu = ( - <SubMenu - title={<span className="submenu-title-wrapper">offset sub menu 2</span>} - key="4" - popupOffset={[10, 15]} - > - <MenuItem key="4-1">inner inner</MenuItem> - <Divider /> - <SubMenu - key="4-2" - title={<span className="submenu-title-wrapper">sub menu 1</span>} - > - <SubMenu - title={<span className="submenu-title-wrapper">sub 4-2-0</span>} - key="4-2-0" - > - <MenuItem key="4-2-0-1">inner inner</MenuItem> - <MenuItem key="4-2-0-2">inner inner2</MenuItem> - </SubMenu> - <MenuItem key="4-2-1">inn</MenuItem> - <SubMenu - title={<span className="submenu-title-wrapper">sub menu 4</span>} - key="4-2-2" - > - <MenuItem key="4-2-2-1">inner inner</MenuItem> - <MenuItem key="4-2-2-2">inner inner2</MenuItem> - </SubMenu> - <SubMenu - title={<span className="submenu-title-wrapper">sub menu 3</span>} - key="4-2-3" - > - <MenuItem key="4-2-3-1">inner inner</MenuItem> - <MenuItem key="4-2-3-2">inner inner2</MenuItem> - </SubMenu> - </SubMenu> - </SubMenu> -); +const nestSubMenu: ItemType = { + key: '4', + type: 'submenu', + label: <span className="submenu-title-wrapper">offset sub menu 2</span>, + popupOffset: [10, 15], + children: [ + { + key: '4-1', + label: 'inner inner', + }, + { + type: 'divider', + }, + { + key: '4-2', + type: 'submenu', + label: <span className="submenu-title-wrapper">sub menu 1</span>, + children: [ + { + key: '4-2-0', + type: 'submenu', + label: <span className="submenu-title-wrapper">sub 4-2-0</span>, + children: [ + { + key: '4-2-0-1', + label: 'inner inner', + }, + { + key: '4-2-0-2', + label: 'inner inner2', + }, + ], + }, + { + key: '4-2-1', + label: 'inn', + }, + { + key: '4-2-2', + type: 'submenu', + label: <span className="submenu-title-wrapper">sub menu 4</span>, + children: [ + { + key: '4-2-2-1', + label: 'inner inner', + }, + { + key: '4-2-2-2', + label: 'inner inner2', + }, + ], + }, + { + key: '4-2-3', + type: 'submenu', + label: <span className="submenu-title-wrapper">sub menu 3</span>, + children: [ + { + key: '4-2-3-1', + label: 'inner inner', + }, + { + key: '4-2-3-2', + label: 'inner inner2', + }, + ], + }, + ], + }, + ], +}; function onOpenChange(value) { console.log('onOpenChange', value); } -const children1 = [ - <SubMenu - title={<span className="submenu-title-wrapper">sub menu</span>} - key="1" - > - <MenuItem key="1-1">0-1</MenuItem> - <MenuItem key="1-2">0-2</MenuItem> - </SubMenu>, +const items1: ItemType[] = [ + { + type: 'submenu', + key: '1', + label: <span className="submenu-title-wrapper">sub menu</span>, + children: [ + { + key: '1-1', + label: '0-1', + }, + { + key: '1-2', + label: '0-2', + }, + ], + }, nestSubMenu, - <MenuItem key="2">1</MenuItem>, - <MenuItem key="3">outer</MenuItem>, - <MenuItem key="5" disabled> - disabled - </MenuItem>, - <MenuItem key="6">outer3</MenuItem>, + { + key: '2', + label: '1', + }, + { + key: '3', + label: 'outer', + }, + { + key: '5', + label: 'disabled', + disabled: true, + }, + { + key: '6', + label: 'outer3', + }, ]; -const children2 = [ - <SubMenu - title={<span className="submenu-title-wrapper">sub menu</span>} - key="1" - > - <MenuItem key="1-1">0-1</MenuItem> - <MenuItem key="1-2">0-2</MenuItem> - </SubMenu>, - <MenuItem key="2">1</MenuItem>, - <MenuItem key="3">outer</MenuItem>, +const items2 = [ + { + type: 'submenu', + key: '1', + label: <span className="submenu-title-wrapper">sub menu</span>, + children: [ + { + key: '1-1', + label: '0-1', + }, + { + key: '1-2', + label: '0-2', + }, + ], + }, + { + key: '2', + label: '1', + }, + { + key: '3', + label: 'outer', + }, ]; const customizeIndicator = <span>Add More Items</span>; @@ -127,7 +196,7 @@ interface CommonMenuProps extends MenuProps { } interface CommonMenuState { - children: React.ReactNode; + items: ItemType[]; overflowedIndicator: React.ReactNode; } @@ -136,13 +205,14 @@ export class CommonMenu extends React.Component< CommonMenuState > { state: CommonMenuState = { - children: children1, + items: items1 as ItemType[], overflowedIndicator: undefined, }; toggleChildren = () => { - this.setState(({ children }) => ({ - children: children === children1 ? children2 : children1, + // @ts-ignore + this.setState(({ items }) => ({ + items: items === items1 ? items2 : items1, })); }; @@ -155,13 +225,13 @@ export class CommonMenu extends React.Component< render() { const { triggerSubMenuAction } = this.props; - const { children, overflowedIndicator } = this.state; + const { items, overflowedIndicator } = this.state; return ( <div> {this.props.updateChildrenAndOverflowedIndicator && ( <div> <button type="button" onClick={this.toggleChildren}> - toggle children + toggle items </button> <button type="button" onClick={this.toggleOverflowedIndicator}> toggle overflowedIndicator @@ -175,9 +245,8 @@ export class CommonMenu extends React.Component< selectedKeys={['3']} overflowedIndicator={overflowedIndicator} {...this.props} - > - {children} - </Menu> + items={items} + /> </div> ); } diff --git a/docs/examples/custom-icon.tsx b/docs/examples/custom-icon.tsx index 7b1f075b..a9f37faf 100644 --- a/docs/examples/custom-icon.tsx +++ b/docs/examples/custom-icon.tsx @@ -1,7 +1,8 @@ /* eslint-disable no-console, no-param-reassign */ import * as React from 'react'; -import Menu, { SubMenu, Item as MenuItem, Divider } from '../../src'; +import Menu from 'rc-menu'; import '../../assets/index.less'; +import type { ItemType } from '@/interface'; const getSvgIcon = (style = {}, text?: React.ReactNode) => { if (text) { @@ -67,50 +68,126 @@ class Demo extends React.Component { console.log(info); }; - renderNestSubMenu = (props = {}) => ( - <SubMenu - title={<span>offset sub menu 2</span>} - key="4" - popupOffset={[10, 15]} - {...props} - > - <MenuItem key="4-1">inner inner</MenuItem> - <Divider /> - <SubMenu key="4-2" title={<span>sub menu 3</span>}> - <SubMenu title="sub 4-2-0" key="4-2-0"> - <MenuItem key="4-2-0-1">inner inner</MenuItem> - <MenuItem key="4-2-0-2">inner inner2</MenuItem> - </SubMenu> - <MenuItem key="4-2-1">inn</MenuItem> - <SubMenu title={<span>sub menu 4</span>} key="4-2-2"> - <MenuItem key="4-2-2-1">inner inner</MenuItem> - <MenuItem key="4-2-2-2">inner inner2</MenuItem> - </SubMenu> - <SubMenu title="sub 4-2-3" key="4-2-3"> - <MenuItem key="4-2-3-1">inner inner</MenuItem> - <MenuItem key="4-2-3-2">inner inner2</MenuItem> - </SubMenu> - </SubMenu> - </SubMenu> - ); + renderNestSubMenu = (props = {}) => ({ + key: '4', + type: 'submenu', + label: <span>offset sub menu 2</span>, + popupOffset: [10, 15], + children: [ + { + key: '4-1', + label: 'inner inner', + }, + { + type: 'divider', + }, + { + key: '4-2', + type: 'submenu', + label: <span>sub menu 3</span>, + children: [ + { + key: '4-2-0', + type: 'submenu', + label: 'sub 4-2-0', + children: [ + { + key: '4-2-0-1', + label: 'inner inner', + }, + { + key: '4-2-0-2', + label: 'inner inner2', + }, + ], + }, + { + key: '4-2-1', + label: 'inn', + }, + { + key: '4-2-2', + type: 'submenu', + label: <span>sub menu 4</span>, + children: [ + { + key: '4-2-2-1', + label: 'inner inner', + }, + { + key: '4-2-2-2', + label: 'inner inner2', + }, + ], + }, + { + key: '4-2-3', + type: 'submenu', + label: 'sub 4-2-3', + children: [ + { + key: '4-2-3-1', + label: 'inner inner', + }, + { + key: '4-2-3-2', + label: 'inner inner2', + }, + ], + }, + ], + }, + ], + ...props, + }); - renderCommonMenu = (props = {}) => ( - <Menu - onClick={this.handleClick} - onOpenChange={this.onOpenChange} - {...props} - > - <SubMenu title={<span>sub menu</span>} key="1"> - <MenuItem key="1-1">0-1</MenuItem> - <MenuItem key="1-2">0-2</MenuItem> - </SubMenu> - {this.renderNestSubMenu()} - <MenuItem key="2">1</MenuItem> - <MenuItem key="3">outer</MenuItem> - <MenuItem disabled>disabled</MenuItem> - <MenuItem key="5">outer3</MenuItem> - </Menu> - ); + renderCommonMenu = (props = {}) => { + const items: ItemType[] = [ + { + key: '1', + type: 'submenu', + label: <span>sub menu</span>, + children: [ + { + key: '1-1', + label: '0-1', + }, + { + key: '1-2', + label: '0-2', + }, + ], + }, + // @ts-ignore + this.renderNestSubMenu(), + { + key: '2', + label: '1', + }, + { + key: '3', + label: 'outer', + }, + { + key: '44', + label: 'disabled', + disabled: true, + }, + { + key: '5', + label: 'outer3', + }, + ]; + + return ( + <Menu + onClick={this.handleClick} + onOpenChange={this.onOpenChange} + items={items} + {...props} + /> + ); + }; render() { const verticalMenu = this.renderCommonMenu({ diff --git a/docs/examples/debug.tsx b/docs/examples/debug.tsx index a9db1e87..488833a7 100644 --- a/docs/examples/debug.tsx +++ b/docs/examples/debug.tsx @@ -2,8 +2,8 @@ import React, { useRef } from 'react'; import type { CSSMotionProps } from 'rc-motion'; -import Menu, { ItemGroup as MenuItemGroup, MenuItem } from '../../src'; -import type { MenuProps } from '../../src'; +import Menu from 'rc-menu'; +import type { MenuProps } from 'rc-menu'; import '../../assets/index.less'; import '../../assets/menu.less'; import type { MenuInfo, MenuRef } from '@/interface'; @@ -69,9 +69,7 @@ export default () => { return ( <> <div> - <Menu ref={menuRef}> - <MenuItem key="light">Light</MenuItem> - </Menu> + <Menu ref={menuRef} items={[{ key: 'light', label: 'Light' }]} /> <button onClick={() => menuRef.current.focus()}>focus</button> <select value={mode} onChange={e => setMode(e.target.value as any)}> <option value="inline">Inline</option> @@ -109,7 +107,6 @@ export default () => { <div style={{ width: narrow ? 350 : undefined }}> <Menu - // direction="rtl" defaultOpenKeys={['sub', 'nest']} forceSubMenuRender={forceRender} mode={mode} @@ -119,45 +116,91 @@ export default () => { inlineCollapsed={inlineCollapsed} openKeys={openKeys} onOpenChange={newOpenKeys => setOpenKeys(newOpenKeys)} - > - <Menu.Item key="mail"> - <a href="http://www.taobao.com">Navigation One</a> - </Menu.Item> - <Menu.Item key="next" onClick={onClick}> - Next Item - </Menu.Item> - <Menu.SubMenu title="Sub Menu" key="sub" onClick={onSubMenuClick}> - <Menu.Item key="sub1" onClick={onClick}> - Sub Item 1 - </Menu.Item> - <Menu.Item key="sub2">Sub Item 2</Menu.Item> - - <Menu.SubMenu title="Nest Menu" key="nest"> - <MenuItemGroup title="group 1" key="grp1"> - <Menu.Item key="21">2</Menu.Item> - <Menu.Item key="22">3</Menu.Item> - </MenuItemGroup> - <MenuItemGroup title="group 2" key="grp2"> - <Menu.Item key="31">4</Menu.Item> - <Menu.Item key="32">5</Menu.Item> - </MenuItemGroup> - </Menu.SubMenu> - </Menu.SubMenu> - <Menu.Item key="disabled" disabled> - Disabled Item - </Menu.Item> - - <Menu.SubMenu - title="Disabled Sub Menu" - key="disabled-sub" - onClick={onSubMenuClick} - disabled - > - <Menu.Item key="dis-sub1" onClick={onClick}> - Disabled Sub Item 1 - </Menu.Item> - </Menu.SubMenu> - </Menu> + items={[ + { + key: 'mail', + label: <a href="http://www.taobao.com">Navigation One</a>, + }, + { + key: 'next', + label: 'Next Item', + onClick: onClick, + }, + { + key: 'sub', + label: 'Sub Menu', + type: 'submenu', + onClick: onSubMenuClick, + children: [ + { + key: 'sub1', + label: 'Sub Item 1', + onClick: onClick, + }, + { + key: 'sub2', + label: 'Sub Item 2', + }, + { + key: 'nest', + label: 'Nest Menu', + type: 'submenu', + children: [ + { + key: 'grp1', + type: 'group', + label: 'group 1', + children: [ + { + key: '21', + label: '2', + }, + { + key: '22', + label: '3', + }, + ], + }, + { + key: 'grp2', + type: 'group', + label: 'group 2', + children: [ + { + key: '31', + label: '4', + }, + { + key: '32', + label: '5', + }, + ], + }, + ], + }, + ], + }, + { + key: 'disabled', + label: 'Disabled Item', + disabled: true, + }, + { + key: 'disabled-sub', + label: 'Disabled Sub Menu', + type: 'submenu', + onClick: onSubMenuClick, + disabled: true, + children: [ + { + key: 'dis-sub1', + label: 'Disabled Sub Item 1', + onClick: onClick, + }, + ], + }, + ]} + /> </div> </> ); diff --git a/docs/examples/fragment.tsx b/docs/examples/fragment.tsx deleted file mode 100644 index 8dc52457..00000000 --- a/docs/examples/fragment.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; -import '../../assets/index.less'; - -export default () => ( - <Menu style={{ width: 200 }}> - <SubMenu title="sub menu" key="1"> - <MenuItem key="1-1">0-1</MenuItem> - <MenuItem key="1-2">0-2</MenuItem> - </SubMenu> - <MenuItem key="2">Menu Item</MenuItem> - <MenuItem key="3">outer</MenuItem> - <> - <SubMenu key="4" title="sub menu"> - <MenuItem key="4-1">inner inner</MenuItem> - <Divider /> - <SubMenu key="4-2" title="sub menu"> - <MenuItem key="4-2-1">inn</MenuItem> - <SubMenu title="sub menu" key="4-2-2"> - <MenuItem key="4-2-2-1">inner inner</MenuItem> - <MenuItem key="4-2-2-2">inner inner2</MenuItem> - </SubMenu> - </SubMenu> - </SubMenu> - <MenuItem disabled>disabled</MenuItem> - <MenuItem key="4-3">outer3</MenuItem> - </> - </Menu> -); diff --git a/docs/examples/items-ref.tsx b/docs/examples/items-ref.tsx index dc18f9f1..ff0121b2 100644 --- a/docs/examples/items-ref.tsx +++ b/docs/examples/items-ref.tsx @@ -2,7 +2,7 @@ import React, { useRef } from 'react'; import '../../assets/index.less'; -import Menu from '../../src'; +import Menu from 'rc-menu'; export default () => { const ref1 = useRef(); diff --git a/docs/examples/items.tsx b/docs/examples/items.tsx index a0044731..e5a05467 100644 --- a/docs/examples/items.tsx +++ b/docs/examples/items.tsx @@ -1,7 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; -import Menu from '../../src'; +import Menu from 'rc-menu'; import '../../assets/index.less'; export default () => ( diff --git a/docs/examples/keyPath.tsx b/docs/examples/keyPath.tsx index 30a5542e..c2892c4c 100644 --- a/docs/examples/keyPath.tsx +++ b/docs/examples/keyPath.tsx @@ -1,7 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; -import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; +import Menu from 'rc-menu'; import '../../assets/index.less'; @@ -12,21 +12,61 @@ class Test extends React.Component { getMenu() { return ( - <Menu onClick={this.onClick} mode="inline"> - <SubMenu key="1" title="submenu1"> - <MenuItem key="1-1">item1-1</MenuItem> - <MenuItem key="1-2">item1-2</MenuItem> - </SubMenu> - <SubMenu key="2" title="submenu2"> - <MenuItem key="2-1">item2-1</MenuItem> - <MenuItem key="2-2">item2-2</MenuItem> - <SubMenu key="2-3" title="submenu2-3"> - <MenuItem key="2-3-1">item2-3-1</MenuItem> - <MenuItem key="2-3-2">item2-3-2</MenuItem> - </SubMenu> - </SubMenu> - <MenuItem key="3">item3</MenuItem> - </Menu> + <Menu + onClick={this.onClick} + mode="inline" + items={[ + { + key: '1', + label: 'submenu1', + type: 'submenu', + children: [ + { + key: '1-1', + label: 'item1-1', + }, + { + key: '1-2', + label: 'item1-2', + }, + ], + }, + { + key: '2', + label: 'submenu2', + type: 'submenu', + children: [ + { + key: '2-1', + label: 'item2-1', + }, + { + key: '2-2', + label: 'item2-2', + }, + { + key: '2-3', + label: 'submenu2-3', + type: 'submenu', + children: [ + { + key: '2-3-1', + label: 'item2-3-1', + }, + { + key: '2-3-2', + label: 'item2-3-2', + }, + ], + }, + ], + }, + { + key: '3', + label: 'item3', + }, + ]} + /> ); } diff --git a/docs/examples/menuItemGroup.tsx b/docs/examples/menuItemGroup.tsx index b598ddda..61c9abd4 100644 --- a/docs/examples/menuItemGroup.tsx +++ b/docs/examples/menuItemGroup.tsx @@ -1,22 +1,48 @@ /* eslint no-console:0 */ import React from 'react'; -import Menu, { Item as MenuItem, ItemGroup as MenuItemGroup } from 'rc-menu'; +import Menu from 'rc-menu'; import '../../assets/index.less'; export default () => ( <div> <h2>menu item group</h2> - <Menu style={{ margin: 20, width: 300 }} onClick={() => console.log('click')}> - <MenuItemGroup title="group 1" key="2"> - <MenuItem key="21">2</MenuItem> - <MenuItem key="22">3</MenuItem> - </MenuItemGroup> - <MenuItemGroup title="group 2" key="3"> - <MenuItem key="31">4</MenuItem> - <MenuItem key="32">5</MenuItem> - </MenuItemGroup> - </Menu> + <Menu + style={{ margin: 20, width: 300 }} + onClick={() => console.log('click')} + items={[ + { + type: 'group', + label: 'group 1', + key: '2', + children: [ + { + key: '21', + label: '2', + }, + { + key: '22', + label: '3', + }, + ], + }, + { + type: 'group', + label: 'group 2', + key: '3', + children: [ + { + key: '31', + label: '4', + }, + { + key: '32', + label: '5', + }, + ], + }, + ]} + /> </div> ); diff --git a/docs/examples/multiple.tsx b/docs/examples/multiple.tsx index 9b9f5057..60572ce2 100644 --- a/docs/examples/multiple.tsx +++ b/docs/examples/multiple.tsx @@ -1,7 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; -import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; +import Menu from 'rc-menu'; import '../../assets/index.less'; @@ -27,31 +27,82 @@ function Demo() { onSelect={handleSelect} onDeselect={handleDeselect} defaultSelectedKeys={['2', '1-1']} - > - <SubMenu title={titleRight} key="1"> - <MenuItem key="1-1">0-1</MenuItem> - <MenuItem key="1-2">0-2</MenuItem> - </SubMenu> - <MenuItem key="2" disabled> - can not deselect me, i am disabled - </MenuItem> - <MenuItem key="3">outer</MenuItem> - <SubMenu title={titleRight1} key="4"> - <MenuItem key="4-1">inner inner</MenuItem> - <Divider /> - <SubMenu key="4-2" title={titleRight2}> - <MenuItem key="4-2-1">inn</MenuItem> - <SubMenu title={titleRight3} key="4-2-2"> - <MenuItem key="4-2-2-1">inner inner</MenuItem> - <MenuItem key="4-2-2-2">inner inner2</MenuItem> - </SubMenu> - </SubMenu> - </SubMenu> - <MenuItem disabled key="disabled"> - disabled - </MenuItem> - <MenuItem key="4-3">outer3</MenuItem> - </Menu> + items={[ + { + label: titleRight, + type: 'submenu', + key: '1', + children: [ + { + label: '0-1', + key: '1-1', + }, + { + label: '0-2', + key: '1-2', + }, + ], + }, + { + label: 'can not deselect me, i am disabled', + key: '2', + disabled: true, + }, + { + label: 'outer', + key: '3', + }, + { + label: titleRight1, + type: 'submenu', + key: '4', + children: [ + { + label: 'inner inner', + key: '4-1', + }, + { + type: 'divider', + }, + { + label: titleRight2, + type: 'submenu', + key: '4-2', + children: [ + { + label: 'inn', + key: '4-2-1', + }, + { + label: titleRight3, + type: 'submenu', + key: '4-2-2', + children: [ + { + label: 'inner inner', + key: '4-2-2-1', + }, + { + label: 'inner inner2', + key: '4-2-2-2', + }, + ], + }, + ], + }, + ], + }, + { + label: 'disabled', + key: 'disabled', + disabled: true, + }, + { + label: 'outer3', + key: '4-3', + }, + ]} + /> ); return ( <div> diff --git a/docs/examples/openKeys.tsx b/docs/examples/openKeys.tsx index f83ec15d..ee37d07e 100644 --- a/docs/examples/openKeys.tsx +++ b/docs/examples/openKeys.tsx @@ -1,7 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; -import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; +import Menu from 'rc-menu'; import '../../assets/index.less'; @@ -28,17 +28,43 @@ class Test extends React.Component { mode="inline" onOpenChange={this.onOpenChange} openKeys={this.state.openKeys} - > - <SubMenu key="1" title="submenu1"> - <MenuItem key="1-1">item1-1</MenuItem> - <MenuItem key="1-2">item1-2</MenuItem> - </SubMenu> - <SubMenu key="2" title="submenu2"> - <MenuItem key="2-1">item2-1</MenuItem> - <MenuItem key="2-2">item2-2</MenuItem> - </SubMenu> - <MenuItem key="3">item3</MenuItem> - </Menu> + items={[ + { + key: '1', + label: 'submenu1', + type: 'submenu', + children: [ + { + key: '1-1', + label: 'item1-1', + }, + { + key: '1-2', + label: 'item1-2', + }, + ], + }, + { + key: '2', + label: 'submenu2', + type: 'submenu', + children: [ + { + key: '2-1', + label: 'item2-1', + }, + { + key: '2-2', + label: 'item2-2', + }, + ], + }, + { + key: '3', + label: 'item3', + }, + ]} + /> ); } diff --git a/docs/examples/rtl-antd.tsx b/docs/examples/rtl-antd.tsx index 91984cf7..c85e12ef 100644 --- a/docs/examples/rtl-antd.tsx +++ b/docs/examples/rtl-antd.tsx @@ -2,8 +2,9 @@ import React from 'react'; import type { CSSMotionProps } from 'rc-motion'; -import Menu, { SubMenu, Item as MenuItem, Divider, MenuProps } from 'rc-menu'; +import Menu, { type MenuProps } from 'rc-menu'; import '../../assets/index.less'; +import type { ItemType } from '@/interface'; function handleClick(info) { console.log(`clicked ${info.key}`); @@ -48,93 +49,161 @@ const motionMap: Record<MenuProps['mode'], CSSMotionProps> = { vertical: verticalMotion, }; -const nestSubMenu = ( - <SubMenu - title={<span className="submenu-title-wrapper">offset sub menu 2</span>} - key="4" - popupOffset={[-10, 15]} - > - <MenuItem key="4-1">inner inner</MenuItem> - <Divider /> - <SubMenu - key="4-2" - title={<span className="submenu-title-wrapper">sub menu 1</span>} - > - <SubMenu - title={<span className="submenu-title-wrapper">sub 4-2-0</span>} - key="4-2-0" - > - <MenuItem key="4-2-0-1">inner inner</MenuItem> - <MenuItem key="4-2-0-2">inner inner2</MenuItem> - </SubMenu> - <MenuItem key="4-2-1">inn</MenuItem> - <SubMenu - title={<span className="submenu-title-wrapper">sub menu 4</span>} - key="4-2-2" - > - <MenuItem key="4-2-2-1">inner inner</MenuItem> - <MenuItem key="4-2-2-2">inner inner2</MenuItem> - </SubMenu> - <SubMenu - title={<span className="submenu-title-wrapper">sub menu 3</span>} - key="4-2-3" - > - <MenuItem key="4-2-3-1">inner inner</MenuItem> - <MenuItem key="4-2-3-2">inner inner2</MenuItem> - </SubMenu> - </SubMenu> - </SubMenu> -); +const nestSubMenu = { + key: '4', + label: <span className="submenu-title-wrapper">offset sub menu 2</span>, + type: 'submenu', + popupOffset: [-10, 15], + children: [ + { + key: '4-1', + label: 'inner inner', + }, + { + type: 'divider', + }, + { + key: '4-2', + label: <span className="submenu-title-wrapper">sub menu 1</span>, + type: 'submenu', + children: [ + { + key: '4-2-0', + label: <span className="submenu-title-wrapper">sub 4-2-0</span>, + type: 'submenu', + children: [ + { + key: '4-2-0-1', + label: 'inner inner', + }, + { + key: '4-2-0-2', + label: 'inner inner2', + }, + ], + }, + { + key: '4-2-1', + label: 'inn', + }, + { + key: '4-2-2', + label: <span className="submenu-title-wrapper">sub menu 4</span>, + type: 'submenu', + children: [ + { + key: '4-2-2-1', + label: 'inner inner', + }, + { + key: '4-2-2-2', + label: 'inner inner2', + }, + ], + }, + { + key: '4-2-3', + label: <span className="submenu-title-wrapper">sub menu 3</span>, + type: 'submenu', + children: [ + { + key: '4-2-3-1', + label: 'inner inner', + }, + { + key: '4-2-3-2', + label: 'inner inner2', + }, + ], + }, + ], + }, + ], +}; function onOpenChange(value) { console.log('onOpenChange', value); } -const children1 = [ - <SubMenu - title={<span className="submenu-title-wrapper">sub menu</span>} - key="1" - > - <MenuItem key="1-1">0-1</MenuItem> - <MenuItem key="1-2">0-2</MenuItem> - </SubMenu>, +const items1 = [ + { + key: '1', + label: <span className="submenu-title-wrapper">sub menu</span>, + type: 'submenu', + children: [ + { + key: '1-1', + label: '0-1', + }, + { + key: '1-2', + label: '0-2', + }, + ], + }, nestSubMenu, - <MenuItem key="2">1</MenuItem>, - <MenuItem key="3">outer</MenuItem>, - <MenuItem key="5" disabled> - disabled - </MenuItem>, - <MenuItem key="6">outer3</MenuItem>, + { + key: '2', + label: '1', + }, + { + key: '3', + label: 'outer', + }, + { + key: '5', + label: 'disabled', + disabled: true, + }, + { + key: '6', + label: 'outer3', + }, ]; -const children2 = [ - <SubMenu - title={<span className="submenu-title-wrapper">sub menu</span>} - key="1" - > - <MenuItem key="1-1">0-1</MenuItem> - <MenuItem key="1-2">0-2</MenuItem> - </SubMenu>, - <MenuItem key="2">1</MenuItem>, - <MenuItem key="3">outer</MenuItem>, +const items2 = [ + { + key: '1', + label: <span className="submenu-title-wrapper">sub menu</span>, + type: 'submenu', + children: [ + { + key: '1-1', + label: '0-1', + }, + { + key: '1-2', + label: '0-2', + }, + ], + }, + { + key: '2', + label: '1', + }, + { + key: '3', + label: 'outer', + }, ]; const customizeIndicator = <span>Add More Items</span>; interface CommonMenuState { - children: React.ReactNode; - overflowedIndicator?: React.ReactNode; + items: ItemType[]; + overflowedIndicator?: React.ReactNode; } class CommonMenu extends React.Component<any, CommonMenuState> { state = { - children: children1, + items: items1, overflowedIndicator: undefined, } as CommonMenuState; - toggleChildren = () => { - this.setState(({ children }) => ({ - children: children === children1 ? children2 : children1, + toggleItems = () => { + // @ts-ignore + this.setState(({ items }) => ({ + items: items === items1 ? items2 : items1, })); }; @@ -147,13 +216,13 @@ class CommonMenu extends React.Component<any, CommonMenuState> { render() { const { updateChildrenAndOverflowedIndicator, ...restProps } = this.props; - const { children, overflowedIndicator } = this.state; + const { items, overflowedIndicator } = this.state; return ( <div> {updateChildrenAndOverflowedIndicator && ( <div> - <button type="button" onClick={this.toggleChildren}> - toggle children + <button type="button" onClick={this.toggleItems}> + toggle items </button> <button type="button" onClick={this.toggleOverflowedIndicator}> toggle overflowedIndicator @@ -167,9 +236,8 @@ class CommonMenu extends React.Component<any, CommonMenuState> { overflowedIndicator={overflowedIndicator} direction="rtl" {...restProps} - > - {children} - </Menu> + items={items} + /> </div> ); } diff --git a/docs/examples/scrollable.tsx b/docs/examples/scrollable.tsx index a7ee7b67..4d1449d9 100644 --- a/docs/examples/scrollable.tsx +++ b/docs/examples/scrollable.tsx @@ -1,13 +1,16 @@ /* eslint no-console:0 */ import React from 'react'; -import Menu, { Item as MenuItem } from 'rc-menu'; +import Menu from 'rc-menu'; import '../../assets/index.less'; -const children = []; +const items = []; for (let i = 0; i < 20; i += 1) { - children.push(<MenuItem key={String(i)}>{i}</MenuItem>); + items.push({ + label: i, + key: String(i), + }); } const menuStyle = { @@ -19,6 +22,6 @@ const menuStyle = { export default () => ( <div> <h2>keyboard scrollable menu</h2> - <Menu style={menuStyle}>{children}</Menu> + <Menu style={menuStyle} items={items} /> </div> ); diff --git a/docs/examples/selectedKeys.tsx b/docs/examples/selectedKeys.tsx index aa68aa97..db1bc1a5 100644 --- a/docs/examples/selectedKeys.tsx +++ b/docs/examples/selectedKeys.tsx @@ -1,15 +1,15 @@ /* eslint no-console:0 */ import React from 'react'; -import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; +import Menu from 'rc-menu'; import '../../assets/index.less'; interface TestState { - destroyed: boolean; - selectedKeys: string[]; - openKeys: string[]; - } + destroyed: boolean; + selectedKeys: string[]; + openKeys: string[]; +} class Test extends React.Component<any, TestState> { state = { @@ -86,17 +86,43 @@ class Test extends React.Component<any, TestState> { onOpenChange={this.onOpenChange} openKeys={this.state.openKeys} selectedKeys={this.state.selectedKeys} - > - <SubMenu key="1" title="submenu1"> - <MenuItem key="1-1">item1-1</MenuItem> - <MenuItem key="1-2">item1-2</MenuItem> - </SubMenu> - <SubMenu key="2" title="submenu2"> - <MenuItem key="2-1">item2-1</MenuItem> - <MenuItem key="2-2">item2-2</MenuItem> - </SubMenu> - <MenuItem key="3">item3</MenuItem> - </Menu> + items={[ + { + key: '1', + label: 'submenu1', + type: 'submenu', + children: [ + { + key: '1-1', + label: 'item1-1', + }, + { + key: '1-2', + label: 'item1-2', + }, + ], + }, + { + key: '2', + label: 'submenu2', + type: 'submenu', + children: [ + { + key: '2-1', + label: 'item2-1', + }, + { + key: '2-2', + label: 'item2-2', + }, + ], + }, + { + key: '3', + label: 'item3', + }, + ]} + /> ); } diff --git a/src/Menu.tsx b/src/Menu.tsx index edad0c5b..d3ece685 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -62,9 +62,6 @@ export interface MenuProps rootClassName?: string; items?: ItemType[]; - /** @deprecated Please use `items` instead */ - children?: React.ReactNode; - disabled?: boolean; /** @private Disable auto overflow. Pls note the prop name may refactor since we do not final decided. */ disabledOverflow?: boolean; @@ -173,7 +170,6 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => { className, tabIndex = 0, items, - children, direction, id, @@ -248,10 +244,10 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => { measureChildList: React.ReactElement[], ] = React.useMemo( () => [ - parseItems(children, items, EMPTY_LIST, _internalComponents, prefixCls), - parseItems(children, items, EMPTY_LIST, {}, prefixCls), + parseItems(items, EMPTY_LIST, _internalComponents, prefixCls), + parseItems(items, EMPTY_LIST, {}, prefixCls), ], - [children, items, _internalComponents], + [items, _internalComponents], ); const [mounted, setMounted] = React.useState(false); diff --git a/src/MenuItem.tsx b/src/MenuItem.tsx index fb83fece..47671dfe 100644 --- a/src/MenuItem.tsx +++ b/src/MenuItem.tsx @@ -28,9 +28,6 @@ export interface MenuItemProps /** @private Do not use. Private warning empty usage */ warnKey?: boolean; - - /** @deprecated No place to use this. Should remove */ - attribute?: Record<string, string>; } // Since Menu event provide the `info.item` which point to the MenuItem node instance. diff --git a/src/interface.ts b/src/interface.ts index f47873d1..9174e48e 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -53,6 +53,10 @@ export interface MenuItemType extends ItemSharedProps { key: React.Key; + title?: string; + + role?: React.AriaRole; + // >>>>> Active onMouseEnter?: MenuHoverEventHandler; onMouseLeave?: MenuHoverEventHandler; @@ -62,6 +66,8 @@ export interface MenuItemType extends ItemSharedProps { } export interface MenuItemGroupType extends ItemSharedProps { + key?: React.Key; + type: 'group'; label?: React.ReactNode; @@ -139,4 +145,6 @@ export type MenuRef = { // ======================== Component ======================== export type ComponentType = 'submenu' | 'item' | 'group' | 'divider'; -export type Components = Partial<Record<ComponentType, React.ComponentType<any>>>; +export type Components = Partial< + Record<ComponentType, React.ComponentType<any>> +>; diff --git a/src/utils/nodeUtil.tsx b/src/utils/nodeUtil.tsx index c0b81106..bf8fec05 100644 --- a/src/utils/nodeUtil.tsx +++ b/src/utils/nodeUtil.tsx @@ -64,13 +64,12 @@ function convertItemsToNodes( } export function parseItems( - children: React.ReactNode | undefined, items: ItemType[] | undefined, keyPath: string[], components: Components, prefixCls?: string, ) { - let childNodes = children; + let childNodes; const mergedComponents: Required<Components> = { divider: Divider, diff --git a/tests/Collapsed.spec.tsx b/tests/Collapsed.spec.tsx index ee6f1c7d..e6d6c158 100644 --- a/tests/Collapsed.spec.tsx +++ b/tests/Collapsed.spec.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence */ import { act, fireEvent, render } from '@testing-library/react'; import React from 'react'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; describe('Menu.Collapsed', () => { describe('inlineCollapse and siderCollapsed', () => { @@ -15,13 +15,31 @@ describe('Menu.Collapsed', () => { it('should always follow openKeys when mode is switched', () => { const genMenu = (props?) => ( - <Menu openKeys={['1']} mode="inline" {...props}> - <SubMenu key="1" title="submenu1"> - <MenuItem key="submenu1">Option 1</MenuItem> - <MenuItem key="submenu2">Option 2</MenuItem> - </SubMenu> - <MenuItem key="2">menu2</MenuItem> - </Menu> + <Menu + openKeys={['1']} + mode="inline" + {...props} + items={[ + { + key: '1', + label: 'submenu1', + children: [ + { + key: 'submenu1', + label: 'Option 1', + }, + { + key: 'submenu2', + label: 'Option 2', + }, + ], + }, + { + key: '2', + label: 'menu2', + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -49,13 +67,28 @@ describe('Menu.Collapsed', () => { it('should always follow submenu popup hidden when mode is switched', () => { const genMenu = (props?) => ( - <Menu mode="vertical" {...props}> - <SubMenu key="1" title="submenu1"> - <SubMenu key="1-1" title="submenu1-1"> - <MenuItem key="Option-1">Option 1</MenuItem> - </SubMenu> - </SubMenu> - </Menu> + <Menu + mode="vertical" + {...props} + items={[ + { + key: '1', + label: 'submenu1', + children: [ + { + key: '1-1', + label: 'submenu1-1', + children: [ + { + key: 'Option-1', + label: 'Option 1', + }, + ], + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -102,15 +135,31 @@ describe('Menu.Collapsed', () => { it('should always follow openKeys when inlineCollapsed is switched', () => { const genMenu = (props?) => ( - <Menu defaultOpenKeys={['1']} mode="inline" {...props}> - <MenuItem key="menu1"> - <span>Option</span> - </MenuItem> - <SubMenu key="1" title="submenu1"> - <MenuItem key="submenu1">Option</MenuItem> - <MenuItem key="submenu2">Option</MenuItem> - </SubMenu> - </Menu> + <Menu + defaultOpenKeys={['1']} + mode="inline" + {...props} + items={[ + { + key: 'menu1', + label: <span>Option</span>, + }, + { + key: '1', + label: 'submenu1', + children: [ + { + key: 'submenu1', + label: 'Option', + }, + { + key: 'submenu2', + label: 'Option', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -155,15 +204,31 @@ describe('Menu.Collapsed', () => { it('inlineCollapsed should works well when specify a not existed default openKeys', () => { const genMenu = (props?) => ( - <Menu defaultOpenKeys={['not-existed']} mode="inline" {...props}> - <MenuItem key="menu1"> - <span>Option</span> - </MenuItem> - <SubMenu key="1" title="submenu1"> - <MenuItem key="submenu1">Option</MenuItem> - <MenuItem key="submenu2">Option</MenuItem> - </SubMenu> - </Menu> + <Menu + defaultOpenKeys={['not-existed']} + mode="inline" + {...props} + items={[ + { + key: 'menu1', + label: <span>Option</span>, + }, + { + key: '1', + label: 'submenu1', + children: [ + { + key: 'submenu1', + label: 'Option', + }, + { + key: 'submenu2', + label: 'Option', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -176,9 +241,6 @@ describe('Menu.Collapsed', () => { jest.runAllTimers(); }); - // wrapper - // .find('Overflow') - // .simulate('transitionEnd', { propertyName: 'width' }); fireEvent.transitionEnd(container.querySelector('.rc-menu-root'), { propertyName: 'width', }); @@ -189,7 +251,6 @@ describe('Menu.Collapsed', () => { }); // Hover to show - // wrapper.find('.rc-menu-submenu-title').at(0).simulate('mouseEnter'); fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); act(() => { @@ -222,24 +283,38 @@ describe('Menu.Collapsed', () => { mode="inline" inlineCollapsed getPopupContainer={node => node.parentNode as HTMLElement} - > - <MenuItem key="menu1">item</MenuItem> - <MenuItem key="menu2" title="title"> - item - </MenuItem> - <MenuItem key="menu3" title={undefined}> - item - </MenuItem> - <MenuItem key="menu4" title={null}> - item - </MenuItem> - <MenuItem key="menu5" title=""> - item - </MenuItem> - <MenuItem key="menu6" title={false as unknown as string}> - item - </MenuItem> - </Menu>, + items={[ + { + key: 'menu1', + label: 'item', + }, + { + key: 'menu2', + label: 'item', + title: 'title', + }, + { + key: 'menu3', + label: 'item', + title: undefined, + }, + { + key: 'menu4', + label: 'item', + title: null, + }, + { + key: 'menu5', + label: 'item', + title: '', + }, + { + key: 'menu6', + label: 'item', + title: false as unknown as string, + }, + ]} + />, ); expect( @@ -259,13 +334,27 @@ describe('Menu.Collapsed', () => { defaultSelectedKeys={['1']} openKeys={['3']} {...props} - > - <MenuItem key="1">Option 1</MenuItem> - <MenuItem key="2">Option 2</MenuItem> - <SubMenu key="3" title="Option 3"> - <MenuItem key="4">Option 4</MenuItem> - </SubMenu> - </Menu> + items={[ + { + key: '1', + label: 'Option 1', + }, + { + key: '2', + label: 'Option 2', + }, + { + key: '3', + label: 'Option 3', + children: [ + { + key: '4', + label: 'Option 4', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -305,13 +394,27 @@ describe('Menu.Collapsed', () => { defaultSelectedKeys={['1']} openKeys={['3']} {...props} - > - <MenuItem key="1">Option 1</MenuItem> - <MenuItem key="2">Option 2</MenuItem> - <SubMenu key="3" title="Option 3"> - <MenuItem key="4">Option 4</MenuItem> - </SubMenu> - </Menu> + items={[ + { + key: '1', + label: 'Option 1', + }, + { + key: '2', + label: 'Option 2', + }, + { + key: '3', + label: 'Option 3', + children: [ + { + key: '4', + label: 'Option 4', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); diff --git a/tests/Focus.spec.tsx b/tests/Focus.spec.tsx index 5457895f..9de3a60d 100644 --- a/tests/Focus.spec.tsx +++ b/tests/Focus.spec.tsx @@ -2,8 +2,9 @@ import { act, fireEvent, render } from '@testing-library/react'; import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import React from 'react'; -import Menu, { MenuItem, MenuItemGroup, MenuRef, SubMenu } from '../src'; +import Menu, { MenuRef } from '../src'; +// TODO: use userEvent instead of fireEvent for better focus testing describe('Focus', () => { beforeAll(() => { // Mock to force make menu item visible @@ -29,11 +30,23 @@ describe('Focus', () => { it('Get focus', async () => { const { container } = await act(async () => render( - <Menu mode="inline" openKeys={['s']}> - <SubMenu key="s" title="submenu"> - <MenuItem key="1">1</MenuItem> - </SubMenu> - </Menu>, + <Menu + mode="inline" + openKeys={['s']} + items={[ + { + key: 's', + type: 'submenu', + label: 'submenu', + children: [ + { + key: '1', + label: '1', + }, + ], + }, + ]} + />, ), ); @@ -52,14 +65,29 @@ describe('Focus', () => { const menuRef = React.createRef<MenuRef>(); const { getByTestId } = await act(async () => render( - <Menu ref={menuRef}> - <SubMenu key="bamboo" title="Disabled" disabled> - <MenuItem key="bamboo-child">Disabled child</MenuItem> - </SubMenu> - <MenuItem key="light" data-testid="first-focusable"> - Light - </MenuItem> - </Menu>, + <Menu + ref={menuRef} + items={[ + { + key: 'bamboo', + type: 'submenu', + label: 'Disabled', + disabled: true, + children: [ + { + key: 'bamboo-child', + label: 'Disabled child', + }, + ], + }, + { + key: 'light', + label: 'Light', + // @ts-ignore + 'data-testid': 'first-focusable', + }, + ]} + />, ), ); @@ -74,12 +102,22 @@ describe('Focus', () => { const menuRef = React.createRef<MenuRef>(); const { getByTestId } = await act(async () => render( - <Menu ref={menuRef} activeKey="cat"> - <MenuItem key="light">Light</MenuItem> - <MenuItem key="cat" data-testid="active-key"> - Cat - </MenuItem> - </Menu>, + <Menu + ref={menuRef} + activeKey="cat" + items={[ + { + key: 'light', + label: 'Light', + }, + { + key: 'cat', + label: 'Cat', + // @ts-ignore + 'data-testid': 'active-key', + }, + ]} + />, ), ); act(() => menuRef.current.focus()); @@ -93,15 +131,34 @@ describe('Focus', () => { const menuRef = React.createRef<MenuRef>(); const { getByTestId } = await act(async () => render( - <Menu ref={menuRef}> - <MenuItemGroup title="group" key="group" /> - <SubMenu key="bamboo" title="Disabled" disabled> - <MenuItem key="bamboo-child">Disabled child</MenuItem> - </SubMenu> - <MenuItem key="light" data-testid="first-focusable"> - Light - </MenuItem> - </Menu>, + <Menu + ref={menuRef} + items={[ + { + key: 'group', + type: 'group', + label: 'group', + }, + { + key: 'bamboo', + type: 'submenu', + label: 'Disabled', + disabled: true, + children: [ + { + key: 'bamboo-child', + label: 'Disabled child', + }, + ], + }, + { + key: 'light', + label: 'Light', + // @ts-ignore + 'data-testid': 'first-focusable', + }, + ]} + />, ), ); @@ -116,17 +173,33 @@ describe('Focus', () => { const menuRef = React.createRef<MenuRef>(); const { getByTestId } = await act(async () => render( - <Menu ref={menuRef}> - <MenuItemGroup title="group" key="group"> - <MenuItem key="group-child-1" disabled> - group-child-1 - </MenuItem> - <MenuItem key="group-child-2" data-testid="first-focusable"> - group-child-2 - </MenuItem> - </MenuItemGroup> - <MenuItem key="light">Light</MenuItem> - </Menu>, + <Menu + ref={menuRef} + items={[ + { + key: 'group', + type: 'group', + label: 'group', + children: [ + { + key: 'group-child-1', + label: 'group-child-1', + disabled: true, + }, + { + key: 'group-child-2', + label: 'group-child-2', + // @ts-ignore + 'data-testid': 'first-focusable', + }, + ], + }, + { + key: 'light', + label: 'Light', + }, + ]} + />, ), ); @@ -141,25 +214,45 @@ describe('Focus', () => { const menuRef = React.createRef<MenuRef>(); const { getByTestId } = await act(async () => render( - <Menu ref={menuRef}> - <MenuItemGroup title="group" key="group"> - <MenuItem key="group-child-1" disabled> - group-child-1 - </MenuItem> - <MenuItemGroup title="nested group" key="nested-group"> - <MenuItem key="nested-group-child-1" disabled> - nested-group-child-1 - </MenuItem> - <MenuItem - key="nested-group-child-2" - data-testid="first-focusable" - > - nested-group-child-2 - </MenuItem> - </MenuItemGroup> - <MenuItem key="group-child-3">group-child-3</MenuItem> - </MenuItemGroup> - </Menu>, + <Menu + ref={menuRef} + items={[ + { + key: 'group', + type: 'group', + label: 'group', + children: [ + { + key: 'group-child-1', + label: 'group-child-1', + disabled: true, + }, + { + key: 'nested-group', + type: 'group', + label: 'nested group', + children: [ + { + key: 'nested-group-child-1', + label: 'nested-group-child-1', + disabled: true, + }, + { + key: 'nested-group-child-2', + label: 'nested-group-child-2', + // @ts-ignore + 'data-testid': 'first-focusable', + }, + ], + }, + { + key: 'group-child-3', + label: 'group-child-3', + }, + ], + }, + ]} + />, ), ); @@ -174,15 +267,40 @@ describe('Focus', () => { const menuRef = React.createRef<MenuRef>(); const { getByTestId, getByTitle } = await act(async () => render( - <Menu ref={menuRef}> - <SubMenu key="sub-menu-disabled" title="Disabled" disabled> - <MenuItem key="sub-menu-disabled-child">Disabled child</MenuItem> - </SubMenu> - <SubMenu key="sub-menu" data-testid="sub-menu" title="Submenu"> - <MenuItem key="sub-menu-child-1">Submenu child</MenuItem> - </SubMenu> - <MenuItem key="light">Light</MenuItem> - </Menu>, + <Menu + ref={menuRef} + items={[ + { + key: 'sub-menu-disabled', + type: 'submenu', + label: 'Disabled', + disabled: true, + children: [ + { + key: 'sub-menu-disabled-child', + label: 'Disabled child', + }, + ], + }, + { + key: 'sub-menu', + type: 'submenu', + label: 'Submenu', + // @ts-ignore + 'data-testid': 'sub-menu', + children: [ + { + key: 'sub-menu-child-1', + label: 'Submenu child', + }, + ], + }, + { + key: 'light', + label: 'Light', + }, + ]} + />, ), ); diff --git a/tests/Keyboard.spec.tsx b/tests/Keyboard.spec.tsx index 73544b0f..30dfbcf2 100644 --- a/tests/Keyboard.spec.tsx +++ b/tests/Keyboard.spec.tsx @@ -1,10 +1,9 @@ /* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence, max-classes-per-file */ -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, act } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import React from 'react'; -import { act } from 'react-dom/test-utils'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; import { isActive, last } from './util'; describe('Menu.Keyboard', () => { @@ -47,11 +46,22 @@ describe('Menu.Keyboard', () => { it('no data-menu-id by init', () => { const { container } = render( - <Menu mode="inline" openKeys={['light']}> - <Menu.SubMenu key="light" title="Light"> - <Menu.Item key="bamboo">Bamboo</Menu.Item> - </Menu.SubMenu> - </Menu>, + <Menu + mode="inline" + openKeys={['light']} + items={[ + { + key: 'light', + label: 'Light', + children: [ + { + key: 'bamboo', + label: 'Bamboo', + }, + ], + }, + ]} + />, ); expect(container.children).toMatchSnapshot(); @@ -65,11 +75,12 @@ describe('Menu.Keyboard', () => { render() { return ( - <Menu> - {this.state.items.map(i => ( - <MenuItem key={i}>{i}</MenuItem> - ))} - </Menu> + <Menu + items={this.state.items.map(i => ({ + key: i, + label: i, + }))} + /> ); } } @@ -95,13 +106,34 @@ describe('Menu.Keyboard', () => { it('Skip disabled item', () => { const { container } = render( - <Menu defaultActiveFirst> - <MenuItem disabled /> - <MenuItem key="1">1</MenuItem> - <MenuItem disabled /> - <MenuItem key="2">2</MenuItem> - <MenuItem disabled /> - </Menu>, + <Menu + defaultActiveFirst + items={[ + { + key: 'disabled-1', + label: '', + disabled: true, + }, + { + key: '1', + label: '1', + }, + { + key: 'disabled-2', + label: '', + disabled: true, + }, + { + key: '2', + label: '2', + }, + { + key: 'disabled-3', + label: '', + disabled: true, + }, + ]} + />, ); // Next item @@ -124,11 +156,21 @@ describe('Menu.Keyboard', () => { it('Enter to open menu and active first item', () => { const { container } = render( - <Menu> - <SubMenu key="s1" title="submenu1"> - <MenuItem key="s1-1">1</MenuItem> - </SubMenu> - </Menu>, + <Menu + items={[ + { + key: 's1', + type: 'submenu', + label: 'submenu1', + children: [ + { + key: 's1-1', + label: '1', + }, + ], + }, + ]} + />, ); // Active first sub menu @@ -150,13 +192,30 @@ describe('Menu.Keyboard', () => { ) { it(`direction ${direction}`, () => { const { container, unmount } = render( - <Menu mode="vertical" direction={direction}> - <SubMenu key="bamboo" title="Bamboo"> - <SubMenu key="light" title="Light"> - <MenuItem key="little">Little</MenuItem> - </SubMenu> - </SubMenu> - </Menu>, + <Menu + mode="vertical" + direction={direction} + items={[ + { + key: 'bamboo', + type: 'submenu', + label: 'Bamboo', + children: [ + { + key: 'light', + type: 'submenu', + label: 'Light', + children: [ + { + key: 'little', + label: 'Little', + }, + ], + }, + ], + }, + ]} + />, ); // Active first @@ -206,12 +265,26 @@ describe('Menu.Keyboard', () => { it('inline keyboard', () => { const { container } = render( - <Menu mode="inline"> - <MenuItem key="light">Light</MenuItem> - <SubMenu key="bamboo" title="Bamboo"> - <MenuItem key="little">Little</MenuItem> - </SubMenu> - </Menu>, + <Menu + mode="inline" + items={[ + { + key: 'light', + label: 'Light', + }, + { + key: 'bamboo', + type: 'submenu', + label: 'Bamboo', + children: [ + { + key: 'little', + label: 'Little', + }, + ], + }, + ]} + />, ); // Nothing happen when no control key @@ -244,10 +317,19 @@ describe('Menu.Keyboard', () => { it('Focus last one', () => { const { container } = render( - <Menu mode="inline"> - <MenuItem key="light">Light</MenuItem> - <MenuItem key="bamboo">Bamboo</MenuItem> - </Menu>, + <Menu + mode="inline" + items={[ + { + key: 'light', + label: 'Light', + }, + { + key: 'bamboo', + label: 'Bamboo', + }, + ]} + />, ); keyDown(container, KeyCode.UP); @@ -256,11 +338,15 @@ describe('Menu.Keyboard', () => { it('Focus to link direct', () => { const { container } = render( - <Menu mode="inline"> - <MenuItem key="light"> - <a href="https://ant.design">Light</a> - </MenuItem> - </Menu>, + <Menu + mode="inline" + items={[ + { + key: 'light', + label: <a href="https://ant.design">Light</a>, + }, + ]} + />, ); const focusSpy = jest.spyOn(container.querySelector('a'), 'focus'); @@ -271,9 +357,16 @@ describe('Menu.Keyboard', () => { it('no dead loop', async () => { const { container } = render( - <Menu mode="vertical" openKeys={['bamboo']}> - <MenuItem key="little">Little</MenuItem> - </Menu>, + <Menu + mode="vertical" + openKeys={['bamboo']} + items={[ + { + key: 'little', + label: 'Little', + }, + ]} + />, ); keyDown(container, KeyCode.DOWN); diff --git a/tests/Menu.spec.tsx b/tests/Menu.spec.tsx index fc7326ef..4cc5d732 100644 --- a/tests/Menu.spec.tsx +++ b/tests/Menu.spec.tsx @@ -1,12 +1,11 @@ /* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence, max-classes-per-file */ import type { MenuMode } from '@/interface'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, act } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; import { resetWarned } from 'rc-util/lib/warning'; import React from 'react'; -import { act } from 'react-dom/test-utils'; import type { MenuRef } from '../src'; -import Menu, { Divider, MenuItem, MenuItemGroup, SubMenu } from '../src'; +import Menu from '../src'; import { isActive, last } from './util'; jest.mock('@rc-component/trigger', () => { @@ -49,23 +48,57 @@ describe('Menu', () => { className="myMenu" openAnimation="fade" {...props} - > - <MenuItemGroup title="g1"> - <MenuItem key="1">1</MenuItem> - <Divider /> - <MenuItem key="2">2</MenuItem> - </MenuItemGroup> - <MenuItem key="3">3</MenuItem> - <MenuItemGroup title="g2"> - <MenuItem key="4">4</MenuItem> - <MenuItem key="5" disabled> - 5 - </MenuItem> - </MenuItemGroup> - <SubMenu key={subKey} title="submenu"> - <MenuItem key="6">6</MenuItem> - </SubMenu> - </Menu> + items={[ + { + key: 'g1', + type: 'group', + label: 'g1', + children: [ + { + key: '1', + label: '1', + }, + { + type: 'divider', + }, + { + key: '2', + label: '2', + }, + ], + }, + { + key: '3', + label: '3', + }, + { + key: 'g2', + type: 'group', + label: 'g2', + children: [ + { + key: '4', + label: '4', + }, + { + key: '5', + label: '5', + disabled: true, + }, + ], + }, + { + key: subKey, + label: 'submenu', + children: [ + { + key: '6', + label: '6', + }, + ], + }, + ]} + /> ); } @@ -105,9 +138,16 @@ describe('Menu', () => { it(`${mode} menu that has a submenu with undefined children without error`, () => { expect(() => render( - <Menu mode={mode}> - <SubMenu /> - </Menu>, + <Menu + mode={mode} + items={[ + { + key: 'submenu', + label: '', + children: undefined, + }, + ]} + />, ), ).not.toThrow(); }); @@ -122,18 +162,40 @@ describe('Menu', () => { it('should support Fragment', () => { const { container } = render( - <Menu> - <SubMenu title="submenu"> - <MenuItem key="6">6</MenuItem> - </SubMenu> - <MenuItem key="7">6</MenuItem> - <> - <SubMenu title="submenu"> - <MenuItem key="8">6</MenuItem> - </SubMenu> - <MenuItem key="9">6</MenuItem> - </> - </Menu>, + <Menu + items={[ + { + key: 'submenu1', + type: 'submenu', + label: 'submenu', + children: [ + { + key: '6', + label: '6', + }, + ], + }, + { + key: '7', + label: '6', + }, + { + key: 'submenu2', + type: 'submenu', + label: 'submenu', + children: [ + { + key: '8', + label: '6', + }, + ], + }, + { + key: '9', + label: '6', + }, + ]} + />, ); expect(container.children).toMatchSnapshot(); }); @@ -142,17 +204,27 @@ describe('Menu', () => { describe('render role listbox', () => { function createMenu() { return ( - <Menu className="myMenu" role="listbox"> - <MenuItem key="1" role="option"> - 1 - </MenuItem> - <MenuItem key="2" role="option"> - 2 - </MenuItem> - <MenuItem key="3" role="option"> - 3 - </MenuItem> - </Menu> + <Menu + className="myMenu" + role="listbox" + items={[ + { + key: '1', + label: '1', + role: 'option', + }, + { + key: '2', + label: '2', + role: 'option', + }, + { + key: '3', + label: '3', + role: 'option', + }, + ]} + /> ); } @@ -164,10 +236,20 @@ describe('Menu', () => { it('set activeKey', () => { const genMenu = (props?) => ( - <Menu activeKey="1" {...props}> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </Menu> + <Menu + activeKey="1" + {...props} + items={[ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -181,40 +263,40 @@ describe('Menu', () => { it('active first item', () => { const { container } = render( - <Menu defaultActiveFirst> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </Menu>, + <Menu + defaultActiveFirst + items={[ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ]} + />, ); expect(container.querySelector('.rc-menu-item')).toHaveClass( 'rc-menu-item-active', ); }); - it('should render none menu item children', () => { - expect(() => { - render( - <Menu activeKey="1"> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - string - {'string'} - {null} - {undefined} - {12345} - <div /> - <input /> - </Menu>, - ); - }).not.toThrow(); - }); - it('select multiple items', () => { const { container } = render( - <Menu multiple> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </Menu>, + <Menu + multiple + items={[ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ]} + />, ); fireEvent.click(container.querySelector('.rc-menu-item')); @@ -275,10 +357,20 @@ describe('Menu', () => { it('can be controlled by selectedKeys', () => { const genMenu = (props?) => ( - <Menu selectedKeys={['1']} {...props}> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </Menu> + <Menu + selectedKeys={['1']} + {...props} + items={[ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ]} + /> ); const { container, rerender } = render(genMenu()); expect(container.querySelector('li').className).toContain('-selected'); @@ -290,9 +382,15 @@ describe('Menu', () => { it('empty selectedKeys not to throw', () => { render( - <Menu selectedKeys={null}> - <MenuItem key="foo">foo</MenuItem> - </Menu>, + <Menu + selectedKeys={null} + items={[ + { + key: 'foo', + label: 'foo', + }, + ]} + />, ); }); @@ -300,9 +398,17 @@ describe('Menu', () => { const onSelect = jest.fn(); const genMenu = (props?) => ( - <Menu onSelect={onSelect} selectedKeys={[]} {...props}> - <MenuItem key="bamboo">Bamboo</MenuItem> - </Menu> + <Menu + onSelect={onSelect} + selectedKeys={[]} + {...props} + items={[ + { + key: 'bamboo', + label: 'Bamboo', + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -321,10 +427,19 @@ describe('Menu', () => { it('select default item', () => { const { container } = render( - <Menu defaultSelectedKeys={['1']}> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </Menu>, + <Menu + defaultSelectedKeys={['1']} + items={[ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ]} + />, ); expect(container.querySelector('li').className).toContain('-selected'); }); @@ -333,10 +448,19 @@ describe('Menu', () => { // don't use selectedKeys as string // it is a compatible feature for https://github.com/ant-design/ant-design/issues/29429 const { container } = render( - <Menu selectedKeys={('item_abc' as unknown) as string[]}> - <MenuItem key="item_a">1</MenuItem> - <MenuItem key="item_abc">2</MenuItem> - </Menu>, + <Menu + selectedKeys={'item_abc' as unknown as string[]} + items={[ + { + key: 'item_a', + label: '1', + }, + { + key: 'item_abc', + label: '2', + }, + ]} + />, ); expect(container.querySelector('li').className).not.toContain('-selected'); expect(container.querySelectorAll('li')[1].className).toContain( @@ -347,14 +471,34 @@ describe('Menu', () => { describe('openKeys', () => { it('can be controlled by openKeys', () => { const genMenu = (props?) => ( - <Menu openKeys={['g1']} {...props}> - <Menu.SubMenu key="g1"> - <MenuItem key="1">1</MenuItem> - </Menu.SubMenu> - <Menu.SubMenu key="g2"> - <MenuItem key="2">2</MenuItem> - </Menu.SubMenu> - </Menu> + <Menu + openKeys={['g1']} + {...props} + items={[ + { + key: 'g1', + type: 'submenu', + label: 'g1', + children: [ + { + key: '1', + label: '1', + }, + ], + }, + { + key: 'g2', + type: 'submenu', + label: 'g2', + children: [ + { + key: '2', + label: '2', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -382,26 +526,47 @@ describe('Menu', () => { openKeys={undefined} selectedKeys={['1']} mode="inline" - > - <SubMenu title="1231"> - <MenuItem> - <a> - <span>123123</span> - </a> - </MenuItem> - </SubMenu> - </Menu>, + items={[ + { + key: 'submenu', + type: 'submenu', + label: '1231', + children: [ + { + key: 'item1', + label: ( + <a> + <span>123123</span> + </a> + ), + }, + ], + }, + ]} + />, ); expect(container.innerHTML).toBeTruthy(); }); it('null of openKeys', () => { const { container } = render( - <Menu openKeys={null} mode="inline"> - <SubMenu key="bamboo" title="Bamboo"> - <MenuItem key="light">Light</MenuItem> - </SubMenu> - </Menu>, + <Menu + openKeys={null} + mode="inline" + items={[ + { + key: 'bamboo', + type: 'submenu', + label: 'Bamboo', + children: [ + { + key: 'light', + label: 'Light', + }, + ], + }, + ]} + />, ); expect(container.innerHTML).toBeTruthy(); }); @@ -409,14 +574,33 @@ describe('Menu', () => { it('open default submenu', () => { const { container } = render( - <Menu defaultOpenKeys={['g1']}> - <SubMenu key="g1"> - <MenuItem key="1">1</MenuItem> - </SubMenu> - <SubMenu key="g2"> - <MenuItem key="2">2</MenuItem> - </SubMenu> - </Menu>, + <Menu + defaultOpenKeys={['g1']} + items={[ + { + key: 'g1', + type: 'submenu', + label: 'g1', + children: [ + { + key: '1', + label: '1', + }, + ], + }, + { + key: 'g2', + type: 'submenu', + label: 'g2', + children: [ + { + key: '2', + label: '2', + }, + ], + }, + ]} + />, ); act(() => { @@ -434,10 +618,19 @@ describe('Menu', () => { it('fires select event', () => { const handleSelect = jest.fn(); const { container } = render( - <Menu onSelect={handleSelect}> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </Menu>, + <Menu + onSelect={handleSelect} + items={[ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ]} + />, ); fireEvent.click(container.querySelector('.rc-menu-item')); expect(handleSelect.mock.calls[0][0].key).toBe('1'); @@ -450,13 +643,31 @@ describe('Menu', () => { const handleClick = jest.fn(); const { container } = render( - <Menu onClick={handleClick} openKeys={['parent']}> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - <Menu.SubMenu key="parent"> - <MenuItem key="3">3</MenuItem> - </Menu.SubMenu> - </Menu>, + <Menu + onClick={handleClick} + openKeys={['parent']} + items={[ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + { + key: 'parent', + type: 'submenu', + label: 'parent', + children: [ + { + key: '3', + label: '3', + }, + ], + }, + ]} + />, ); act(() => { @@ -482,10 +693,20 @@ describe('Menu', () => { it('fires deselect event', () => { const handleDeselect = jest.fn(); const { container } = render( - <Menu multiple onDeselect={handleDeselect}> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </Menu>, + <Menu + multiple + onDeselect={handleDeselect} + items={[ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ]} + />, ); const item = container.querySelector('.rc-menu-item'); fireEvent.click(item); @@ -495,24 +716,44 @@ describe('Menu', () => { it('active by mouse enter', () => { const { container } = render( - <Menu> - <MenuItem key="item1">item</MenuItem> - <MenuItem disabled>disabled</MenuItem> - <MenuItem key="item2">item2</MenuItem> - </Menu>, + <Menu + items={[ + { + key: 'item1', + label: 'item', + }, + { + key: 'disabled', + label: 'disabled', + disabled: true, + }, + { + key: 'item2', + label: 'item2', + }, + ]} + />, ); - // wrapper.find('li').last().simulate('mouseEnter'); fireEvent.mouseEnter(last(container.querySelectorAll('.rc-menu-item'))); - // expect(wrapper.isActive(2)).toBeTruthy(); isActive(container, 2); }); it('active by key down', () => { const genMenu = (props?) => ( - <Menu activeKey="1" {...props}> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </Menu> + <Menu + activeKey="1" + {...props} + items={[ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -530,9 +771,16 @@ describe('Menu', () => { it('defaultActiveFirst', () => { const { container } = render( - <Menu selectedKeys={['foo']} defaultActiveFirst> - <MenuItem key="foo">foo</MenuItem> - </Menu>, + <Menu + selectedKeys={['foo']} + defaultActiveFirst + items={[ + { + key: 'foo', + label: 'foo', + }, + ]} + />, ); isActive(container, 0); }); @@ -550,12 +798,26 @@ describe('Menu', () => { }; render( - <Menu builtinPlacements={builtinPlacements}> - <MenuItem>menuItem</MenuItem> - <SubMenu title="submenu"> - <MenuItem>menuItem</MenuItem> - </SubMenu> - </Menu>, + <Menu + builtinPlacements={builtinPlacements} + items={[ + { + key: '1', + label: 'menuItem', + }, + { + key: '2', + type: 'submenu', + label: 'submenu', + children: [ + { + key: '3', + label: 'menuItem', + }, + ], + }, + ]} + />, ); expect(global.triggerProps.builtinPlacements.leftTop).toEqual( @@ -572,11 +834,23 @@ describe('Menu', () => { it('defaultMotions should work correctly', () => { const genMenu = (props?) => ( - <Menu mode="inline" defaultMotions={defaultMotions} {...props}> - <SubMenu key="bamboo"> - <MenuItem key="light" /> - </SubMenu> - </Menu> + <Menu + mode="inline" + defaultMotions={defaultMotions} + {...props} + items={[ + { + key: 'bamboo', + type: 'submenu', + children: [ + { + key: 'light', + label: '', + }, + ], + }, + ]} + /> ); const { rerender } = render(genMenu()); @@ -605,11 +879,19 @@ describe('Menu', () => { defaultMotions={defaultMotions} motion={{ motionName: 'bambooLight' }} {...props} - > - <SubMenu key="bamboo"> - <MenuItem key="light" /> - </SubMenu> - </Menu> + items={[ + { + key: 'bamboo', + type: 'submenu', + children: [ + { + key: 'light', + label: '', + }, + ], + }, + ]} + /> ); const { rerender } = render(genMenu()); @@ -628,11 +910,22 @@ describe('Menu', () => { it('inline does not affect vertical motion', () => { const genMenu = (props?) => ( - <Menu defaultMotions={defaultMotions} {...props}> - <SubMenu key="bamboo"> - <MenuItem key="light" /> - </SubMenu> - </Menu> + <Menu + defaultMotions={defaultMotions} + {...props} + items={[ + { + key: 'bamboo', + type: 'submenu', + children: [ + { + key: 'light', + label: '', + }, + ], + }, + ]} + /> ); const { rerender } = render(genMenu({ mode: 'vertical' })); @@ -646,10 +939,20 @@ describe('Menu', () => { it('onMouseEnter should work', () => { const onMouseEnter = jest.fn(); const { container } = render( - <Menu onMouseEnter={onMouseEnter} defaultSelectedKeys={['test1']}> - <MenuItem key="test1">Navigation One</MenuItem> - <MenuItem key="test2">Navigation Two</MenuItem> - </Menu>, + <Menu + onMouseEnter={onMouseEnter} + defaultSelectedKeys={['test1']} + items={[ + { + key: 'test1', + label: 'Navigation One', + }, + { + key: 'test2', + label: 'Navigation Two', + }, + ]} + />, ); fireEvent.mouseEnter(container.querySelector('.rc-menu-root')); @@ -658,11 +961,23 @@ describe('Menu', () => { it('Nest children active should bump to top', async () => { const { container } = render( - <Menu activeKey="light" mode="vertical"> - <SubMenu key="bamboo" title="Bamboo"> - <MenuItem key="light">Light</MenuItem> - </SubMenu> - </Menu>, + <Menu + activeKey="light" + mode="vertical" + items={[ + { + key: 'bamboo', + type: 'submenu', + label: 'Bamboo', + children: [ + { + key: 'light', + label: 'Light', + }, + ], + }, + ]} + />, ); expect(container.querySelector('.rc-menu-submenu-active')).toBeTruthy(); @@ -672,9 +987,14 @@ describe('Menu', () => { const errorSpy = jest.spyOn(console, 'error'); const { unmount } = render( - <Menu> - <MenuItem key="bamboo">Bamboo</MenuItem> - </Menu>, + <Menu + items={[ + { + key: 'bamboo', + label: 'Bamboo', + }, + ]} + />, ); unmount(); @@ -697,11 +1017,20 @@ describe('Menu', () => { mode="vertical" onOpenChange={onOpenChange} {...props} - > - <SubMenu key="bamboo" title="Bamboo"> - <MenuItem key="light">Light</MenuItem> - </SubMenu> - </Menu>, + items={[ + { + key: 'bamboo', + type: 'submenu', + label: 'Bamboo', + children: [ + { + key: 'light', + label: 'Light', + }, + ], + }, + ]} + />, ); // Open menu @@ -723,11 +1052,24 @@ describe('Menu', () => { const onOpenChange = jest.fn(); const { container } = render( - <Menu openKeys={['bamboo']} mode="inline" onOpenChange={onOpenChange}> - <SubMenu key="bamboo" title="Bamboo"> - <MenuItem key="light">Light</MenuItem> - </SubMenu> - </Menu>, + <Menu + openKeys={['bamboo']} + mode="inline" + onOpenChange={onOpenChange} + items={[ + { + key: 'bamboo', + type: 'submenu', + label: 'Bamboo', + children: [ + { + key: 'light', + label: 'Light', + }, + ], + }, + ]} + />, ); // Open menu @@ -743,9 +1085,15 @@ describe('Menu', () => { it('should support ref', () => { const menuRef = React.createRef<MenuRef>(); const { container } = render( - <Menu ref={menuRef}> - <MenuItem key="light">Light</MenuItem> - </Menu>, + <Menu + ref={menuRef} + items={[ + { + key: 'light', + label: 'Light', + }, + ]} + />, ); expect(menuRef.current?.list).toBe(container.querySelector('ul')); @@ -756,11 +1104,23 @@ describe('Menu', () => { it('should render a divider with role="separator"', () => { const menuRef = React.createRef<MenuRef>(); const { container } = render( - <Menu ref={menuRef} activeKey="cat"> - <MenuItem key="light">Light</MenuItem> - <Divider /> - <MenuItem key="cat">Cat</MenuItem> - </Menu>, + <Menu + ref={menuRef} + activeKey="cat" + items={[ + { + key: 'light', + label: 'Light', + }, + { + type: 'divider', + }, + { + key: 'cat', + label: 'Cat', + }, + ]} + />, ); // Get the divider element with the rc-menu-item-divider class const divider = container.querySelector('.rc-menu-item-divider'); @@ -768,6 +1128,7 @@ describe('Menu', () => { // Assert that the divider element with rc-menu-item-divider class has role="separator" expect(divider).toHaveAttribute('role', 'separator'); }); + it('expandIcon should be hidden when setting null or false', () => { const App = ({ expand, @@ -776,18 +1137,46 @@ describe('Menu', () => { expand?: React.ReactNode; subExpand?: React.ReactNode; }) => ( - <Menu expandIcon={expand}> - <SubMenu title="sub menu" key="1" expandIcon={subExpand}> - <MenuItem key="1-1">0-1</MenuItem> - <MenuItem key="1-2">0-2</MenuItem> - </SubMenu> - , - <SubMenu title="sub menu2" key="2"> - <MenuItem key="2-1">0-1</MenuItem> - <MenuItem key="2-2">0-2</MenuItem> - </SubMenu> - ,<MenuItem key="cat">Cat</MenuItem> - </Menu> + <Menu + expandIcon={expand} + items={[ + { + key: '1', + type: 'submenu', + label: 'sub menu', + expandIcon: subExpand, + children: [ + { + key: '1-1', + label: '0-1', + }, + { + key: '1-2', + label: '0-2', + }, + ], + }, + { + key: '2', + type: 'submenu', + label: 'sub menu2', + children: [ + { + key: '2-1', + label: '0-1', + }, + { + key: '2-2', + label: '0-2', + }, + ], + }, + { + key: 'cat', + label: 'Cat', + }, + ]} + /> ); const { container, rerender } = render( diff --git a/tests/MenuItem.spec.tsx b/tests/MenuItem.spec.tsx index d5cc0d12..18371353 100644 --- a/tests/MenuItem.spec.tsx +++ b/tests/MenuItem.spec.tsx @@ -2,7 +2,7 @@ import { fireEvent, render } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; import React from 'react'; -import Menu, { MenuItem, MenuItemGroup, SubMenu } from '../src'; +import Menu from '../src'; describe('MenuItem', () => { const subMenuIconText = 'SubMenuIcon'; @@ -27,9 +27,17 @@ describe('MenuItem', () => { describe('custom icon', () => { it('should render custom arrow icon correctly.', () => { const { container } = render( - <Menu mode="vertical" itemIcon={itemIcon} expandIcon={expandIcon}> - <MenuItem key="1">1</MenuItem> - </Menu>, + <Menu + mode="vertical" + itemIcon={itemIcon} + expandIcon={expandIcon} + items={[ + { + key: '1', + label: '1', + }, + ]} + />, ); const menuItemText = container.querySelector('.rc-menu-item').textContent; expect(menuItemText).toEqual(`1${menuItemIconText}`); @@ -38,12 +46,22 @@ describe('MenuItem', () => { it('should render custom arrow icon correctly (with children props).', () => { const targetText = 'target'; const { container } = render( - <Menu mode="vertical" itemIcon={itemIcon} expandIcon={expandIcon}> - <MenuItem key="1" itemIcon={() => <span>{targetText}</span>}> - 1 - </MenuItem> - <MenuItem key="2">2</MenuItem> - </Menu>, + <Menu + mode="vertical" + itemIcon={itemIcon} + expandIcon={expandIcon} + items={[ + { + key: '1', + label: '1', + itemIcon: () => <span>{targetText}</span>, + }, + { + key: '2', + label: '2', + }, + ]} + />, ); const menuItemText = container.querySelector('.rc-menu-item').textContent; expect(menuItemText).toEqual(`1${targetText}`); @@ -53,11 +71,16 @@ describe('MenuItem', () => { it('not fires select event when disabled', () => { const handleSelect = jest.fn(); const { container } = render( - <Menu onSelect={handleSelect}> - <MenuItem disabled> - <span className="xx">Item content</span> - </MenuItem> - </Menu>, + <Menu + onSelect={handleSelect} + items={[ + { + key: '1', + disabled: true, + label: <span className="xx">Item content</span>, + }, + ]} + />, ); fireEvent.click(container.querySelector('.xx')); @@ -67,9 +90,16 @@ describe('MenuItem', () => { describe('menuItem events', () => { function renderMenu(props, itemProps) { return render( - <Menu {...props}> - <MenuItem key="light" {...itemProps} /> - </Menu>, + <Menu + {...props} + items={[ + { + key: 'light', + label: 'light', + ...itemProps, + }, + ]} + />, ); } @@ -120,21 +150,42 @@ describe('MenuItem', () => { }; const { container } = render( - <Menu mode="inline" activeKey="1" openKeys={['bamboo']}> - <MenuItem key="1" {...restProps}> - 1 - </MenuItem> - <SubMenu key="bamboo" {...restProps}> - <MenuItem key="2" {...restProps}> - 3 - </MenuItem> - </SubMenu> - <MenuItemGroup {...restProps}> - <MenuItem key="3" {...restProps}> - 4 - </MenuItem> - </MenuItemGroup> - </Menu>, + <Menu + mode="inline" + activeKey="1" + openKeys={['bamboo']} + items={[ + { + key: '1', + label: '1', + ...restProps, + }, + { + key: 'bamboo', + label: 'bamboo', + type: 'submenu', + ...restProps, + children: [ + { + key: '2', + label: '3', + ...restProps, + }, + ], + }, + { + type: 'group', + ...restProps, + children: [ + { + key: '3', + label: '4', + ...restProps, + }, + ], + }, + ]} + />, ); expect(container.children).toMatchSnapshot(); @@ -153,9 +204,15 @@ describe('MenuItem', () => { describe('overwrite default role', () => { it('should set role to none if null', () => { const { container } = render( - <Menu> - <MenuItem role={null}>test</MenuItem> - </Menu>, + <Menu + items={[ + { + key: '1', + label: 'test', + role: null, + }, + ]} + />, ); expect(container.querySelector('li')).toMatchSnapshot(); @@ -163,9 +220,15 @@ describe('MenuItem', () => { it('should set role to none if none', () => { const { container } = render( - <Menu> - <MenuItem role="none">test</MenuItem> - </Menu>, + <Menu + items={[ + { + key: '1', + label: 'test', + role: 'none', + }, + ]} + />, ); expect(container.querySelector('li')).toMatchSnapshot(); @@ -173,9 +236,15 @@ describe('MenuItem', () => { it('should set role to listitem', () => { const { container } = render( - <Menu> - <MenuItem role="listitem">test</MenuItem> - </Menu>, + <Menu + items={[ + { + key: '1', + label: 'test', + role: 'listitem', + }, + ]} + />, ); expect(container.querySelector('li')).toMatchSnapshot(); @@ -183,9 +252,15 @@ describe('MenuItem', () => { it('should set role to option', () => { const { container } = render( - <Menu> - <MenuItem role="option">test</MenuItem> - </Menu>, + <Menu + items={[ + { + key: '1', + label: 'test', + role: 'option', + }, + ]} + />, ); expect(container.querySelector('li')).toMatchSnapshot(); diff --git a/tests/Private.spec.tsx b/tests/Private.spec.tsx index 06bdac9b..4f426afe 100644 --- a/tests/Private.spec.tsx +++ b/tests/Private.spec.tsx @@ -2,7 +2,7 @@ import { render } from '@testing-library/react'; import classnames from 'classnames'; import React from 'react'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; describe('Private Props', () => { it('_internalRenderMenuItem', () => { @@ -13,9 +13,13 @@ describe('Private Props', () => { className: classnames(node.props.className, 'inject-cls'), }) } - > - <MenuItem key="1">1</MenuItem> - </Menu>, + items={[ + { + key: '1', + label: '1', + }, + ]} + />, ); expect(container.querySelector('.inject-cls')).toBeTruthy(); @@ -31,11 +35,20 @@ describe('Private Props', () => { className: classnames(node.props.className, 'inject-cls'), }) } - > - <SubMenu key="1" title="1"> - <MenuItem key="1-1">1-1</MenuItem> - </SubMenu> - </Menu>, + items={[ + { + key: '1', + type: 'submenu', + label: '1', + children: [ + { + key: '1-1', + label: '1-1', + }, + ], + }, + ]} + />, ); expect(container.querySelector('.inject-cls')).toBeTruthy(); diff --git a/tests/React18.spec.tsx b/tests/React18.spec.tsx index b505027d..51bfdd2d 100644 --- a/tests/React18.spec.tsx +++ b/tests/React18.spec.tsx @@ -2,7 +2,7 @@ import { act, render } from '@testing-library/react'; import React from 'react'; import type { MenuProps } from '../src'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; describe('React18', () => { function runAllTimer() { @@ -24,17 +24,44 @@ describe('React18', () => { function createMenu(props?: MenuProps) { return ( <React.StrictMode> - <Menu {...props}> - <SubMenu key="s1" title="submenu1"> - <MenuItem key="s1-1">1</MenuItem> - <SubMenu key="s1-2" title="submenu1-1"> - <MenuItem key="s1-2-1">2</MenuItem> - </SubMenu> - </SubMenu> - <SubMenu key="s2" title="submenu2"> - <MenuItem key="s2-2">2</MenuItem> - </SubMenu> - </Menu> + <Menu + {...props} + items={[ + { + key: 's1', + label: 'submenu1', + type: 'submenu', + children: [ + { + key: 's1-1', + label: '1', + }, + { + key: 's1-2', + label: 'submenu1-1', + type: 'submenu', + children: [ + { + key: 's1-2-1', + label: '2', + }, + ], + }, + ], + }, + { + key: 's2', + label: 'submenu2', + type: 'submenu', + children: [ + { + key: 's2-2', + label: '2', + }, + ], + }, + ]} + /> </React.StrictMode> ); } diff --git a/tests/Responsive.spec.tsx b/tests/Responsive.spec.tsx index 9d1e6ff6..acaa3705 100644 --- a/tests/Responsive.spec.tsx +++ b/tests/Responsive.spec.tsx @@ -4,7 +4,7 @@ import ResizeObserver from 'rc-resize-observer'; import KeyCode from 'rc-util/lib/KeyCode'; import { spyElementPrototype } from 'rc-util/lib/test/domHook'; import React from 'react'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; import { OVERFLOW_KEY } from '../src/hooks/useKeyRecords'; import { last } from './util'; @@ -84,11 +84,25 @@ describe('Menu.Responsive', () => { it('ssr render full', () => { const { container } = render( - <Menu mode="horizontal"> - <MenuItem key="light">Light</MenuItem> - <SubMenu key="bamboo">Bamboo</SubMenu> - <MenuItem key="little">Little</MenuItem> - </Menu>, + <Menu + mode="horizontal" + items={[ + { + key: 'light', + label: 'Light', + }, + { + key: 'bamboo', + label: 'Bamboo', + type: 'submenu', + children: [], + }, + { + key: 'little', + label: 'Little', + }, + ]} + />, ); expect(container.children).toMatchSnapshot(); @@ -103,13 +117,28 @@ describe('Menu.Responsive', () => { activeKey="little" onOpenChange={onOpenChange} {...props} - > - <MenuItem key="light">Light</MenuItem> - <MenuItem key="bamboo">Bamboo</MenuItem> - <SubMenu key="home" title="Home"> - <MenuItem key="little">Little</MenuItem> - </SubMenu> - </Menu> + items={[ + { + key: 'light', + label: 'Light', + }, + { + key: 'bamboo', + label: 'Bamboo', + }, + { + key: 'home', + label: 'Home', + type: 'submenu', + children: [ + { + key: 'little', + label: 'Little', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -150,13 +179,6 @@ describe('Menu.Responsive', () => { }); spy.mockRestore(); - // Should show the rest icon - // expect( - // last(container.querySelectorAll('.rc-menu-overflow-item-rest')), - // ).not.toHaveStyle({ - // opacity: '0', - // }); - // Should set active on rest expect( last(container.querySelectorAll('.rc-menu-overflow-item-rest')), diff --git a/tests/SubMenu.spec.tsx b/tests/SubMenu.spec.tsx index f77e6d76..dd3950d7 100644 --- a/tests/SubMenu.spec.tsx +++ b/tests/SubMenu.spec.tsx @@ -2,7 +2,7 @@ import { act, fireEvent, render } from '@testing-library/react'; import { resetWarned } from 'rc-util/lib/warning'; import React from 'react'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; import { isActive, last } from './util'; jest.mock('@rc-component/trigger', () => { @@ -48,17 +48,44 @@ describe('SubMenu', () => { function createMenu(props?) { return ( - <Menu {...props}> - <SubMenu key="s1" title="submenu1"> - <MenuItem key="s1-1">1</MenuItem> - <SubMenu key="s1-2" title="submenu1-1"> - <MenuItem key="s1-2-1">2</MenuItem> - </SubMenu> - </SubMenu> - <SubMenu key="s2" title="submenu2"> - <MenuItem key="s2-2">2</MenuItem> - </SubMenu> - </Menu> + <Menu + {...props} + items={[ + { + key: 's1', + label: 'submenu1', + type: 'submenu', + children: [ + { + key: 's1-1', + label: '1', + }, + { + key: 's1-2', + label: 'submenu1-1', + type: 'submenu', + children: [ + { + key: 's1-2-1', + label: '2', + }, + ], + }, + ], + }, + { + key: 's2', + label: 'submenu2', + type: 'submenu', + children: [ + { + key: 's2-2', + label: '2', + }, + ], + }, + ]} + /> ); } @@ -68,11 +95,23 @@ describe('SubMenu', () => { it("don't show submenu when disabled", () => { const { container } = render( - <Menu mode="vertical"> - <SubMenu key="s" title="submenu" disabled> - <MenuItem key="1">1</MenuItem> - </SubMenu> - </Menu>, + <Menu + mode="vertical" + items={[ + { + key: 's', + label: 'submenu', + type: 'submenu', + disabled: true, + children: [ + { + key: '1', + label: '1', + }, + ], + }, + ]} + />, ); fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); @@ -81,11 +120,24 @@ describe('SubMenu', () => { it('offsets the submenu popover', () => { render( - <Menu mode="horizontal" disabledOverflow> - <SubMenu key="s" title="submenu" popupOffset={[0, 15]}> - <MenuItem key="1">1</MenuItem> - </SubMenu> - </Menu>, + <Menu + mode="horizontal" + disabledOverflow + items={[ + { + key: 's', + label: 'submenu', + type: 'submenu', + popupOffset: [0, 15], + children: [ + { + key: '1', + label: '1', + }, + ], + }, + ]} + />, ); const { popupAlign } = global.triggerProps; @@ -98,12 +150,24 @@ describe('SubMenu', () => { mode="vertical" itemIcon={itemIcon} expandIcon={<span>SubMenuIconNode</span>} - > - <SubMenu key="s" title="submenu"> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </SubMenu> - </Menu>, + items={[ + { + key: 's', + label: 'submenu', + type: 'submenu', + children: [ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ], + }, + ]} + />, ); const wrapperWithExpandIconFunction = render( @@ -111,12 +175,24 @@ describe('SubMenu', () => { mode="vertical" itemIcon={itemIcon} expandIcon={() => <span>SubMenuIconNode</span>} - > - <SubMenu key="s" title="submenu"> - <MenuItem key="1">1</MenuItem> - <MenuItem key="2">2</MenuItem> - </SubMenu> - </Menu>, + items={[ + { + key: 's', + label: 'submenu', + type: 'submenu', + children: [ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ], + }, + ]} + />, ); const subMenuText = container.querySelector( @@ -132,16 +208,25 @@ describe('SubMenu', () => { it('should Not render custom arrow icon in horizontal mode.', () => { const { container } = render( - <Menu mode="horizontal" disabledOverflow> - <SubMenu - key="s" - title="submenu" - itemIcon={itemIcon} - expandIcon={<span>SubMenuIconNode</span>} - > - <MenuItem key="1">1</MenuItem> - </SubMenu> - </Menu>, + <Menu + mode="horizontal" + disabledOverflow + items={[ + { + key: 's', + label: 'submenu', + type: 'submenu', + itemIcon: itemIcon, + expandIcon: <span>SubMenuIconNode</span>, + children: [ + { + key: '1', + label: '1', + }, + ], + }, + ]} + />, ); const childText = container.querySelector( @@ -178,11 +263,21 @@ describe('SubMenu', () => { describe('openSubMenuOnMouseEnter and closeSubMenuOnMouseLeave are true', () => { it('toggles when mouse enter and leave', () => { const { container } = render( - <Menu> - <SubMenu key="s1" title="submenu1"> - <MenuItem key="s1-1">1</MenuItem> - </SubMenu> - </Menu>, + <Menu + items={[ + { + key: 's1', + label: 'submenu1', + type: 'submenu', + children: [ + { + key: 's1-1', + label: '1', + }, + ], + }, + ]} + />, ); // Enter @@ -222,89 +317,77 @@ describe('SubMenu', () => { it('fires openChange event', () => { const handleOpenChange = jest.fn(); const { container } = render( - <Menu onOpenChange={handleOpenChange}> - <MenuItem key="1">1</MenuItem> - <SubMenu title="s1"> - <MenuItem key="2">2</MenuItem> - <SubMenu title="s2"> - <MenuItem key="3">3</MenuItem> - </SubMenu> - </SubMenu> - </Menu>, + <Menu + onOpenChange={handleOpenChange} + items={[ + { + key: '1', + label: '1', + }, + { + key: 's1', + label: 's1', + type: 'submenu', + children: [ + { + key: '2', + label: '2', + }, + { + key: 's2', + label: 's2', + type: 'submenu', + children: [ + { + key: '3', + label: '3', + }, + ], + }, + ], + }, + ]} + />, ); // First fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); runAllTimer(); - expect(handleOpenChange).toHaveBeenCalledWith(['tmp_key-1']); + expect(handleOpenChange).toHaveBeenCalledWith(['s1']); // Second fireEvent.mouseEnter( container.querySelectorAll('.rc-menu-submenu-title')[1], ); runAllTimer(); - expect(handleOpenChange).toHaveBeenCalledWith([ - 'tmp_key-1', - 'tmp_key-tmp_key-1-1', - ]); + expect(handleOpenChange).toHaveBeenCalledWith(['s1', 's2']); }); describe('undefined key', () => { - it('warning item', () => { - resetWarned(); - - const errorSpy = jest - .spyOn(console, 'error') - .mockImplementation(() => {}); - - render( - <Menu> - <MenuItem>1</MenuItem> - </Menu>, - ); - - expect(errorSpy).toHaveBeenCalledWith( - 'Warning: MenuItem should not leave undefined `key`.', - ); - - errorSpy.mockRestore(); - }); - - it('warning sub menu', () => { - resetWarned(); - - const errorSpy = jest - .spyOn(console, 'error') - .mockImplementation(() => {}); - - render( - <Menu> - <SubMenu /> - </Menu>, - ); - - expect(errorSpy).toHaveBeenCalledWith( - 'Warning: SubMenu should not leave undefined `key`.', - ); - - errorSpy.mockRestore(); - }); - it('should not warning', () => { resetWarned(); - + const errors: any[] = []; const errorSpy = jest .spyOn(console, 'error') - .mockImplementation(() => {}); + .mockImplementation((msg, ...args) => { + // Only collect non-act related warnings + if (!msg.includes('act(...)')) { + errors.push([msg, ...args]); + } + }); render( - <Menu> - <Menu.Divider /> - </Menu>, + <Menu + items={[ + { + type: 'divider', + }, + ]} + />, ); - expect(errorSpy).not.toHaveBeenCalled(); - + // Check if there are any non-act related warnings + expect(errors).toHaveLength(0); errorSpy.mockRestore(); }); }); @@ -336,16 +419,24 @@ describe('SubMenu', () => { const onMouseEnter = jest.fn(); const onMouseLeave = jest.fn(); const { container } = render( - <Menu openKeys={['s1']}> - <SubMenu - key="s1" - title="submenu1" - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} - > - <MenuItem key="s1-1">1</MenuItem> - </SubMenu> - </Menu>, + <Menu + openKeys={['s1']} + items={[ + { + key: 's1', + label: 'submenu1', + type: 'submenu', + onMouseEnter, + onMouseLeave, + children: [ + { + key: 's1-1', + label: '1', + }, + ], + }, + ]} + />, ); fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); expect(onMouseEnter).toHaveBeenCalledTimes(1); @@ -396,15 +487,24 @@ describe('SubMenu', () => { }); it('should take style prop', () => { - const App = () => ( - <Menu style={{ backgroundColor: 'black' }}> - <SubMenu key="s1" title="submenu1"> - <MenuItem key="s1-1">1</MenuItem> - </SubMenu> - </Menu> + const { container } = render( + <Menu + style={{ backgroundColor: 'black' }} + items={[ + { + key: 's1', + label: 'submenu1', + type: 'submenu', + children: [ + { + key: 's1-1', + label: '1', + }, + ], + }, + ]} + />, ); - - const { container } = render(<App />); expect(container.querySelector('.rc-menu')).toHaveStyle({ backgroundColor: 'black', }); @@ -412,11 +512,18 @@ describe('SubMenu', () => { it('not pass style into sub menu item', () => { const { container } = render( - <Menu mode="horizontal" style={{ background: 'green' }} disabledOverflow> - <MenuItem style={{ color: 'red' }} key="1"> - 1 - </MenuItem> - </Menu>, + <Menu + mode="horizontal" + style={{ background: 'green' }} + disabledOverflow + items={[ + { + key: '1', + label: '1', + style: { color: 'red' }, + }, + ]} + />, ); expect(container.querySelector('.rc-menu-item')).toHaveStyle({ @@ -428,14 +535,35 @@ describe('SubMenu', () => { const onOpenChange = jest.fn(); const { container } = render( - <Menu mode="inline" onOpenChange={onOpenChange}> - <SubMenu key="bamboo" title="Bamboo" disabled> - <MenuItem key="little">Little</MenuItem> - </SubMenu> - <SubMenu key="light" title="Light"> - <MenuItem key="sub">Sub</MenuItem> - </SubMenu> - </Menu>, + <Menu + mode="inline" + onOpenChange={onOpenChange} + items={[ + { + key: 'bamboo', + label: 'Bamboo', + type: 'submenu', + disabled: true, + children: [ + { + key: 'little', + label: 'Little', + }, + ], + }, + { + key: 'light', + label: 'Light', + type: 'submenu', + children: [ + { + key: 'sub', + label: 'Sub', + }, + ], + }, + ]} + />, ); // Disabled @@ -449,11 +577,24 @@ describe('SubMenu', () => { it('popup className should correct', () => { const { container } = render( - <Menu mode="horizontal" openKeys={['light']} disabledOverflow> - <SubMenu key="light"> - <SubMenu key="bamboo" /> - </SubMenu> - </Menu>, + <Menu + mode="horizontal" + openKeys={['light']} + disabledOverflow + items={[ + { + key: 'light', + type: 'submenu', + children: [ + { + key: 'bamboo', + type: 'submenu', + children: [], + }, + ], + }, + ]} + />, ); runAllTimer(); @@ -469,19 +610,34 @@ describe('SubMenu', () => { it('should support rootClassName', () => { const { container } = render( - <Menu rootClassName="custom-className" defaultOpenKeys={['1', '1-1']}> - <SubMenu key="1" title="submenu1"> - <MenuItem key="1-1" role="option"> - submenu7 - </MenuItem> - </SubMenu> - <MenuItem key="2" role="option"> - 2 - </MenuItem> - <MenuItem key="3" role="option"> - 3 - </MenuItem> - </Menu>, + <Menu + rootClassName="custom-className" + defaultOpenKeys={['1', '1-1']} + items={[ + { + key: '1', + label: 'submenu1', + type: 'submenu', + children: [ + { + key: '1-1', + label: 'submenu7', + role: 'option', + }, + ], + }, + { + key: '2', + label: '2', + role: 'option', + }, + { + key: '3', + label: '3', + role: 'option', + }, + ]} + />, ); expect(container.children).toMatchSnapshot(); @@ -508,15 +664,22 @@ describe('SubMenu', () => { it('submenu should support popupStyle', () => { const { container } = render( - <Menu> - <SubMenu - key="s1" - title="submenu1" - popupStyle={{ zIndex: 100, width: 150 }} - > - <MenuItem key="s1-1">1</MenuItem> - </SubMenu> - </Menu>, + <Menu + items={[ + { + key: 's1', + label: 'submenu1', + type: 'submenu', + popupStyle: { zIndex: 100, width: 150 }, + children: [ + { + key: 's1-1', + label: '1', + }, + ], + }, + ]} + />, ); fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); diff --git a/tests/__snapshots__/Menu.spec.tsx.snap b/tests/__snapshots__/Menu.spec.tsx.snap index 633a94e3..3e7344f5 100644 --- a/tests/__snapshots__/Menu.spec.tsx.snap +++ b/tests/__snapshots__/Menu.spec.tsx.snap @@ -134,11 +134,11 @@ HTMLCollection [ role="none" > <div - aria-controls="rc-menu-uuid-test-tmp_key-3-popup" + aria-controls="rc-menu-uuid-test-tmp-3-popup" aria-expanded="false" aria-haspopup="true" class="rc-menu-submenu-title" - data-menu-id="rc-menu-uuid-test-tmp_key-3" + data-menu-id="rc-menu-uuid-test-tmp-3" role="menuitem" tabindex="-1" title="submenu" @@ -249,11 +249,11 @@ HTMLCollection [ role="none" > <div - aria-controls="rc-menu-uuid-test-tmp_key-3-popup" + aria-controls="rc-menu-uuid-test-tmp-3-popup" aria-expanded="false" aria-haspopup="true" class="rc-menu-submenu-title" - data-menu-id="rc-menu-uuid-test-tmp_key-3" + data-menu-id="rc-menu-uuid-test-tmp-3" role="menuitem" tabindex="-1" title="submenu" @@ -368,11 +368,11 @@ HTMLCollection [ role="none" > <div - aria-controls="rc-menu-uuid-test-tmp_key-3-popup" + aria-controls="rc-menu-uuid-test-tmp-3-popup" aria-expanded="false" aria-haspopup="true" class="rc-menu-submenu-title" - data-menu-id="rc-menu-uuid-test-tmp_key-3" + data-menu-id="rc-menu-uuid-test-tmp-3" role="menuitem" style="padding-left: 24px;" tabindex="-1" @@ -489,11 +489,11 @@ HTMLCollection [ role="none" > <div - aria-controls="rc-menu-uuid-test-tmp_key-3-popup" + aria-controls="rc-menu-uuid-test-tmp-3-popup" aria-expanded="false" aria-haspopup="true" class="rc-menu-submenu-title" - data-menu-id="rc-menu-uuid-test-tmp_key-3" + data-menu-id="rc-menu-uuid-test-tmp-3" role="menuitem" style="padding-right: 24px;" tabindex="-1" @@ -526,11 +526,11 @@ HTMLCollection [ role="none" > <div - aria-controls="rc-menu-uuid-test-tmp_key-0-popup" + aria-controls="rc-menu-uuid-test-submenu1-popup" aria-expanded="false" aria-haspopup="true" class="rc-menu-submenu-title" - data-menu-id="rc-menu-uuid-test-tmp_key-0" + data-menu-id="rc-menu-uuid-test-submenu1" role="menuitem" tabindex="-1" title="submenu" @@ -554,11 +554,11 @@ HTMLCollection [ role="none" > <div - aria-controls="rc-menu-uuid-test-tmp_key-2-popup" + aria-controls="rc-menu-uuid-test-submenu2-popup" aria-expanded="false" aria-haspopup="true" class="rc-menu-submenu-title" - data-menu-id="rc-menu-uuid-test-tmp_key-2" + data-menu-id="rc-menu-uuid-test-submenu2" role="menuitem" tabindex="-1" title="submenu" @@ -676,11 +676,11 @@ HTMLCollection [ role="none" > <div - aria-controls="rc-menu-uuid-test-tmp_key-3-popup" + aria-controls="rc-menu-uuid-test-tmp-3-popup" aria-expanded="false" aria-haspopup="true" class="rc-menu-submenu-title" - data-menu-id="rc-menu-uuid-test-tmp_key-3" + data-menu-id="rc-menu-uuid-test-tmp-3" role="menuitem" tabindex="-1" title="submenu" @@ -791,11 +791,11 @@ HTMLCollection [ role="none" > <div - aria-controls="rc-menu-uuid-test-tmp_key-3-popup" + aria-controls="rc-menu-uuid-test-tmp-3-popup" aria-expanded="false" aria-haspopup="true" class="rc-menu-submenu-title" - data-menu-id="rc-menu-uuid-test-tmp_key-3" + data-menu-id="rc-menu-uuid-test-tmp-3" role="menuitem" tabindex="-1" title="submenu" diff --git a/tests/__snapshots__/MenuItem.spec.tsx.snap b/tests/__snapshots__/MenuItem.spec.tsx.snap index 4d761f6b..94a0b593 100644 --- a/tests/__snapshots__/MenuItem.spec.tsx.snap +++ b/tests/__snapshots__/MenuItem.spec.tsx.snap @@ -52,7 +52,7 @@ exports[`MenuItem overwrite default role should set extra to option 1`] = ` exports[`MenuItem overwrite default role should set role to listitem 1`] = ` <li class="rc-menu-item" - data-menu-id="rc-menu-uuid-test-tmp_key-0" + data-menu-id="rc-menu-uuid-test-1" role="listitem" tabindex="-1" > @@ -63,7 +63,7 @@ exports[`MenuItem overwrite default role should set role to listitem 1`] = ` exports[`MenuItem overwrite default role should set role to none if none 1`] = ` <li class="rc-menu-item" - data-menu-id="rc-menu-uuid-test-tmp_key-0" + data-menu-id="rc-menu-uuid-test-1" role="none" tabindex="-1" > @@ -74,7 +74,7 @@ exports[`MenuItem overwrite default role should set role to none if none 1`] = ` exports[`MenuItem overwrite default role should set role to none if null 1`] = ` <li class="rc-menu-item" - data-menu-id="rc-menu-uuid-test-tmp_key-0" + data-menu-id="rc-menu-uuid-test-1" role="none" tabindex="-1" > @@ -86,7 +86,7 @@ exports[`MenuItem overwrite default role should set role to option 1`] = ` <li aria-selected="false" class="rc-menu-item" - data-menu-id="rc-menu-uuid-test-tmp_key-0" + data-menu-id="rc-menu-uuid-test-1" role="option" tabindex="-1" > @@ -128,9 +128,9 @@ HTMLCollection [ role="menuitem" style="padding-left: 24px;" tabindex="-1" - title="title" + title="bamboo" > - title + bamboo <i class="rc-menu-submenu-arrow" /> @@ -163,10 +163,7 @@ HTMLCollection [ <div class="rc-menu-item-group-title" role="presentation" - title="title" - > - title - </div> + /> <ul class="rc-menu-item-group-list" role="group" diff --git a/tests/__snapshots__/Responsive.spec.tsx.snap b/tests/__snapshots__/Responsive.spec.tsx.snap index 36cd20ab..79a2fc63 100644 --- a/tests/__snapshots__/Responsive.spec.tsx.snap +++ b/tests/__snapshots__/Responsive.spec.tsx.snap @@ -29,7 +29,9 @@ HTMLCollection [ class="rc-menu-submenu-title" role="menuitem" tabindex="-1" + title="Bamboo" > + Bamboo <i class="rc-menu-submenu-arrow" /> @@ -69,8 +71,6 @@ HTMLCollection [ <div aria-hidden="true" style="display: none;" - > - Bamboo - </div>, + />, ] `;