Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

<p align="center">English | <a href="./README.zh-CN.md">简体中文</a></p>


## Highlights

- Supports top, bottom, left, and right tab positions with RTL layouts.
Expand Down Expand Up @@ -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. |
Expand Down Expand Up @@ -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
Expand Down
31 changes: 19 additions & 12 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

<p align="center"><a href="./README.md">English</a> | 简体中文</p>


## 特性

- 支持 RTL 布局的顶部、底部、左侧和右侧选项卡位置。
Expand Down Expand Up @@ -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` | - | 当选项卡导航滚动时触发。 |
Expand All @@ -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` 等也都支持。 |

## 本地开发

Expand Down
11 changes: 9 additions & 2 deletions src/TabNavList/OperationNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
const [open, setOpen] = useState(false);
const [selectedKey, setSelectedKey] = useState<string>(null);

const { icon: moreIcon = 'More' } = moreProps;
const { icon: moreIcon = 'More', popupRender } = moreProps;

const popupId = `${id}-more-popup`;
const dropdownPrefix = `${prefixCls}-dropdown`;
Expand Down Expand Up @@ -119,6 +119,13 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
</Menu>
);

const overlay = popupRender
? popupRender(menu, {
tabs,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restTabs ?

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;
Expand Down Expand Up @@ -196,7 +203,7 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
const moreNode: React.ReactNode = mobile ? null : (
<Dropdown
prefixCls={dropdownPrefix}
overlay={menu}
overlay={overlay}
visible={tabs.length ? open : false}
onVisibleChange={setOpen}
overlayClassName={overlayClassName}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type {
AnimatedConfig,
EditableConfig,
MoreProps,
PopupRender,
Tab,
TabBarExtraContent,
} from './interface';
Expand Down
20 changes: 15 additions & 5 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,23 @@ export type TriggerProps = {
trigger?: 'hover' | 'click';
};
export type moreIcon = React.ReactNode;

export type Tab = Omit<TabPaneProps, 'tab'> & {
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<DropdownProps, 'children'>;

export type SizeInfo = [width: number, height: number];
Expand All @@ -31,11 +46,6 @@ export type TabOffsetMap = Map<React.Key, TabOffset>;

export type TabPosition = 'left' | 'right' | 'top' | 'bottom';

export interface Tab extends Omit<TabPaneProps, 'tab'> {
key: string;
label: React.ReactNode;
}

type RenderTabBarProps = {
id: string;
activeKey: string;
Expand Down
36 changes: 36 additions & 0 deletions tests/overflow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => (
<div data-testid="custom-popup">
<div data-testid="tab-count">{tabs.length}</div>
{menu}
</div>
));
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();
});
});
Loading