diff --git a/README.md b/README.md index ee99138f..3200d588 100644 --- a/README.md +++ b/README.md @@ -74,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. | @@ -102,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: { restTabs: Tab[], onClose: () => void }) => ReactElement` | - | Customize the dropdown popup content. The `info` object provides `restTabs` (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 870e5fc6..1ec0a21a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -74,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` | - | 当选项卡导航滚动时触发。 | @@ -102,6 +102,14 @@ npm start | `label` | React.ReactNode | - | Tab 标签内容。 | | `style` | React.CSSProperties | - | 面板样式。 | +### MoreProps + +| 名称 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `icon` | ReactNode | - | 更多按钮的图标。 | +| `popupRender` | `(menu: ReactElement, info: { restTabs: Tab[], onClose: () => void }) => ReactElement` | - | 自定义下拉弹层内容。`info` 对象提供 `restTabs`(所有溢出标签)和 `onClose`(关闭下拉菜单的函数)。 | +| 其他下拉属性 | 来自 DropdownProps | - | 其他 [rc-dropdown](https://github.com/react-component/dropdown) 属性如 `trigger`、`overlayClassName`、`visible` 等也都支持。 | + ## 本地开发 ```bash diff --git a/src/TabNavList/OperationNode.tsx b/src/TabNavList/OperationNode.tsx index a56ee5a9..59a33952 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, { + restTabs: 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: { + restTabs: 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..03a70ad6 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, { restTabs }) => ( +
+
{restTabs.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('restTabs'); + 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(); + }); });