diff --git a/README.md b/README.md index 8b89fc66..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` | MoreProps | - | Overflow dropdown config. | +| `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/src/TabNavList/OperationNode.tsx b/src/TabNavList/OperationNode.tsx index a56ee5a9..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,6 +119,13 @@ const OperationNode = React.forwardRef((prop ); + const overlay = popupRender + ? popupRender(menu, { + tabs, + onClose: () => setOpen(false), + }) + : menu; + function selectOffset(offset: -1 | 1) { const enabledTabs = tabs.filter(tab => !tab.disabled); let selectedIndex = enabledTabs.findIndex(tab => tab.key === selectedKey) || 0; @@ -196,7 +203,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[]; + onClose: () => void; + }, +) => React.ReactElement; + export type MoreProps = { icon?: moreIcon; + popupRender?: PopupRender; } & Omit; export type SizeInfo = [width: number, height: number]; @@ -31,11 +46,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; diff --git a/tests/overflow.test.tsx b/tests/overflow.test.tsx index 6f4e6e84..ab46a7ad 100644 --- a/tests/overflow.test.tsx +++ b/tests/overflow.test.tsx @@ -671,4 +671,40 @@ describe('Tabs.Overflow', () => { unmount(); jest.useRealTimers(); }); + + 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 } })); + + 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]; + expect(callArgs[1]).toHaveProperty('tabs'); + expect(callArgs[1]).toHaveProperty('onClose'); + expect(document.querySelector('[data-testid="custom-popup"]')).toBeTruthy(); + + const onClose = callArgs[1].onClose; + act(() => { + onClose(); + jest.runAllTimers(); + }); + const dropdownPopup = document.querySelector('.rc-tabs-dropdown'); + expect(dropdownPopup?.classList.contains('rc-tabs-dropdown-hidden')).toBeTruthy(); + + jest.useRealTimers(); + }); });