From 02f91bb2cdaec2c5137e68686443ea160af63168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Fri, 3 Jul 2026 10:54:42 +0800 Subject: [PATCH 1/6] feat: add popupRender for more popup customization - Add PopupRender type with tabs, activeKey, and onClose - Implement popupRender in OperationNode - Add demo example for search in overflow menu Co-Authored-By: Claude --- docs/demo/search-overflow.md | 8 ++++ docs/examples/search-overflow.tsx | 76 +++++++++++++++++++++++++++++++ src/TabNavList/OperationNode.tsx | 13 +++++- src/index.ts | 1 + src/interface.ts | 21 +++++++-- 5 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 docs/demo/search-overflow.md create mode 100644 docs/examples/search-overflow.tsx diff --git a/docs/demo/search-overflow.md b/docs/demo/search-overflow.md new file mode 100644 index 00000000..e663d77a --- /dev/null +++ b/docs/demo/search-overflow.md @@ -0,0 +1,8 @@ +--- +title: Search Overflow +nav: + title: Demo + path: /demo +--- + + diff --git a/docs/examples/search-overflow.tsx b/docs/examples/search-overflow.tsx new file mode 100644 index 00000000..e5c18c70 --- /dev/null +++ b/docs/examples/search-overflow.tsx @@ -0,0 +1,76 @@ +import React, { useState, useMemo } from 'react'; +import Tabs from '../../src'; +import type { TabsProps, PopupRender } from '../../src'; +import Menu, { MenuItem as MenuItemNode } from '@rc-component/menu'; + +const items: TabsProps['items'] = []; + +for (let i = 0; i < 15; i += 1) { + items.push({ key: String(i), label: `Tab ${i + 1}`, children: `Content of ${i}` }); +} + +export default () => { + const [activeKey, setActiveKey] = useState('0'); + const [searchValue, setSearchValue] = useState(''); + + // popupRender 回调:使用 info.tabs 重新渲染菜单 + const popupRender: PopupRender = (menu, { tabs, activeKey: selectedKey, onClose }) => { + // 过滤 tabs + const filteredTabs = useMemo(() => { + if (!searchValue) return tabs; + return tabs.filter(tab => + String(tab.label).toLowerCase().includes(searchValue.toLowerCase()), + ); + }, [tabs, searchValue]); + + // 检查当前 activeKey 是否在过滤后的列表中 + const isActiveKeyInFiltered = filteredTabs.some(t => t.key === selectedKey); + const selectedKeys = isActiveKeyInFiltered ? [selectedKey] : []; + + return ( +
+ setSearchValue(e.target.value)} + style={{ + width: '100%', + padding: '4px 8px', + marginBottom: '8px', + border: '1px solid #d9d9d9', + borderRadius: '4px', + outline: 'none', + boxSizing: 'border-box', + }} + onClick={e => e.stopPropagation()} + /> + { + setActiveKey(key); + onClose(); + }} + > + {filteredTabs.map(tab => ( + + {tab.label} + + ))} + +
+ ); + }; + + return ( +
+ +
+ ); +}; diff --git a/src/TabNavList/OperationNode.tsx b/src/TabNavList/OperationNode.tsx index a56ee5a9..3b243643 100644 --- a/src/TabNavList/OperationNode.tsx +++ b/src/TabNavList/OperationNode.tsx @@ -119,6 +119,17 @@ const OperationNode = React.forwardRef((prop ); + // Handle popupRender to allow custom popup content + const { popupRender } = moreProps; + const handleClose = () => setOpen(false); + const overlay = popupRender + ? popupRender(menu, { + tabs, + activeKey: props.activeKey, + onClose: handleClose, + }) + : menu; + function selectOffset(offset: -1 | 1) { const enabledTabs = tabs.filter(tab => !tab.disabled); let selectedIndex = enabledTabs.findIndex(tab => tab.key === selectedKey) || 0; @@ -196,7 +207,7 @@ const OperationNode = React.forwardRef((prop const moreNode: React.ReactNode = mobile ? null : ( & { + key: string; + label: React.ReactNode; +}; + +export type PopupRender = ( + menu: React.ReactElement, + info: { + tabs: Tab[]; + activeKey?: string; + onClose: () => void; + }, +) => React.ReactElement; + export type MoreProps = { icon?: moreIcon; + popupRender?: PopupRender; } & Omit; export type SizeInfo = [width: number, height: number]; @@ -31,11 +47,6 @@ export type TabOffsetMap = Map; export type TabPosition = 'left' | 'right' | 'top' | 'bottom'; -export interface Tab extends Omit { - key: string; - label: React.ReactNode; -} - type RenderTabBarProps = { id: string; activeKey: string; From be49ecb7346e7c903722a2cb3077d8ae1acf1d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Fri, 3 Jul 2026 11:18:34 +0800 Subject: [PATCH 2/6] feat: add popupRender for more dropdown customization - Add popupRender unit tests - Add documentation in README - Remove demo files (will be implemented in ant-design) Co-Authored-By: Claude --- README.md | 1 + docs/demo/search-overflow.md | 8 ---- docs/examples/search-overflow.tsx | 76 ------------------------------- tests/overflow.test.tsx | 74 ++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 84 deletions(-) delete mode 100644 docs/demo/search-overflow.md delete mode 100644 docs/examples/search-overflow.tsx diff --git a/README.md b/README.md index 76ab4366..e8221408 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ ReactDom.render( | editable | { onEdit(type: 'add' \| 'remove', info: { key, event }), showAdd: boolean, removeIcon: ReactNode, addIcon: ReactNode } | - | config tab editable | | locale | { dropdownAriaLabel: string, removeAriaLabel: string, addAriaLabel: string } | - | Accessibility locale help text | | moreIcon | ReactNode | - | collapse icon | +| more | { icon?: ReactNode, popupRender?: (menu: ReactElement, info: { tabs: Tab[], activeKey?: string, onClose: () => void }) => ReactElement } | - | more dropdown config, support custom popup content | ### TabItem diff --git a/docs/demo/search-overflow.md b/docs/demo/search-overflow.md deleted file mode 100644 index e663d77a..00000000 --- a/docs/demo/search-overflow.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Search Overflow -nav: - title: Demo - path: /demo ---- - - diff --git a/docs/examples/search-overflow.tsx b/docs/examples/search-overflow.tsx deleted file mode 100644 index e5c18c70..00000000 --- a/docs/examples/search-overflow.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { useState, useMemo } from 'react'; -import Tabs from '../../src'; -import type { TabsProps, PopupRender } from '../../src'; -import Menu, { MenuItem as MenuItemNode } from '@rc-component/menu'; - -const items: TabsProps['items'] = []; - -for (let i = 0; i < 15; i += 1) { - items.push({ key: String(i), label: `Tab ${i + 1}`, children: `Content of ${i}` }); -} - -export default () => { - const [activeKey, setActiveKey] = useState('0'); - const [searchValue, setSearchValue] = useState(''); - - // popupRender 回调:使用 info.tabs 重新渲染菜单 - const popupRender: PopupRender = (menu, { tabs, activeKey: selectedKey, onClose }) => { - // 过滤 tabs - const filteredTabs = useMemo(() => { - if (!searchValue) return tabs; - return tabs.filter(tab => - String(tab.label).toLowerCase().includes(searchValue.toLowerCase()), - ); - }, [tabs, searchValue]); - - // 检查当前 activeKey 是否在过滤后的列表中 - const isActiveKeyInFiltered = filteredTabs.some(t => t.key === selectedKey); - const selectedKeys = isActiveKeyInFiltered ? [selectedKey] : []; - - return ( -
- setSearchValue(e.target.value)} - style={{ - width: '100%', - padding: '4px 8px', - marginBottom: '8px', - border: '1px solid #d9d9d9', - borderRadius: '4px', - outline: 'none', - boxSizing: 'border-box', - }} - onClick={e => e.stopPropagation()} - /> - { - setActiveKey(key); - onClose(); - }} - > - {filteredTabs.map(tab => ( - - {tab.label} - - ))} - -
- ); - }; - - return ( -
- -
- ); -}; diff --git a/tests/overflow.test.tsx b/tests/overflow.test.tsx index 6f4e6e84..cb539dbc 100644 --- a/tests/overflow.test.tsx +++ b/tests/overflow.test.tsx @@ -1,5 +1,6 @@ import { act, fireEvent, render } from '@testing-library/react'; import { KeyCode, spyElementPrototypes } from '@rc-component/util'; +import Menu from '@rc-component/menu'; import React from 'react'; import type { TabsProps } from '../src'; import Tabs from '../src'; @@ -671,4 +672,77 @@ describe('Tabs.Overflow', () => { unmount(); jest.useRealTimers(); }); + + describe('popupRender', () => { + it('should call popupRender with correct params', () => { + jest.useFakeTimers(); + const popupRender = jest.fn((menu, info) => menu); + const { container } = render(getTabs({ more: { popupRender } })); + + triggerResize(container); + act(() => { + jest.runAllTimers(); + }); + fireEvent.mouseEnter(container.querySelector('.rc-tabs-nav-more')); + act(() => { + jest.runAllTimers(); + }); + + expect(popupRender).toHaveBeenCalled(); + const callArgs = popupRender.mock.calls[0]; + // 验证第一个参数是 menu 元素 + expect(callArgs[0].type).toBe(Menu); + // 验证第二个参数包含 tabs + expect(callArgs[1]).toHaveProperty('tabs'); + expect(callArgs[1]).toHaveProperty('activeKey'); + expect(callArgs[1]).toHaveProperty('onClose'); + + jest.useRealTimers(); + }); + + it('should render custom popup content', () => { + jest.useFakeTimers(); + const popupRender = jest.fn((menu, { tabs }) => ( +
+
{tabs.length}
+ {menu} +
+ )); + const { container } = render(getTabs({ more: { popupRender } })); + + triggerResize(container); + act(() => { + jest.runAllTimers(); + }); + fireEvent.mouseEnter(container.querySelector('.rc-tabs-nav-more')); + act(() => { + jest.runAllTimers(); + }); + + expect(document.querySelector('[data-testid="custom-popup"]')).toBeTruthy(); + expect(document.querySelector('[data-testid="tab-count"]')).toBeTruthy(); + + jest.useRealTimers(); + }); + + it('should pass onClose function', () => { + jest.useFakeTimers(); + const popupRender = jest.fn((menu, { onClose }) => menu); + const { container } = render(getTabs({ more: { popupRender } })); + + triggerResize(container); + act(() => { + jest.runAllTimers(); + }); + fireEvent.mouseEnter(container.querySelector('.rc-tabs-nav-more')); + act(() => { + jest.runAllTimers(); + }); + + const callArgs = popupRender.mock.calls[0]; + expect(typeof callArgs[1].onClose).toBe('function'); + + jest.useRealTimers(); + }); + }); }); From 6c85f8433a96f6532552d8f8dea380986a039326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Fri, 3 Jul 2026 12:40:23 +0800 Subject: [PATCH 3/6] feat: remove activeKey from popupRender parameters for more dropdown customization --- README.md | 2 +- src/TabNavList/OperationNode.tsx | 1 - src/interface.ts | 1 - tests/overflow.test.tsx | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index e8221408..69725db9 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ ReactDom.render( | editable | { onEdit(type: 'add' \| 'remove', info: { key, event }), showAdd: boolean, removeIcon: ReactNode, addIcon: ReactNode } | - | config tab editable | | locale | { dropdownAriaLabel: string, removeAriaLabel: string, addAriaLabel: string } | - | Accessibility locale help text | | moreIcon | ReactNode | - | collapse icon | -| more | { icon?: ReactNode, popupRender?: (menu: ReactElement, info: { tabs: Tab[], activeKey?: string, onClose: () => void }) => ReactElement } | - | more dropdown config, support custom popup content | +| more | { icon?: ReactNode, popupRender?: (menu: ReactElement, info: { tabs: Tab[], onClose: () => void }) => ReactElement } | - | more dropdown config, support custom popup content | ### TabItem diff --git a/src/TabNavList/OperationNode.tsx b/src/TabNavList/OperationNode.tsx index 3b243643..c14d4e8d 100644 --- a/src/TabNavList/OperationNode.tsx +++ b/src/TabNavList/OperationNode.tsx @@ -125,7 +125,6 @@ const OperationNode = React.forwardRef((prop const overlay = popupRender ? popupRender(menu, { tabs, - activeKey: props.activeKey, onClose: handleClose, }) : menu; diff --git a/src/interface.ts b/src/interface.ts index edb887fa..287d5f83 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -18,7 +18,6 @@ export type PopupRender = ( menu: React.ReactElement, info: { tabs: Tab[]; - activeKey?: string; onClose: () => void; }, ) => React.ReactElement; diff --git a/tests/overflow.test.tsx b/tests/overflow.test.tsx index cb539dbc..3ca791ed 100644 --- a/tests/overflow.test.tsx +++ b/tests/overflow.test.tsx @@ -694,7 +694,6 @@ describe('Tabs.Overflow', () => { expect(callArgs[0].type).toBe(Menu); // 验证第二个参数包含 tabs expect(callArgs[1]).toHaveProperty('tabs'); - expect(callArgs[1]).toHaveProperty('activeKey'); expect(callArgs[1]).toHaveProperty('onClose'); jest.useRealTimers(); From cbd487203322b9b1a77b7c063a774242c0141848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Fri, 3 Jul 2026 12:48:55 +0800 Subject: [PATCH 4/6] feat: enhance popupRender support for custom content in OperationNode --- src/TabNavList/OperationNode.tsx | 7 +-- tests/overflow.test.tsx | 92 ++++++++++---------------------- 2 files changed, 30 insertions(+), 69 deletions(-) diff --git a/src/TabNavList/OperationNode.tsx b/src/TabNavList/OperationNode.tsx index c14d4e8d..db3a13bd 100644 --- a/src/TabNavList/OperationNode.tsx +++ b/src/TabNavList/OperationNode.tsx @@ -57,7 +57,7 @@ const OperationNode = React.forwardRef((prop const [open, setOpen] = useState(false); const [selectedKey, setSelectedKey] = useState(null); - const { icon: moreIcon = 'More' } = moreProps; + const { icon: moreIcon = 'More', popupRender } = moreProps; const popupId = `${id}-more-popup`; const dropdownPrefix = `${prefixCls}-dropdown`; @@ -119,13 +119,10 @@ const OperationNode = React.forwardRef((prop ); - // Handle popupRender to allow custom popup content - const { popupRender } = moreProps; - const handleClose = () => setOpen(false); const overlay = popupRender ? popupRender(menu, { tabs, - onClose: handleClose, + onClose: () => setOpen(false), }) : menu; diff --git a/tests/overflow.test.tsx b/tests/overflow.test.tsx index 3ca791ed..f8d6d668 100644 --- a/tests/overflow.test.tsx +++ b/tests/overflow.test.tsx @@ -673,75 +673,39 @@ describe('Tabs.Overflow', () => { jest.useRealTimers(); }); - describe('popupRender', () => { - it('should call popupRender with correct params', () => { - jest.useFakeTimers(); - const popupRender = jest.fn((menu, info) => menu); - const { container } = render(getTabs({ more: { popupRender } })); - - triggerResize(container); - act(() => { - jest.runAllTimers(); - }); - fireEvent.mouseEnter(container.querySelector('.rc-tabs-nav-more')); - act(() => { - jest.runAllTimers(); - }); - - expect(popupRender).toHaveBeenCalled(); - const callArgs = popupRender.mock.calls[0]; - // 验证第一个参数是 menu 元素 - expect(callArgs[0].type).toBe(Menu); - // 验证第二个参数包含 tabs - expect(callArgs[1]).toHaveProperty('tabs'); - expect(callArgs[1]).toHaveProperty('onClose'); + it('should pass correct params and support custom popup content', () => { + jest.useFakeTimers(); + const popupRender = jest.fn((menu, { tabs }) => ( +
+
{tabs.length}
+ {menu} +
+ )); + const { container } = render(getTabs({ more: { popupRender } })); - jest.useRealTimers(); + triggerResize(container); + act(() => { + jest.runAllTimers(); }); - - it('should render custom popup content', () => { - jest.useFakeTimers(); - const popupRender = jest.fn((menu, { tabs }) => ( -
-
{tabs.length}
- {menu} -
- )); - const { container } = render(getTabs({ more: { popupRender } })); - - triggerResize(container); - act(() => { - jest.runAllTimers(); - }); - fireEvent.mouseEnter(container.querySelector('.rc-tabs-nav-more')); - act(() => { - jest.runAllTimers(); - }); - - expect(document.querySelector('[data-testid="custom-popup"]')).toBeTruthy(); - expect(document.querySelector('[data-testid="tab-count"]')).toBeTruthy(); - - jest.useRealTimers(); + fireEvent.mouseEnter(container.querySelector('.rc-tabs-nav-more')); + act(() => { + jest.runAllTimers(); }); - it('should pass onClose function', () => { - jest.useFakeTimers(); - const popupRender = jest.fn((menu, { onClose }) => menu); - const { container } = render(getTabs({ more: { popupRender } })); - - triggerResize(container); - act(() => { - jest.runAllTimers(); - }); - fireEvent.mouseEnter(container.querySelector('.rc-tabs-nav-more')); - act(() => { - jest.runAllTimers(); - }); - - const callArgs = popupRender.mock.calls[0]; - expect(typeof callArgs[1].onClose).toBe('function'); + expect(popupRender).toHaveBeenCalled(); + const callArgs = popupRender.mock.calls[0]; + expect(callArgs[1]).toHaveProperty('tabs'); + expect(callArgs[1]).toHaveProperty('onClose'); + expect(document.querySelector('[data-testid="custom-popup"]')).toBeTruthy(); - jest.useRealTimers(); + const onClose = callArgs[1].onClose; + fireEvent.click(document.querySelector('.rc-tabs-dropdown')); + act(() => { + onClose(); + jest.runAllTimers(); }); + expect(container.querySelector('.rc-tabs-dropdown')).toBeFalsy(); + + jest.useRealTimers(); }); }); From c9d9aa6be18cf6f0edc31f96dc1d863f7bafdbe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Fri, 3 Jul 2026 13:32:16 +0800 Subject: [PATCH 5/6] fix: lint error --- tests/overflow.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/overflow.test.tsx b/tests/overflow.test.tsx index f8d6d668..ae94617e 100644 --- a/tests/overflow.test.tsx +++ b/tests/overflow.test.tsx @@ -1,6 +1,5 @@ import { act, fireEvent, render } from '@testing-library/react'; import { KeyCode, spyElementPrototypes } from '@rc-component/util'; -import Menu from '@rc-component/menu'; import React from 'react'; import type { TabsProps } from '../src'; import Tabs from '../src'; From bbd079ead4354ab544edacd0d512dc4bc3fb4153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=AC=A2?= Date: Fri, 3 Jul 2026 15:18:55 +0800 Subject: [PATCH 6/6] feat: update README to include MoreProps details and enhance dropdown customization tests --- README.md | 11 +++++++++-- README.zh-CN.md | 31 +++++++++++++++++++------------ tests/overflow.test.tsx | 4 ++-- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c06a55b7..43a40c03 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@

English | 简体中文

- ## Highlights - Supports top, bottom, left, and right tab positions with RTL layouts. @@ -75,7 +74,7 @@ Then open `http://localhost:8000`. | `indicator` | `{ size?: GetIndicatorSize; align?: 'start' \| 'center' \| 'end' }` | - | Indicator size and alignment. | | `items` | Tab[] | [] | Tab items. | | `locale` | TabsLocale | - | Accessibility locale text. | -| `more` | { icon?: ReactNode, popupRender?: (menu: ReactElement, info: { tabs: Tab[], onClose: () => void }) => ReactElement } | - | more dropdown config, support custom popup content | +| `more` | MoreProps | - | more dropdown config, see [MoreProps](#moreprops) for full API. Additionally supports `popupRender` for custom popup content. | | `onChange` | `(activeKey: string) => void` | - | Triggered when active tab changes. | | `onTabClick` | `(activeKey, event) => void` | - | Triggered when a tab is clicked. | | `onTabScroll` | `({ direction }) => void` | - | Triggered when tab navigation scrolls. | @@ -103,6 +102,14 @@ Then open `http://localhost:8000`. | `label` | React.ReactNode | - | Tab label. | | `style` | React.CSSProperties | - | Panel style. | +### MoreProps + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| `icon` | ReactNode | - | The icon shown in the more trigger. | +| `popupRender` | `(menu: ReactElement, info: { tabs: Tab[], onClose: () => void }) => ReactElement` | - | Customize the dropdown popup content. The `info` object provides `tabs` (all overflowed tabs) and `onClose` (function to close the dropdown). | +| Other dropdown props | from DropdownProps | - | All other [rc-dropdown](https://github.com/react-component/dropdown) props such as `trigger`, `overlayClassName`, `visible`, etc. are also supported. | + ## Development ```bash diff --git a/README.zh-CN.md b/README.zh-CN.md index c6d5c424..cacc85b0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -15,7 +15,6 @@

English | 简体中文

- ## 特性 - 支持 RTL 布局的顶部、底部、左侧和右侧选项卡位置。 @@ -75,7 +74,7 @@ npm start | `indicator` | `{ size?: GetIndicatorSize; align?: 'start' \| 'center' \| 'end' }` | - | 指示器尺寸和对齐方式。 | | `items` | Tab[] | [] | 选项卡项目。 | | `locale` | TabsLocale | - | 无障碍本地化文本。 | -| `more` | MoreProps | - | 溢出下拉菜单配置。 | +| `more` | MoreProps | - | 溢出下拉菜单配置,详情见 [MoreProps](#moreprops)。支持 `popupRender` 自定义弹层内容。 | | `onChange` | `(activeKey: string) => void` | - | 当活动选项卡更改时触发。 | | `onTabClick` | `(activeKey, event) => void` | - | 单击选项卡时触发。 | | `onTabScroll` | `({ direction }) => void` | - | 当选项卡导航滚动时触发。 | @@ -90,18 +89,26 @@ npm start ### Tab +| 名称 | 类型 | 默认值 | 说明 | +| ----------------- | ------------------- | ------ | ---------------------------------- | +| `children` | React.ReactNode | - | 选项卡面板内容。 | +| `className` | string | - | 面板 className。 | +| `closable` | boolean | - | 是否可以在可编辑模式下关闭选项卡。 | +| `closeIcon` | React.ReactNode | - | 自定义关闭图标。 | +| `destroyOnHidden` | boolean | false | 销毁非活动面板。 | +| `disabled` | boolean | false | 禁用该选项卡。 | +| `forceRender` | boolean | false | 在面板变为活动状态之前渲染面板。 | +| `key` | string | - | 需要唯一的 Tab 键。 | +| `label` | React.ReactNode | - | Tab 标签内容。 | +| `style` | React.CSSProperties | - | 面板样式。 | + +### MoreProps + | 名称 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | -| `children` | React.ReactNode | - | 选项卡面板内容。 | -| `className` | string | - | 面板 className。 | -| `closable` | boolean | - | 是否可以在可编辑模式下关闭选项卡。 | -| `closeIcon` | React.ReactNode | - | 自定义关闭图标。 | -| `destroyOnHidden` | boolean | false | 销毁非活动面板。 | -| `disabled` | boolean | false | 禁用该选项卡。 | -| `forceRender` | boolean | false | 在面板变为活动状态之前渲染面板。 | -| `key` | string | - | 需要唯一的 Tab 键。 | -| `label` | React.ReactNode | - | Tab 标签内容。 | -| `style` | React.CSSProperties | - | 面板样式。 | +| `icon` | ReactNode | - | 更多按钮的图标。 | +| `popupRender` | `(menu: ReactElement, info: { tabs: Tab[], onClose: () => void }) => ReactElement` | - | 自定义下拉弹层内容。`info` 对象提供 `tabs`(所有溢出标签)和 `onClose`(关闭下拉菜单的函数)。 | +| 其他下拉属性 | 来自 DropdownProps | - | 其他 [rc-dropdown](https://github.com/react-component/dropdown) 属性如 `trigger`、`overlayClassName`、`visible` 等也都支持。 | ## 本地开发 diff --git a/tests/overflow.test.tsx b/tests/overflow.test.tsx index ae94617e..ab46a7ad 100644 --- a/tests/overflow.test.tsx +++ b/tests/overflow.test.tsx @@ -698,12 +698,12 @@ describe('Tabs.Overflow', () => { expect(document.querySelector('[data-testid="custom-popup"]')).toBeTruthy(); const onClose = callArgs[1].onClose; - fireEvent.click(document.querySelector('.rc-tabs-dropdown')); act(() => { onClose(); jest.runAllTimers(); }); - expect(container.querySelector('.rc-tabs-dropdown')).toBeFalsy(); + const dropdownPopup = document.querySelector('.rc-tabs-dropdown'); + expect(dropdownPopup?.classList.contains('rc-tabs-dropdown-hidden')).toBeTruthy(); jest.useRealTimers(); });