Skip to content

Commit 02f91bb

Browse files
刘欢claude
andcommitted
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 <noreply@anthropic.com>
1 parent 926f67e commit 02f91bb

5 files changed

Lines changed: 113 additions & 6 deletions

File tree

docs/demo/search-overflow.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: Search Overflow
3+
nav:
4+
title: Demo
5+
path: /demo
6+
---
7+
8+
<code src="../examples/search-overflow.tsx"></code>

docs/examples/search-overflow.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React, { useState, useMemo } from 'react';
2+
import Tabs from '../../src';
3+
import type { TabsProps, PopupRender } from '../../src';
4+
import Menu, { MenuItem as MenuItemNode } from '@rc-component/menu';
5+
6+
const items: TabsProps['items'] = [];
7+
8+
for (let i = 0; i < 15; i += 1) {
9+
items.push({ key: String(i), label: `Tab ${i + 1}`, children: `Content of ${i}` });
10+
}
11+
12+
export default () => {
13+
const [activeKey, setActiveKey] = useState('0');
14+
const [searchValue, setSearchValue] = useState('');
15+
16+
// popupRender 回调:使用 info.tabs 重新渲染菜单
17+
const popupRender: PopupRender = (menu, { tabs, activeKey: selectedKey, onClose }) => {
18+
// 过滤 tabs
19+
const filteredTabs = useMemo(() => {
20+
if (!searchValue) return tabs;
21+
return tabs.filter(tab =>
22+
String(tab.label).toLowerCase().includes(searchValue.toLowerCase()),
23+
);
24+
}, [tabs, searchValue]);
25+
26+
// 检查当前 activeKey 是否在过滤后的列表中
27+
const isActiveKeyInFiltered = filteredTabs.some(t => t.key === selectedKey);
28+
const selectedKeys = isActiveKeyInFiltered ? [selectedKey] : [];
29+
30+
return (
31+
<div style={{ padding: '8px' }}>
32+
<input
33+
placeholder="搜索 Tab..."
34+
value={searchValue}
35+
onChange={e => setSearchValue(e.target.value)}
36+
style={{
37+
width: '100%',
38+
padding: '4px 8px',
39+
marginBottom: '8px',
40+
border: '1px solid #d9d9d9',
41+
borderRadius: '4px',
42+
outline: 'none',
43+
boxSizing: 'border-box',
44+
}}
45+
onClick={e => e.stopPropagation()}
46+
/>
47+
<Menu
48+
selectedKeys={selectedKeys}
49+
onClick={({ key }) => {
50+
setActiveKey(key);
51+
onClose();
52+
}}
53+
>
54+
{filteredTabs.map(tab => (
55+
<MenuItemNode key={tab.key} disabled={tab.disabled}>
56+
{tab.label}
57+
</MenuItemNode>
58+
))}
59+
</Menu>
60+
</div>
61+
);
62+
};
63+
64+
return (
65+
<div style={{ maxWidth: 550 }}>
66+
<Tabs
67+
activeKey={activeKey}
68+
onChange={setActiveKey}
69+
items={items}
70+
more={{
71+
popupRender,
72+
}}
73+
/>
74+
</div>
75+
);
76+
};

src/TabNavList/OperationNode.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
119119
</Menu>
120120
);
121121

122+
// Handle popupRender to allow custom popup content
123+
const { popupRender } = moreProps;
124+
const handleClose = () => setOpen(false);
125+
const overlay = popupRender
126+
? popupRender(menu, {
127+
tabs,
128+
activeKey: props.activeKey,
129+
onClose: handleClose,
130+
})
131+
: menu;
132+
122133
function selectOffset(offset: -1 | 1) {
123134
const enabledTabs = tabs.filter(tab => !tab.disabled);
124135
let selectedIndex = enabledTabs.findIndex(tab => tab.key === selectedKey) || 0;
@@ -196,7 +207,7 @@ const OperationNode = React.forwardRef<HTMLDivElement, OperationNodeProps>((prop
196207
const moreNode: React.ReactNode = mobile ? null : (
197208
<Dropdown
198209
prefixCls={dropdownPrefix}
199-
overlay={menu}
210+
overlay={overlay}
200211
visible={tabs.length ? open : false}
201212
onVisibleChange={setOpen}
202213
overlayClassName={overlayClassName}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type {
55
AnimatedConfig,
66
EditableConfig,
77
MoreProps,
8+
PopupRender,
89
Tab,
910
TabBarExtraContent,
1011
} from './interface';

src/interface.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,24 @@ export type TriggerProps = {
88
trigger?: 'hover' | 'click';
99
};
1010
export type moreIcon = React.ReactNode;
11+
12+
export type Tab = Omit<TabPaneProps, 'tab'> & {
13+
key: string;
14+
label: React.ReactNode;
15+
};
16+
17+
export type PopupRender = (
18+
menu: React.ReactElement,
19+
info: {
20+
tabs: Tab[];
21+
activeKey?: string;
22+
onClose: () => void;
23+
},
24+
) => React.ReactElement;
25+
1126
export type MoreProps = {
1227
icon?: moreIcon;
28+
popupRender?: PopupRender;
1329
} & Omit<DropdownProps, 'children'>;
1430

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

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

34-
export interface Tab extends Omit<TabPaneProps, 'tab'> {
35-
key: string;
36-
label: React.ReactNode;
37-
}
38-
3950
type RenderTabBarProps = {
4051
id: string;
4152
activeKey: string;

0 commit comments

Comments
 (0)