From 4780eb7a8f012d318201aa2c355f709780a6a227 Mon Sep 17 00:00:00 2001 From: zyf <1246271707@qq.com> Date: Sun, 16 Feb 2025 22:51:13 +0800 Subject: [PATCH 1/7] add popupRender for subMenu --- docs/demo/navigation.md | 3 + docs/examples/navigation.less | 76 +++++++++++++++ docs/examples/navigation.tsx | 174 +++++++++++++++++++++++++++++++++ src/Menu.tsx | 5 + src/SubMenu/index.tsx | 42 +++++--- src/context/MenuContext.tsx | 3 + src/interface.ts | 9 +- tests/popupRender.test.tsx | 175 ++++++++++++++++++++++++++++++++++ 8 files changed, 473 insertions(+), 14 deletions(-) create mode 100644 docs/demo/navigation.md create mode 100644 docs/examples/navigation.less create mode 100644 docs/examples/navigation.tsx create mode 100644 tests/popupRender.test.tsx diff --git a/docs/demo/navigation.md b/docs/demo/navigation.md new file mode 100644 index 00000000..abf1c551 --- /dev/null +++ b/docs/demo/navigation.md @@ -0,0 +1,3 @@ +## navigation + + diff --git a/docs/examples/navigation.less b/docs/examples/navigation.less new file mode 100644 index 00000000..0b8518ce --- /dev/null +++ b/docs/examples/navigation.less @@ -0,0 +1,76 @@ +.navigation-popup { + padding: 24px; + min-width: 480px; + background: #fff; + border-radius: 8px; + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08); + + .navigation-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; + } + + .navigation-menu-item { + padding: 0; + margin: 0; + + a { + display: block; + padding: 16px; + text-decoration: none; + color: inherit; + border-radius: 6px; + transition: all 0.3s; + + h3 { + margin: 0 0 8px; + font-size: 16px; + font-weight: 500; + } + + p { + margin: 0; + color: rgba(0, 0, 0, 0.45); + font-size: 14px; + line-height: 1.5; + } + + &:hover { + background: rgba(0, 0, 0, 0.02); + } + } + } +} + +.panel-popup { + padding: 16px; + min-width: 240px; + background: #fff; + border-radius: 8px; + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08); + + .panel-header { + padding: 0 8px 12px; + border-bottom: 1px solid rgba(0, 0, 0, 0.06); + margin-bottom: 8px; + + h4 { + margin: 0; + font-size: 14px; + color: rgba(0, 0, 0, 0.45); + } + } + + .panel-content { + .rc-menu-item { + padding: 8px 12px; + margin: 0; + border-radius: 4px; + + &:hover { + background: rgba(0, 0, 0, 0.02); + } + } + } +} diff --git a/docs/examples/navigation.tsx b/docs/examples/navigation.tsx new file mode 100644 index 00000000..16f7cb12 --- /dev/null +++ b/docs/examples/navigation.tsx @@ -0,0 +1,174 @@ +import React from 'react'; +import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; +import type { ReactElement } from 'react'; +import './navigation.less'; + +const NavigationDemo = () => { + const menuItems = [ + // { + // key: 'home', + // label: 'Home' + // }, + { + key: 'features', + label: 'Features', + children: [ + { + key: 'getting-started', + label: ( + +

Getting Started

+

Quick start guide and learn the basics.

+
+ ), + }, + { + key: 'components', + label: ( + +

Components

+

Explore our component library.

+
+ ), + }, + { + key: 'templates', + label: ( + +

Templates

+

Ready-to-use template designs.

+
+ ), + }, + ], + }, + // { + // key: 'resources', + // label: 'Resources', + // children: [ + // { + // key: 'blog', + // label: ( + // + //

Blog

+ //

Latest updates and articles.

+ //
+ // ) + // }, + // { + // key: 'community', + // label: ( + // + //

Community

+ //

Join our developer community.

+ //
+ // ) + // } + // ] + // } + ]; + const popupRender = (node: ReactElement) => ( +
+
+ {React.Children.map(node.props.children.props.children, child => ( +
+ {React.cloneElement(child, { + className: `${child.props.className || ''} navigation-menu-item`, + })} +
+ ))} +
+
+ ); + + return ; +}; + +const MixedPanelDemo = () => { + const totalPopupRender = ( + node: ReactElement, + info: { item: any; keys: string[] }, + ) => { + const isSecondLevel = info.keys.length == 2; + if (isSecondLevel) { + return ( +
+
+ {React.Children.map(node.props.children.props.children, child => ( +
+ {React.cloneElement(child, { + className: `${child.props.className || ''} navigation-menu-item`, + })} +
+ ))} +
+
+ ); + } + return node; + }; + const singlePopupRender = ( + node: ReactElement, + info: { item: any; keys: string[] }, + ) => { + const isSecondLevel = info.keys.length == 2; + if (isSecondLevel) { + return ( +
+
+

{info.item.title}

+
+
{node}
+
+ ); + } + return node; + }; + return ( + + Home + + Product A + Product B + + + +

Product C

+

Description for Product C.

+
+
+ + +

Product D

+

Description for Product D.

+
+
+
+
+ + Enterprise + Personal + + Healthcare + Education + + +
+ ); +}; + +const Demo = () => { + return ( +
+

NavigationDemo

+ +

MixedPanelDemo

+ +
+ ); +}; +export default Demo; diff --git a/src/Menu.tsx b/src/Menu.tsx index edad0c5b..4df583e3 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -31,6 +31,7 @@ import type { SelectEventHandler, SelectInfo, TriggerSubMenuAction, + PopupRender, } from './interface'; import MenuItem from './MenuItem'; import SubMenu from './SubMenu'; @@ -158,6 +159,8 @@ export interface MenuProps * By zombieJ */ _internalComponents?: Components; + + popupRender?: PopupRender; } interface LegacyMenuProps extends MenuProps { @@ -240,6 +243,7 @@ const Menu = React.forwardRef((props, ref) => { _internalComponents, + popupRender, ...restProps } = props as LegacyMenuProps; @@ -663,6 +667,7 @@ const Menu = React.forwardRef((props, ref) => { // Events onItemClick={onInternalClick} onOpenChange={onInternalOpenChange} + popupRender={popupRender} > {container} diff --git a/src/SubMenu/index.tsx b/src/SubMenu/index.tsx index 3703918d..75b57bdc 100644 --- a/src/SubMenu/index.tsx +++ b/src/SubMenu/index.tsx @@ -4,7 +4,7 @@ import Overflow from 'rc-overflow'; import warning from 'rc-util/lib/warning'; import SubMenuList from './SubMenuList'; import { parseChildren } from '../utils/commonUtil'; -import type { MenuInfo, SubMenuType } from '../interface'; +import type { MenuInfo, SubMenuType, PopupRender } from '../interface'; import MenuContextProvider, { MenuContext } from '../context/MenuContext'; import useMemoCallback from '../hooks/useMemoCallback'; import PopupTrigger from './PopupTrigger'; @@ -36,7 +36,7 @@ export interface SubMenuProps /** @private Do not use. Private warning empty usage */ warnKey?: boolean; - + popupRender?: PopupRender; // >>>>>>>>>>>>>>>>>>>>> Next Round <<<<<<<<<<<<<<<<<<<<<<< // onDestroy?: DestroyEventHandler; } @@ -72,7 +72,7 @@ const InternalSubMenu = React.forwardRef( onTitleClick, onTitleMouseEnter, onTitleMouseLeave, - + popupRender: propsPopupRender, ...restProps } = props; @@ -102,6 +102,7 @@ const InternalSubMenu = React.forwardRef( onOpenChange, onActive, + popupRender: contextPopupRender, } = React.useContext(MenuContext); const { _internalRenderSubMenuItem } = React.useContext(PrivateContext); @@ -277,6 +278,30 @@ const InternalSubMenu = React.forwardRef( triggerModeRef.current = mode; } + const mergedPopupRender = propsPopupRender || contextPopupRender; + + // renderPopupContent + const renderPopupContent = () => { + const triggerMode = triggerModeRef.current; + const originNode = ( + + + {children} + + + ); + + if (mergedPopupRender) { + return mergedPopupRender(originNode, { + item: props, + keys: connectedPath, + }); + } + return originNode; + }; + if (!overflowDisabled) { const triggerMode = triggerModeRef.current; @@ -290,16 +315,7 @@ const InternalSubMenu = React.forwardRef( popupClassName={popupClassName} popupOffset={popupOffset} popupStyle={popupStyle} - popup={ - - - {children} - - - } + popup={renderPopupContent()} disabled={mergedDisabled} onVisibleChange={onPopupVisibleChange} > diff --git a/src/context/MenuContext.tsx b/src/context/MenuContext.tsx index c34c4bdf..44a39e52 100644 --- a/src/context/MenuContext.tsx +++ b/src/context/MenuContext.tsx @@ -8,6 +8,7 @@ import type { MenuMode, RenderIconType, TriggerSubMenuAction, + PopupRender, } from '../interface'; export interface MenuContextProps { @@ -46,6 +47,8 @@ export interface MenuContextProps { builtinPlacements?: BuiltinPlacements; triggerSubMenuAction?: TriggerSubMenuAction; + popupRender?: PopupRender; + // Icon itemIcon?: RenderIconType; expandIcon?: RenderIconType; diff --git a/src/interface.ts b/src/interface.ts index f47873d1..476b3bfb 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -139,4 +139,11 @@ export type MenuRef = { // ======================== Component ======================== export type ComponentType = 'submenu' | 'item' | 'group' | 'divider'; -export type Components = Partial>>; +export type Components = Partial< + Record> +>; + +export type PopupRender = ( + node: React.ReactElement, + info: { item: any; keys: string[] }, +) => React.ReactNode; diff --git a/tests/popupRender.test.tsx b/tests/popupRender.test.tsx new file mode 100644 index 00000000..f4be03c3 --- /dev/null +++ b/tests/popupRender.test.tsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import Menu, { SubMenu, Item as MenuItem } from '../src'; +import type { ReactElement } from 'react'; + +describe('Menu PopupRender Tests', () => { + // 基础测试数据 + const basicItems = [ + { + key: 'submenu1', + label: 'SubMenu 1', + children: [ + { key: 'child1', label: 'Child 1' }, + { key: 'child2', label: 'Child 2' }, + ], + }, + ]; + + // 基础 popupRender 函数 + const basicPopupRender = jest.fn((node: ReactElement) => { + return ( +
+ {React.cloneElement(node, { + className: `${node.props.className || ''} custom-popup-content`, + })} +
+ ); + }); + + describe('Basic PopupRender Functionality', () => { + it('should apply custom wrapper to popup content', async () => { + render( + + + Child + + , + ); + fireEvent.mouseEnter(screen.getByText('Test')); + // 验证自定义包装器 + await waitFor(() => { + expect(screen.getByTestId('custom-popup')).toBeInTheDocument(); + }); + }); + + it('should work with items prop configuration', async () => { + render( + , + ); + fireEvent.mouseEnter(screen.getByText('SubMenu 1')); + await waitFor(() => { + expect(screen.getByTestId('custom-popup')).toBeInTheDocument(); + expect(screen.getByText('Child 1')).toBeInTheDocument(); + }); + }); + }); + + describe('Nested PopupRender Behavior', () => { + const nestedPopupRender = ( + node: ReactElement, + info: { keys: string[] }, + ) => ( +
+ {node} +
+ ); + + it('should handle nested popupRender with different levels', async () => { + render( + + + + Child + + + , + ); + + // 触发一级菜单 + fireEvent.mouseEnter(screen.getByText('Level 1')); + await waitFor(() => { + expect(screen.getByTestId('level-1')).toBeInTheDocument(); + }); + + // 触发二级菜单 + fireEvent.mouseEnter(screen.getByText('Level 2')); + await waitFor(() => { + expect(screen.getByTestId('level-2')).toBeInTheDocument(); + }); + }); + }); + + describe('Conditional PopupRender', () => { + const conditionalPopupRender = ( + node: ReactElement, + info: { keys: string[] }, + ) => { + if (info.keys.length === 1) { + return ( +
+ {node} +
+ ); + } + return ( +
+ {node} +
+ ); + }; + + it('should apply different styles based on conditions', async () => { + render( + + + + Child + + + , + ); + + fireEvent.mouseEnter(screen.getByText('Main')); + await waitFor(() => { + expect(screen.getByTestId('style-a')).toBeInTheDocument(); + }); + + fireEvent.mouseEnter(screen.getByText('Sub')); + await waitFor(() => { + expect(screen.getByTestId('style-b')).toBeInTheDocument(); + }); + }); + }); + + describe('PopupRender with Content Modification', () => { + const contentModifyingPopupRender = (node: ReactElement) => { + return ( +
+
+ Menu +
+ {node} +
+ Footer +
+
+ ); + }; + + it('should add additional content to popup', async () => { + render( + + + Child + + , + ); + + fireEvent.mouseEnter(screen.getByText('Test')); + + await waitFor(() => { + expect(screen.getByTestId('popup-header')).toBeInTheDocument(); + expect(screen.getByTestId('popup-footer')).toBeInTheDocument(); + expect(screen.getByText('Child')).toBeInTheDocument(); + }); + }); + }); +}); From 378bf7464beac92a91fd7d8fbd217873ce6eaea6 Mon Sep 17 00:00:00 2001 From: zyf <81416635+Zyf665@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:43:10 +0000 Subject: [PATCH 2/7] rename file --- docs/demo/customPopupRender.md | 3 + docs/demo/navigation.md | 3 - ...navigation.less => customPopupRender.less} | 0 .../{navigation.tsx => customPopupRender.tsx} | 58 +++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) create mode 100644 docs/demo/customPopupRender.md delete mode 100644 docs/demo/navigation.md rename docs/examples/{navigation.less => customPopupRender.less} (100%) rename docs/examples/{navigation.tsx => customPopupRender.tsx} (85%) diff --git a/docs/demo/customPopupRender.md b/docs/demo/customPopupRender.md new file mode 100644 index 00000000..6e0b702a --- /dev/null +++ b/docs/demo/customPopupRender.md @@ -0,0 +1,3 @@ +## customPopupRender + + diff --git a/docs/demo/navigation.md b/docs/demo/navigation.md deleted file mode 100644 index abf1c551..00000000 --- a/docs/demo/navigation.md +++ /dev/null @@ -1,3 +0,0 @@ -## navigation - - diff --git a/docs/examples/navigation.less b/docs/examples/customPopupRender.less similarity index 100% rename from docs/examples/navigation.less rename to docs/examples/customPopupRender.less diff --git a/docs/examples/navigation.tsx b/docs/examples/customPopupRender.tsx similarity index 85% rename from docs/examples/navigation.tsx rename to docs/examples/customPopupRender.tsx index 16f7cb12..6804b3b6 100644 --- a/docs/examples/navigation.tsx +++ b/docs/examples/customPopupRender.tsx @@ -1,14 +1,14 @@ import React from 'react'; import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; import type { ReactElement } from 'react'; -import './navigation.less'; +import './customPopupRender.less'; const NavigationDemo = () => { const menuItems = [ - // { - // key: 'home', - // label: 'Home' - // }, + { + key: 'home', + label: 'Home', + }, { key: 'features', label: 'Features', @@ -42,30 +42,30 @@ const NavigationDemo = () => { }, ], }, - // { - // key: 'resources', - // label: 'Resources', - // children: [ - // { - // key: 'blog', - // label: ( - // - //

Blog

- //

Latest updates and articles.

- //
- // ) - // }, - // { - // key: 'community', - // label: ( - // - //

Community

- //

Join our developer community.

- //
- // ) - // } - // ] - // } + { + key: 'resources', + label: 'Resources', + children: [ + { + key: 'blog', + label: ( + +

Blog

+

Latest updates and articles.

+
+ ), + }, + { + key: 'community', + label: ( + +

Community

+

Join our developer community.

+
+ ), + }, + ], + }, ]; const popupRender = (node: ReactElement) => (
From 88b303a0947c8acfdaa607e0bc8cc12f2494741d Mon Sep 17 00:00:00 2001 From: zyf <1246271707@qq.com> Date: Wed, 19 Feb 2025 21:43:29 +0800 Subject: [PATCH 3/7] modify test cases and optimize renderPopupContent method --- src/SubMenu/index.tsx | 19 ++-- tests/popupRender.test.tsx | 206 ++++++++----------------------------- 2 files changed, 56 insertions(+), 169 deletions(-) diff --git a/src/SubMenu/index.tsx b/src/SubMenu/index.tsx index 75b57bdc..8cbb31cd 100644 --- a/src/SubMenu/index.tsx +++ b/src/SubMenu/index.tsx @@ -278,10 +278,7 @@ const InternalSubMenu = React.forwardRef( triggerModeRef.current = mode; } - const mergedPopupRender = propsPopupRender || contextPopupRender; - - // renderPopupContent - const renderPopupContent = () => { + const renderPopupContent = React.useMemo(() => { const triggerMode = triggerModeRef.current; const originNode = ( ( ); - + const mergedPopupRender = propsPopupRender || contextPopupRender; if (mergedPopupRender) { return mergedPopupRender(originNode, { item: props, @@ -300,7 +297,15 @@ const InternalSubMenu = React.forwardRef( }); } return originNode; - }; + }, [ + propsPopupRender, + contextPopupRender, + triggerModeRef, + connectedPath, + popupId, + children, + props, + ]); if (!overflowDisabled) { const triggerMode = triggerModeRef.current; @@ -315,7 +320,7 @@ const InternalSubMenu = React.forwardRef( popupClassName={popupClassName} popupOffset={popupOffset} popupStyle={popupStyle} - popup={renderPopupContent()} + popup={renderPopupContent} disabled={mergedDisabled} onVisibleChange={onPopupVisibleChange} > diff --git a/tests/popupRender.test.tsx b/tests/popupRender.test.tsx index f4be03c3..eadf5563 100644 --- a/tests/popupRender.test.tsx +++ b/tests/popupRender.test.tsx @@ -1,175 +1,57 @@ import React from 'react'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import Menu, { SubMenu, Item as MenuItem } from '../src'; import type { ReactElement } from 'react'; describe('Menu PopupRender Tests', () => { - // 基础测试数据 - const basicItems = [ - { - key: 'submenu1', - label: 'SubMenu 1', - children: [ - { key: 'child1', label: 'Child 1' }, - { key: 'child2', label: 'Child 2' }, - ], - }, - ]; - - // 基础 popupRender 函数 - const basicPopupRender = jest.fn((node: ReactElement) => { - return ( -
- {React.cloneElement(node, { - className: `${node.props.className || ''} custom-popup-content`, - })} -
- ); - }); - - describe('Basic PopupRender Functionality', () => { - it('should apply custom wrapper to popup content', async () => { - render( - - - Child - - , - ); - fireEvent.mouseEnter(screen.getByText('Test')); - // 验证自定义包装器 - await waitFor(() => { - expect(screen.getByTestId('custom-popup')).toBeInTheDocument(); - }); - }); - - it('should work with items prop configuration', async () => { - render( - , - ); - fireEvent.mouseEnter(screen.getByText('SubMenu 1')); - await waitFor(() => { - expect(screen.getByTestId('custom-popup')).toBeInTheDocument(); - expect(screen.getByText('Child 1')).toBeInTheDocument(); - }); - }); - }); - - describe('Nested PopupRender Behavior', () => { - const nestedPopupRender = ( - node: ReactElement, - info: { keys: string[] }, - ) => ( -
( +
+ {React.cloneElement(node, { + className: `${node.props.className || ''} custom-popup-content`, + })} +
+ ); + + it('should render popup with custom wrapper', () => { + render( + - {node} -
+ + Child + +
, ); - it('should handle nested popupRender with different levels', async () => { - render( - - - - Child - - - , - ); - - // 触发一级菜单 - fireEvent.mouseEnter(screen.getByText('Level 1')); - await waitFor(() => { - expect(screen.getByTestId('level-1')).toBeInTheDocument(); - }); - - // 触发二级菜单 - fireEvent.mouseEnter(screen.getByText('Level 2')); - await waitFor(() => { - expect(screen.getByTestId('level-2')).toBeInTheDocument(); - }); - }); + expect(screen.getByTestId('custom-popup')).toBeInTheDocument(); + expect(screen.getByText('Child')).toBeInTheDocument(); }); - describe('Conditional PopupRender', () => { - const conditionalPopupRender = ( - node: ReactElement, - info: { keys: string[] }, - ) => { - if (info.keys.length === 1) { - return ( -
- {node} -
- ); - } - return ( -
- {node} -
- ); - }; - - it('should apply different styles based on conditions', async () => { - render( - - - - Child - - - , - ); - - fireEvent.mouseEnter(screen.getByText('Main')); - await waitFor(() => { - expect(screen.getByTestId('style-a')).toBeInTheDocument(); - }); - - fireEvent.mouseEnter(screen.getByText('Sub')); - await waitFor(() => { - expect(screen.getByTestId('style-b')).toBeInTheDocument(); - }); - }); - }); - - describe('PopupRender with Content Modification', () => { - const contentModifyingPopupRender = (node: ReactElement) => { - return ( -
-
- Menu -
- {node} -
- Footer -
-
- ); - }; - - it('should add additional content to popup', async () => { - render( - - - Child - - , - ); - - fireEvent.mouseEnter(screen.getByText('Test')); + it('should work with items prop', () => { + const items = [ + { + key: 'submenu1', + label: 'SubMenu 1', + children: [ + { key: 'child1', label: 'Child 1' }, + { key: 'child2', label: 'Child 2' }, + ], + }, + ]; + + render( + , + ); - await waitFor(() => { - expect(screen.getByTestId('popup-header')).toBeInTheDocument(); - expect(screen.getByTestId('popup-footer')).toBeInTheDocument(); - expect(screen.getByText('Child')).toBeInTheDocument(); - }); - }); + expect(screen.getByTestId('custom-popup')).toBeInTheDocument(); + expect(screen.getByText('Child 1')).toBeInTheDocument(); + expect(screen.getByText('Child 2')).toBeInTheDocument(); }); }); From f8124dd76f89b5b35b98ee257fb202ff937b686f Mon Sep 17 00:00:00 2001 From: zyf <1246271707@qq.com> Date: Sun, 23 Feb 2025 14:13:51 +0800 Subject: [PATCH 4/7] modify test cases And popupContent code --- src/SubMenu/index.tsx | 14 +++++++++----- tests/popupRender.test.tsx | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/SubMenu/index.tsx b/src/SubMenu/index.tsx index 8cbb31cd..46a83a13 100644 --- a/src/SubMenu/index.tsx +++ b/src/SubMenu/index.tsx @@ -277,12 +277,15 @@ const InternalSubMenu = React.forwardRef( } else { triggerModeRef.current = mode; } - + const popupContentTriggerMode = triggerModeRef.current; const renderPopupContent = React.useMemo(() => { - const triggerMode = triggerModeRef.current; const originNode = ( {children} @@ -291,20 +294,21 @@ const InternalSubMenu = React.forwardRef( ); const mergedPopupRender = propsPopupRender || contextPopupRender; if (mergedPopupRender) { - return mergedPopupRender(originNode, { + const node = mergedPopupRender(originNode, { item: props, keys: connectedPath, }); + return node; } return originNode; }, [ propsPopupRender, contextPopupRender, - triggerModeRef, connectedPath, popupId, children, props, + popupContentTriggerMode, ]); if (!overflowDisabled) { diff --git a/tests/popupRender.test.tsx b/tests/popupRender.test.tsx index eadf5563..69ccb383 100644 --- a/tests/popupRender.test.tsx +++ b/tests/popupRender.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; import Menu, { SubMenu, Item as MenuItem } from '../src'; import type { ReactElement } from 'react'; @@ -12,8 +12,8 @@ describe('Menu PopupRender Tests', () => {
); - it('should render popup with custom wrapper', () => { - render( + it('should render popup with custom wrapper', async () => { + const { container } = render( { , ); - - expect(screen.getByTestId('custom-popup')).toBeInTheDocument(); - expect(screen.getByText('Child')).toBeInTheDocument(); + expect( + container.querySelectorAll('.rc-menu-submenu-horizontal')[0], + ).toHaveClass('rc-menu-submenu-open'); }); - it('should work with items prop', () => { + it('should work with items prop', async () => { const items = [ { key: 'submenu1', @@ -41,7 +41,7 @@ describe('Menu PopupRender Tests', () => { }, ]; - render( + const { container } = render( { />, ); - expect(screen.getByTestId('custom-popup')).toBeInTheDocument(); - expect(screen.getByText('Child 1')).toBeInTheDocument(); - expect(screen.getByText('Child 2')).toBeInTheDocument(); + expect( + container.querySelectorAll('.rc-menu-submenu-horizontal')[0], + ).toHaveClass('rc-menu-submenu-open'); }); }); From 38ac58d42a6e29d619005fc24db2ad6a63b616d3 Mon Sep 17 00:00:00 2001 From: Zyf665 <1246271707@qq.com> Date: Sun, 16 Mar 2025 13:48:36 +0800 Subject: [PATCH 5/7] fix type assertion for popupRender item prop --- src/interface.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interface.ts b/src/interface.ts index 476b3bfb..8ae7fad5 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,4 +1,5 @@ import type * as React from 'react'; +import type { SubMenuProps } from './SubMenu'; // ========================= Options ========================= interface ItemSharedProps { @@ -145,5 +146,5 @@ export type Components = Partial< export type PopupRender = ( node: React.ReactElement, - info: { item: any; keys: string[] }, + info: { item: SubMenuProps; keys: string[] }, ) => React.ReactNode; From caaf56f554f3f89b05d4f3ccf04819c6aef29031 Mon Sep 17 00:00:00 2001 From: MadCcc Date: Thu, 3 Apr 2025 15:25:46 +0800 Subject: [PATCH 6/7] chore: fix tsc --- docs/examples/customPopupRender.tsx | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/docs/examples/customPopupRender.tsx b/docs/examples/customPopupRender.tsx index 6804b3b6..cf2d7138 100644 --- a/docs/examples/customPopupRender.tsx +++ b/docs/examples/customPopupRender.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; +import Menu, { SubMenu, Item as MenuItem } from '../../src'; import type { ReactElement } from 'react'; import './customPopupRender.less'; @@ -85,10 +85,7 @@ const NavigationDemo = () => { }; const MixedPanelDemo = () => { - const totalPopupRender = ( - node: ReactElement, - info: { item: any; keys: string[] }, - ) => { + const totalPopupRender = (node: ReactElement, info: { item: any; keys: string[] }) => { const isSecondLevel = info.keys.length == 2; if (isSecondLevel) { return ( @@ -107,10 +104,7 @@ const MixedPanelDemo = () => { } return node; }; - const singlePopupRender = ( - node: ReactElement, - info: { item: any; keys: string[] }, - ) => { + const singlePopupRender = (node: ReactElement, info: { item: any; keys: string[] }) => { const isSecondLevel = info.keys.length == 2; if (isSecondLevel) { return ( @@ -148,11 +142,7 @@ const MixedPanelDemo = () => { Enterprise Personal - + Healthcare Education From b32753e20a4b537eda69f34ac1f215c141937500 Mon Sep 17 00:00:00 2001 From: MadCcc Date: Thu, 3 Apr 2025 16:43:04 +0800 Subject: [PATCH 7/7] chore: fix lint --- .eslintrc.js | 8 +++++++- src/Menu.tsx | 1 + src/context/MenuContext.tsx | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index a7964d70..feefc81a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,6 +12,12 @@ module.exports = { '@typescript-eslint/no-explicit-any': 0, 'jsx-a11y/label-has-associated-control': 0, 'jsx-a11y/label-has-for': 0, - '@typescript-eslint/no-empty-interface': "off", + '@typescript-eslint/no-empty-interface': 0, + '@typescript-eslint/consistent-indexed-object-style': 0, + '@typescript-eslint/switch-exhaustiveness-check': 0, + '@typescript-eslint/no-parameter-properties': 0, + '@typescript-eslint/no-throw-literal': 0, + '@typescript-eslint/type-annotation-spacing': 0, + '@typescript-eslint/ban-types': 0, }, }; diff --git a/src/Menu.tsx b/src/Menu.tsx index fef13401..0207191e 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -95,6 +95,7 @@ export interface MenuProps /** Menu motion define. Use `defaultMotions` if you need config motion of each mode */ motion?: CSSMotionProps; /** Default menu motion of each mode */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars defaultMotions?: Partial<{ [key in MenuMode | 'other']: CSSMotionProps }>; // Popup diff --git a/src/context/MenuContext.tsx b/src/context/MenuContext.tsx index e86fc5a2..5611c1ed 100644 --- a/src/context/MenuContext.tsx +++ b/src/context/MenuContext.tsx @@ -38,6 +38,7 @@ export interface MenuContextProps { // Motion motion?: CSSMotionProps; + // eslint-disable-next-line @typescript-eslint/no-unused-vars defaultMotions?: Partial<{ [key in MenuMode | 'other']: CSSMotionProps }>; // Popup