Skip to content

Commit 2e730f5

Browse files
committed
feat(catalogue): support tabs in cataligue
1 parent 9545f76 commit 2e730f5

File tree

6 files changed

+257
-42
lines changed

6 files changed

+257
-42
lines changed

src/catalogue/components/catalogue.tsx

Lines changed: 89 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
import React from 'react';
2-
import { Dropdown, DropdownProps, Form, Input } from 'antd';
1+
import React, { useState } from 'react';
2+
import { Dropdown, DropdownProps, Form, Input, Tabs } from 'antd';
33
import { BlockHeader } from 'dt-react-component';
44
import { IBlockHeaderProps } from 'dt-react-component/blockHeader';
55

66
import { InputMode, ITreeNode, useTreeData } from '../useTreeData';
7-
import { CatalogIcon, DragIcon, EllipsisIcon } from './icon';
7+
import { CatalogIcon, CloseIcon, DragIcon, EllipsisIcon, SearchIcon } from './icon';
88
import CatalogueTree, { ICatalogueTree } from './tree';
99

10-
export interface ICatalogue
10+
interface Tab {
11+
readonly key: string;
12+
readonly title: React.ReactNode;
13+
}
14+
15+
type readOnlyTab = readonly Tab[];
16+
17+
type TabKey<T extends readOnlyTab> = T[number]['key'];
18+
19+
interface NormalCatalogueProps
1120
extends Pick<IBlockHeaderProps, 'tooltip' | 'addonAfter' | 'addonBefore' | 'title'>,
1221
ICatalogueTree {
1322
showSearch?: boolean;
@@ -19,23 +28,43 @@ export interface ICatalogue
1928
onSearch?: (value: string) => void;
2029
onSave?: (data: ITreeNode, value: string) => Promise<string | void>;
2130
}
31+
interface TabsCatalogueProps<T extends readOnlyTab> extends NormalCatalogueProps {
32+
tabList?: T;
33+
activeTabKey?: TabKey<T>;
34+
defaultTabKey?: TabKey<T>;
35+
onTabChange?: (key: TabKey<T>) => void;
36+
}
37+
38+
export type CatalogueProps<T extends readOnlyTab = any> =
39+
| TabsCatalogueProps<T>
40+
| NormalCatalogueProps;
41+
42+
function isTabMode<T extends readOnlyTab>(
43+
props: CatalogueProps<T>
44+
): props is TabsCatalogueProps<T> {
45+
return 'tabList' in props;
46+
}
47+
48+
const Catalogue = <T extends readOnlyTab>(props: CatalogueProps<T>) => {
49+
const {
50+
title,
51+
addonBefore = <CatalogIcon style={{ fontSize: 20 }} />,
52+
tooltip = false,
53+
showSearch = false,
54+
placeholder = '搜索目录名称',
55+
addonAfter,
56+
edit = true,
57+
treeData,
58+
draggable,
59+
overlay,
60+
onChange,
61+
onSearch,
62+
onSave,
63+
...rest
64+
} = props;
65+
66+
const [tabSearch, setTabSearch] = useState(false);
2267

23-
const Catalogue = ({
24-
title,
25-
addonBefore = <CatalogIcon style={{ fontSize: 20 }} />,
26-
tooltip = false,
27-
showSearch = false,
28-
placeholder = '搜索目录名称',
29-
addonAfter,
30-
edit = true,
31-
treeData,
32-
draggable,
33-
overlay,
34-
onChange,
35-
onSearch,
36-
onSave,
37-
...rest
38-
}: ICatalogue) => {
3968
const [form] = Form.useForm();
4069

4170
const loopTree = (data: ITreeNode[]): ITreeNode[] => {
@@ -64,25 +93,48 @@ const Catalogue = ({
6493
const renderHeader = () => {
6594
if (!title) return null;
6695
return (
67-
<BlockHeader
68-
title={title}
69-
tooltip={tooltip}
70-
background={false}
71-
addonBefore={addonBefore}
72-
addonAfter={addonAfter}
73-
spaceBottom={12}
74-
/>
96+
<div className="dt-catalogue__header">
97+
<BlockHeader
98+
title={title}
99+
tooltip={tooltip}
100+
background={false}
101+
addonBefore={addonBefore}
102+
addonAfter={addonAfter}
103+
spaceBottom={12}
104+
/>
105+
</div>
75106
);
76107
};
77108

78109
const renderSearch = () => {
79-
if (!showSearch) return null;
110+
if (!showSearch || (isTabMode(props) && !tabSearch)) return null;
80111
return (
81-
<Input.Search
82-
placeholder={placeholder}
83-
style={{ marginBottom: 12 }}
84-
onSearch={onSearch}
85-
/>
112+
<div className="dt-catalogue__search">
113+
<Input.Search placeholder={placeholder} onSearch={onSearch} />
114+
{isTabMode(props) && (
115+
<CloseIcon className="close" style={{}} onClick={() => setTabSearch(false)} />
116+
)}
117+
</div>
118+
);
119+
};
120+
121+
const renderTab = () => {
122+
if (!isTabMode(props) || tabSearch) return null;
123+
const { activeTabKey, tabList, onTabChange } = props;
124+
return (
125+
<Tabs
126+
className="dt-catalogue__tabs"
127+
size="small"
128+
tabBarExtraContent={
129+
<SearchIcon className="search" onClick={() => setTabSearch(true)} />
130+
}
131+
activeKey={activeTabKey}
132+
onChange={onTabChange}
133+
>
134+
{tabList?.map((tab: { key: string; title: React.ReactNode }) => (
135+
<Tabs.TabPane tab={tab.title} key={tab.key} />
136+
))}
137+
</Tabs>
86138
);
87139
};
88140

@@ -182,10 +234,9 @@ const Catalogue = ({
182234

183235
return (
184236
<div className="dt-catalogue">
185-
<div className="dt-catalogue__header">
186-
{renderHeader()}
187-
{renderSearch()}
188-
</div>
237+
{renderHeader()}
238+
{renderSearch()}
239+
{renderTab()}
189240
<CatalogueTree
190241
treeData={loopTree(treeData)}
191242
draggable={draggable ? { icon: false } : false}

src/catalogue/components/icon.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,51 @@ const PlusCircleIcon = function ({ className, ...rest }: IIcon) {
274274
);
275275
};
276276

277+
const SearchIcon = function ({ className, ...rest }: IIcon) {
278+
return (
279+
<span className={classNames('dt-catalogue__icon', className)} {...rest}>
280+
<svg
281+
xmlns="http://www.w3.org/2000/svg"
282+
width="1em"
283+
height="1em"
284+
viewBox="0 0 64 64"
285+
fill="currentColor"
286+
>
287+
<path
288+
fillRule="evenodd"
289+
clipRule="evenodd"
290+
d="M30.1927 5C43.6716 5 54.3855 15.9572 54.3855 29.7421C54.3855 43.527 43.6716 54.4842 30.1927 54.4842C16.7139 54.4842 6 43.527 6 29.7421C6 15.9572 16.7139 5 30.1927 5ZM30.1927 9.52055C19.2345 9.52055 10.5217 18.4311 10.5217 29.7421C10.5217 41.053 19.2345 49.9636 30.1927 49.9636C41.151 49.9636 49.8638 41.053 49.8638 29.7421C49.8638 18.4311 41.151 9.52055 30.1927 9.52055ZM51.165 49.982C50.2767 49.2338 48.9479 49.2778 48.1115 50.1141C47.2285 50.9968 47.2285 52.4279 48.1115 53.3106L54.1405 59.338L54.2843 59.47C55.1725 60.2182 56.5014 60.1742 57.3378 59.338C58.2207 58.4553 58.2207 57.0242 57.3378 56.1415L51.3088 50.1141L51.165 49.982Z"
291+
fill="currentColor"
292+
/>
293+
</svg>
294+
</span>
295+
);
296+
};
297+
298+
const CloseIcon = function ({ className, ...rest }: IIcon) {
299+
return (
300+
<span className={classNames('dt-catalogue__icon', className)} {...rest}>
301+
<svg
302+
xmlns="http://www.w3.org/2000/svg"
303+
width="1em"
304+
height="1em"
305+
viewBox="0 0 64 64"
306+
fill="currentColor"
307+
>
308+
<path
309+
fillRule="evenodd"
310+
clipRule="evenodd"
311+
d="M51.0918 12.9081C51.8729 13.6892 51.8729 14.9555 51.0918 15.7365L34.8284 32L51.0918 48.2635C51.8729 49.0445 51.8729 50.3108 51.0918 51.0919C50.3108 51.8729 49.0444 51.8729 48.2634 51.0919L31.9999 34.8284L15.7365 51.0919C14.9554 51.8729 13.6891 51.8729 12.9081 51.0919C12.127 50.3108 12.127 49.0445 12.9081 48.2635L29.1715 32L12.9081 15.7365C12.127 14.9555 12.127 13.6892 12.9081 12.9081C13.6891 12.1271 14.9554 12.1271 15.7365 12.9081L31.9999 29.1716L48.2634 12.9081C49.0444 12.1271 50.3108 12.1271 51.0918 12.9081Z"
312+
fill="currentColor"
313+
/>
314+
</svg>
315+
</span>
316+
);
317+
};
318+
277319
export {
278320
CatalogIcon,
321+
CloseIcon,
279322
DeleteIcon,
280323
DownTriangleIcon,
281324
DragIcon,
@@ -287,4 +330,5 @@ export {
287330
MenuUnFoldIcon,
288331
PlusCircleIcon,
289332
PlusSquareIcon,
333+
SearchIcon,
290334
};

src/catalogue/demos/tabs.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { Catalogue, EllipsisText } from 'dt-react-component';
3+
4+
import { useTreeData } from '../useTreeData';
5+
6+
enum TreeType {
7+
Api = 'api',
8+
Index = 'index',
9+
}
10+
11+
const DEFAULT_DATA = (key: TreeType) => [
12+
{
13+
title: `0-0-${key}`,
14+
key: '0-0',
15+
children: [
16+
{
17+
title: (
18+
<EllipsisText
19+
value="长长长长长长长长长长长长长长长长长长长长长长长长Title"
20+
maxWidth="100%"
21+
/>
22+
),
23+
key: '0-0-0',
24+
children: [
25+
{ title: '0-0-0-0', key: '0-0-0-0' },
26+
{ title: '0-0-0-1', key: '0-0-0-1' },
27+
{ title: '0-0-0-2', key: '0-0-0-2' },
28+
],
29+
},
30+
{
31+
title: '0-0-1',
32+
key: '0-0-1',
33+
children: [
34+
{ title: '0-0-1-0', key: '0-0-1-0' },
35+
{ title: '0-0-1-1', key: '0-0-1-1' },
36+
{ title: '0-0-1-2', key: '0-0-1-2' },
37+
],
38+
},
39+
{
40+
title: '0-0-2',
41+
key: '0-0-2',
42+
},
43+
],
44+
},
45+
{
46+
title: `0-1-${key}`,
47+
key: '0-1',
48+
children: [
49+
{ title: '0-1-0-0', key: '0-1-0-0' },
50+
{ title: '0-1-0-1', key: '0-1-0-1' },
51+
{ title: '0-1-0-2', key: '0-1-0-2' },
52+
],
53+
},
54+
{
55+
title: `0-2-${key}`,
56+
key: '0-2',
57+
},
58+
];
59+
60+
export default () => {
61+
const treeData = useTreeData();
62+
const [activeKey, setActiveKey] = useState(TreeType.Index);
63+
64+
useEffect(() => {
65+
treeData.initData(DEFAULT_DATA(activeKey));
66+
}, [activeKey]);
67+
68+
return (
69+
<div style={{ height: 300, width: 320 }}>
70+
<Catalogue
71+
tooltip="嘿嘿,这是tooltip"
72+
title="标签目录"
73+
showSearch
74+
treeData={treeData.data}
75+
edit={false}
76+
activeTabKey={activeKey}
77+
tabList={
78+
[
79+
{ key: TreeType.Api, title: 'API' },
80+
{ key: TreeType.Index, title: '指标' },
81+
] as const
82+
}
83+
onTabChange={(key) => setActiveKey(key)}
84+
expandedKeys={treeData.expandedKeys}
85+
onExpand={treeData.setExpandedKeys}
86+
/>
87+
</div>
88+
);
89+
};

src/catalogue/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ demo:
2121
<code src="./demos/operator.tsx">可操作的目录树</code>
2222
<code src="./demos/drag.tsx">可拖拽的目录树</code>
2323
<code src="./demos/config.tsx">配置操作项的目录树</code>
24+
<code src="./demos/tabs.tsx">配置操作项的目录树</code>
2425

2526
## API
2627

src/catalogue/index.scss

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
&__header {
99
padding: 12px 12px 0;
1010
}
11+
&__search {
12+
display: flex;
13+
padding: 0 12px 6px;
14+
align-items: center;
15+
.close {
16+
font-size: 16px;
17+
color: #B1B4C5;
18+
cursor: pointer;
19+
margin-left: 46px;
20+
}
21+
}
1122
.dt-catalogue__tree {
1223
padding: 2px 0 24px;
1324
}
@@ -130,4 +141,23 @@
130141
}
131142
}
132143
}
144+
&__tabs {
145+
margin-bottom: 6px;
146+
height: fit-content;
147+
&.ant-tabs-top > .ant-tabs-nav {
148+
margin: 0;
149+
padding: 0 16px;
150+
}
151+
.ant-tabs-extra-content {
152+
.search {
153+
font-size: 16px;
154+
color: #B1B4C5;
155+
cursor: pointer;
156+
}
157+
}
158+
}
159+
&__icon {
160+
display: flex;
161+
align-items: center;
162+
}
133163
}

src/catalogue/useTreeData.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useState } from 'react';
22
import { DataNode } from 'antd/lib/tree';
33
import { cloneDeep } from 'lodash';
44

5-
import { ICatalogue } from './components/catalogue';
5+
import { CatalogueProps } from './components/catalogue';
66

77
export interface ITreeNode extends Omit<DataNode, 'children' | 'title'> {
88
inputMode?: InputMode;
@@ -25,15 +25,15 @@ export enum InputMode {
2525
export const useTreeData = (): {
2626
data: ITreeNode[];
2727
loading: boolean;
28-
expandedKeys: ICatalogue['expandedKeys'];
28+
expandedKeys: CatalogueProps['expandedKeys'];
2929
initData: (treeData: ITreeNode[]) => void;
3030
onChange: (node?: ITreeNode, inputMode?: InputMode) => void;
3131
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
32-
setExpandedKeys: React.Dispatch<React.SetStateAction<ICatalogue['expandedKeys']>>;
32+
setExpandedKeys: React.Dispatch<React.SetStateAction<CatalogueProps['expandedKeys']>>;
3333
} => {
3434
const [data, setData] = useState<ITreeNode[]>([]);
3535
const [loading, setLoading] = useState(false);
36-
const [expandedKeys, setExpandedKeys] = useState<ICatalogue['expandedKeys']>([]);
36+
const [expandedKeys, setExpandedKeys] = useState<CatalogueProps['expandedKeys']>([]);
3737

3838
const initData = (treeData: ITreeNode[]) => {
3939
setData(treeData);

0 commit comments

Comments
 (0)