Skip to content

Commit d926afa

Browse files
authored
feat(catalogue): add catalogue (#519)
* feat(catalogue): add catalogue * fix: add useTreeData return type * feat(catalogue): change catalogue and add examples * feat(catalogue): add custom overlay, form.error msg and drag style * feat(catalogue): support title and icon, change CatalogueTree * feat(catalogue): support tabs in cataligue * feat(catalogue): change catalogue and add some utils to CRUD treedata * feat(catalogue): remove some unuse icon * fix(catalogue): remove some function to utils * fix(catalogue): change all demo import path
1 parent 41b7e0e commit d926afa

File tree

34 files changed

+1946
-2131
lines changed

34 files changed

+1946
-2131
lines changed
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import React, { useState } from 'react';
2+
import { Dropdown, DropdownProps, Form, Input, Tabs } from 'antd';
3+
import { BlockHeader, EllipsisText } from 'dt-react-component';
4+
import { IBlockHeaderProps } from 'dt-react-component/blockHeader';
5+
6+
import { ITreeNode } from '../useTreeData';
7+
import { CatalogIcon, CloseIcon, DragIcon, EllipsisIcon, SearchIcon } from './icon';
8+
import CatalogueTree, { ICatalogueTree } from './tree';
9+
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<U extends Record<string, any> = {}>
20+
extends Partial<Pick<IBlockHeaderProps, 'tooltip' | 'addonAfter' | 'addonBefore' | 'title'>>,
21+
ICatalogueTree<U> {
22+
showSearch?: boolean;
23+
edit?: boolean;
24+
placeholder?: string;
25+
loading?: boolean;
26+
onCancelSave?: (item: ITreeNode<U>) => void;
27+
overlay?: (item: ITreeNode<U>) => DropdownProps['overlay'];
28+
onSearch?: (value: string) => void;
29+
onSave?: (data: ITreeNode<U>, value: string) => Promise<string | void>;
30+
}
31+
interface TabsCatalogueProps<U extends Record<string, any>, T extends readOnlyTab>
32+
extends NormalCatalogueProps<U> {
33+
tabList?: T;
34+
activeTabKey?: TabKey<T>;
35+
defaultTabKey?: TabKey<T>;
36+
onTabChange?: (key: TabKey<T>) => void;
37+
}
38+
39+
export type CatalogueProps<U extends Record<string, any> = {}, T extends readOnlyTab = any> =
40+
| TabsCatalogueProps<U, T>
41+
| NormalCatalogueProps<U>;
42+
43+
function isTabMode<U extends Record<string, any> = {}, T extends readOnlyTab = any>(
44+
props: CatalogueProps<U, T>
45+
): props is TabsCatalogueProps<U, T> {
46+
return 'tabList' in props;
47+
}
48+
49+
const Catalogue = <U extends Record<string, any> = {}, T extends readOnlyTab = any>(
50+
props: CatalogueProps<U, T>
51+
) => {
52+
const {
53+
title,
54+
addonBefore = <CatalogIcon style={{ fontSize: 20 }} />,
55+
tooltip = false,
56+
showSearch = false,
57+
placeholder = '搜索目录名称',
58+
addonAfter,
59+
edit = true,
60+
treeData,
61+
draggable,
62+
titleRender,
63+
overlay,
64+
onSearch,
65+
onSave,
66+
onCancelSave,
67+
...rest
68+
} = props;
69+
70+
const [tabSearch, setTabSearch] = useState(false);
71+
72+
const [form] = Form.useForm();
73+
74+
const defaultTitleRender = (item: ITreeNode<U>) => {
75+
if (item.edit) {
76+
return (
77+
<Form form={form} preserve={false} className="tree__title--input">
78+
<Form.Item name="catalog_input" initialValue={item?.title as string}>
79+
<Input
80+
size="small"
81+
placeholder={`请输入${title}名称`}
82+
maxLength={100}
83+
autoFocus
84+
onFocus={() => form.setFields([{ name: 'catalog_input', errors: [] }])}
85+
onClick={(e) => e.stopPropagation()}
86+
onBlur={({ target }) => handleInputSubmit(item, target.value)}
87+
onPressEnter={({ target }) =>
88+
handleInputSubmit(item, (target as any).value)
89+
}
90+
/>
91+
</Form.Item>
92+
</Form>
93+
);
94+
}
95+
return (
96+
<div className="tree__title">
97+
<div className="tree__title--text">
98+
<EllipsisText value={item.title} watchParentSizeChange maxWidth="100%" />
99+
</div>
100+
{edit && renderNodeHover(item)}
101+
</div>
102+
);
103+
};
104+
105+
const renderHeader = () => {
106+
if (!title) return null;
107+
return (
108+
<div className="dt-catalogue__header">
109+
<BlockHeader
110+
title={title}
111+
tooltip={tooltip}
112+
background={false}
113+
addonBefore={addonBefore}
114+
addonAfter={addonAfter}
115+
spaceBottom={12}
116+
/>
117+
</div>
118+
);
119+
};
120+
121+
const renderSearch = () => {
122+
if (!showSearch || (isTabMode(props) && !tabSearch)) return null;
123+
return (
124+
<div className="dt-catalogue__search">
125+
<Input.Search placeholder={placeholder} onSearch={onSearch} />
126+
{isTabMode(props) && (
127+
<CloseIcon className="close" style={{}} onClick={() => setTabSearch(false)} />
128+
)}
129+
</div>
130+
);
131+
};
132+
133+
const renderTab = () => {
134+
if (!isTabMode(props) || tabSearch) return null;
135+
const { activeTabKey, tabList, onTabChange } = props;
136+
return (
137+
<Tabs
138+
className="dt-catalogue__tabs"
139+
size="small"
140+
tabBarExtraContent={
141+
<SearchIcon className="search" onClick={() => setTabSearch(true)} />
142+
}
143+
activeKey={activeTabKey}
144+
onChange={onTabChange}
145+
>
146+
{tabList?.map((tab: { key: string; title: React.ReactNode }) => (
147+
<Tabs.TabPane tab={tab.title} key={tab.key} />
148+
))}
149+
</Tabs>
150+
);
151+
};
152+
153+
const handleInputSubmit = (item: ITreeNode<U>, value: string) => {
154+
if (!value) {
155+
return onCancelSave?.(item);
156+
}
157+
onSave?.(item, value).then((msg) => {
158+
form.setFields([{ name: 'catalog_input', errors: msg ? [msg] : [] }]);
159+
});
160+
};
161+
162+
const renderNodeHover = (item: ITreeNode<U>) => {
163+
return (
164+
<div
165+
className="tree__title--operation"
166+
onClick={(e) => {
167+
e.stopPropagation();
168+
}}
169+
>
170+
{overlay && (
171+
<Dropdown
172+
overlay={overlay(item)}
173+
placement="bottomRight"
174+
arrow
175+
destroyPopupOnHide
176+
getPopupContainer={(triggerNode) =>
177+
triggerNode.parentElement as HTMLElement
178+
}
179+
>
180+
<EllipsisIcon onClick={(e) => e.stopPropagation()} />
181+
</Dropdown>
182+
)}
183+
{draggable && <DragIcon />}
184+
</div>
185+
);
186+
};
187+
188+
return (
189+
<div className="dt-catalogue">
190+
{renderHeader()}
191+
{renderSearch()}
192+
{renderTab()}
193+
<CatalogueTree
194+
treeData={treeData}
195+
draggable={draggable ? { icon: false } : false}
196+
titleRender={titleRender || defaultTitleRender}
197+
{...rest}
198+
/>
199+
</div>
200+
);
201+
};
202+
203+
export default Catalogue;

0 commit comments

Comments
 (0)