@rovula/ui 0.0.48 → 0.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/bundle.css +9 -0
- package/dist/cjs/bundle.js +3 -3
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Tree/Tree.d.ts +4 -45
- package/dist/cjs/types/components/Tree/Tree.stories.d.ts +8 -1
- package/dist/cjs/types/components/Tree/TreeItem.d.ts +4 -0
- package/dist/cjs/types/components/Tree/index.d.ts +4 -0
- package/dist/cjs/types/components/Tree/type.d.ts +76 -0
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/components/Tree/Tree.js +15 -49
- package/dist/components/Tree/Tree.stories.js +117 -8
- package/dist/components/Tree/TreeItem.js +81 -0
- package/dist/components/Tree/index.js +4 -0
- package/dist/components/Tree/type.js +1 -0
- package/dist/esm/bundle.css +9 -0
- package/dist/esm/bundle.js +1 -1
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Tree/Tree.d.ts +4 -45
- package/dist/esm/types/components/Tree/Tree.stories.d.ts +8 -1
- package/dist/esm/types/components/Tree/TreeItem.d.ts +4 -0
- package/dist/esm/types/components/Tree/index.d.ts +4 -0
- package/dist/esm/types/components/Tree/type.d.ts +76 -0
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/index.d.ts +82 -2
- package/dist/index.js +1 -0
- package/dist/src/theme/global.css +12 -0
- package/package.json +1 -1
- package/src/components/Tree/Tree.stories.tsx +230 -8
- package/src/components/Tree/Tree.tsx +44 -183
- package/src/components/Tree/TreeItem.tsx +231 -0
- package/src/components/Tree/index.ts +5 -0
- package/src/components/Tree/type.ts +90 -0
- package/src/index.ts +1 -0
|
@@ -1,170 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
ReactNode,
|
|
5
|
-
useCallback,
|
|
6
|
-
useMemo,
|
|
7
|
-
useState,
|
|
8
|
-
useEffect,
|
|
9
|
-
} from "react";
|
|
1
|
+
import React, { FC, useCallback, useEffect, useState } from "react";
|
|
2
|
+
import TreeItem from "./TreeItem";
|
|
3
|
+
import { TreeData, TreeProps } from "./type";
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
title: string;
|
|
14
|
-
icon?: ReactNode;
|
|
15
|
-
children?: TreeData[];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export interface TreeItemProps extends TreeData {
|
|
19
|
-
isFirstLevel?: boolean;
|
|
20
|
-
isLastItem: boolean;
|
|
21
|
-
checkIsExpanded: (id: string) => boolean;
|
|
22
|
-
checkIsChecked: (id: string) => boolean;
|
|
23
|
-
onExpandChange?: (id: string, expanded: boolean) => void;
|
|
24
|
-
onCheckedChange?: (id: string, checked: boolean) => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const TreeItem: FC<TreeItemProps> = ({
|
|
28
|
-
id,
|
|
29
|
-
title,
|
|
30
|
-
children,
|
|
31
|
-
isFirstLevel = false,
|
|
32
|
-
isLastItem,
|
|
33
|
-
checkIsExpanded,
|
|
34
|
-
checkIsChecked,
|
|
35
|
-
onExpandChange,
|
|
36
|
-
onCheckedChange,
|
|
37
|
-
}) => {
|
|
38
|
-
const isChecked = useMemo(() => checkIsChecked(id), [checkIsChecked, id]);
|
|
39
|
-
const isExpanded = useMemo(() => checkIsExpanded(id), [checkIsExpanded, id]);
|
|
40
|
-
const hasChildren = useMemo(() => !!children?.length, [children]);
|
|
41
|
-
|
|
42
|
-
const handleExpandToggle = useCallback(() => {
|
|
43
|
-
onExpandChange?.(id, !isExpanded);
|
|
44
|
-
}, [id, isExpanded, onExpandChange]);
|
|
45
|
-
|
|
46
|
-
// TODO move to props
|
|
47
|
-
const lineSize = 2;
|
|
48
|
-
const horizontalLineWidth = 4;
|
|
49
|
-
const expandButtonSize = 30;
|
|
50
|
-
const spacing = 2;
|
|
51
|
-
|
|
52
|
-
const styles = {
|
|
53
|
-
branch: {
|
|
54
|
-
height: isLastItem
|
|
55
|
-
? `calc(50% + ${lineSize}px)`
|
|
56
|
-
: `calc(100% + ${lineSize}px)`,
|
|
57
|
-
width: lineSize,
|
|
58
|
-
marginTop: -lineSize,
|
|
59
|
-
borderBottomLeftRadius: lineSize / 2,
|
|
60
|
-
},
|
|
61
|
-
horizontalLine: {
|
|
62
|
-
height: lineSize,
|
|
63
|
-
width:
|
|
64
|
-
lineSize +
|
|
65
|
-
horizontalLineWidth +
|
|
66
|
-
(hasChildren ? 0 : expandButtonSize + spacing),
|
|
67
|
-
marginLeft: -lineSize + 0.1,
|
|
68
|
-
borderBottomLeftRadius: lineSize / 2,
|
|
69
|
-
},
|
|
70
|
-
expandButton: {
|
|
71
|
-
width: expandButtonSize,
|
|
72
|
-
height: expandButtonSize,
|
|
73
|
-
},
|
|
74
|
-
childPadding: {
|
|
75
|
-
paddingLeft: isFirstLevel
|
|
76
|
-
? expandButtonSize / 2 - lineSize / 2
|
|
77
|
-
: expandButtonSize / 2 + horizontalLineWidth - lineSize / 2,
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<div className="flex flex-row w-full">
|
|
83
|
-
<div
|
|
84
|
-
className={cn("bg-grey-150", { "h-1/2": isLastItem })}
|
|
85
|
-
style={styles.branch}
|
|
86
|
-
/>
|
|
87
|
-
<div className="flex flex-col w-full">
|
|
88
|
-
<div className="flex items-center py-2 min-h-10">
|
|
89
|
-
{!isFirstLevel && (
|
|
90
|
-
<div className="bg-grey-150" style={styles.horizontalLine} />
|
|
91
|
-
)}
|
|
92
|
-
|
|
93
|
-
{isFirstLevel && !hasChildren && (
|
|
94
|
-
<div className="flex mr-[2px]" style={styles.expandButton} />
|
|
95
|
-
)}
|
|
96
|
-
|
|
97
|
-
{hasChildren && (
|
|
98
|
-
<div className="flex mr-[2px]" style={styles.expandButton}>
|
|
99
|
-
<ActionButton
|
|
100
|
-
variant="icon"
|
|
101
|
-
size="sm"
|
|
102
|
-
onClick={handleExpandToggle}
|
|
103
|
-
>
|
|
104
|
-
<Icon name={isExpanded ? "chevron-down" : "chevron-right"} />
|
|
105
|
-
</ActionButton>
|
|
106
|
-
</div>
|
|
107
|
-
)}
|
|
108
|
-
<Checkbox
|
|
109
|
-
id={id}
|
|
110
|
-
className="size-[16pt]"
|
|
111
|
-
checked={isChecked}
|
|
112
|
-
onCheckedChange={(newChecked) =>
|
|
113
|
-
onCheckedChange?.(id, newChecked as boolean)
|
|
114
|
-
}
|
|
115
|
-
/>
|
|
116
|
-
<div className="ml-2 gap-1 flex items-center text-foreground">
|
|
117
|
-
<Icon
|
|
118
|
-
name={isExpanded ? "folder-open" : "folder"}
|
|
119
|
-
className="fill-warning"
|
|
120
|
-
/>
|
|
121
|
-
<label
|
|
122
|
-
htmlFor={id}
|
|
123
|
-
className="flex-1 cursor-pointer text-subtitle5 text-ellipsis"
|
|
124
|
-
>
|
|
125
|
-
{title}
|
|
126
|
-
</label>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
{isExpanded && hasChildren && (
|
|
130
|
-
<div className="flex flex-col" style={styles.childPadding}>
|
|
131
|
-
{children?.map((child, idx) => (
|
|
132
|
-
<TreeItem
|
|
133
|
-
key={child.id}
|
|
134
|
-
{...child}
|
|
135
|
-
isLastItem={idx === children.length - 1}
|
|
136
|
-
checkIsExpanded={checkIsExpanded}
|
|
137
|
-
checkIsChecked={checkIsChecked}
|
|
138
|
-
onExpandChange={onExpandChange}
|
|
139
|
-
onCheckedChange={onCheckedChange}
|
|
140
|
-
/>
|
|
141
|
-
))}
|
|
142
|
-
</div>
|
|
143
|
-
)}
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
);
|
|
147
|
-
};
|
|
148
|
-
export type TreeProps = {
|
|
149
|
-
data: TreeData[];
|
|
150
|
-
defaultExpandedId?: string[];
|
|
151
|
-
defaultCheckedId?: string[];
|
|
152
|
-
checkedId?: string[];
|
|
153
|
-
onCheckedChange?: (checkedId: string[]) => void;
|
|
154
|
-
defaultExpandAll?: boolean;
|
|
155
|
-
defaultCheckAll?: boolean;
|
|
156
|
-
hierarchicalCheck?: boolean;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
export const Tree: FC<TreeProps> = ({
|
|
5
|
+
const Tree: FC<TreeProps> = ({
|
|
6
|
+
classes,
|
|
160
7
|
data,
|
|
161
8
|
defaultExpandedId = [],
|
|
162
9
|
defaultCheckedId = [],
|
|
163
10
|
checkedId,
|
|
11
|
+
loadingId,
|
|
12
|
+
renderIcon,
|
|
13
|
+
renderRightSection,
|
|
14
|
+
renderElement,
|
|
15
|
+
renderTitle,
|
|
16
|
+
onExpandChange,
|
|
164
17
|
onCheckedChange,
|
|
165
18
|
defaultExpandAll = false,
|
|
166
19
|
defaultCheckAll = false,
|
|
167
20
|
hierarchicalCheck = false,
|
|
21
|
+
showIcon = true,
|
|
22
|
+
disabled,
|
|
23
|
+
enableSeparatorLine = true,
|
|
168
24
|
}) => {
|
|
169
25
|
const [checkedState, setCheckedState] = useState<Record<string, boolean>>({});
|
|
170
26
|
const [expandedState, setExpandedState] = useState<Record<string, boolean>>(
|
|
@@ -190,7 +46,7 @@ export const Tree: FC<TreeProps> = ({
|
|
|
190
46
|
allExpanded[node.id] = true;
|
|
191
47
|
});
|
|
192
48
|
setExpandedState(allExpanded);
|
|
193
|
-
} else {
|
|
49
|
+
} else if (defaultExpandedId?.length) {
|
|
194
50
|
const initialExpandedState = defaultExpandedId.reduce((acc, id) => {
|
|
195
51
|
acc[id] = true;
|
|
196
52
|
return acc;
|
|
@@ -206,7 +62,7 @@ export const Tree: FC<TreeProps> = ({
|
|
|
206
62
|
allChecked[node.id] = true;
|
|
207
63
|
});
|
|
208
64
|
setCheckedState(allChecked);
|
|
209
|
-
} else if (!checkedId) {
|
|
65
|
+
} else if (!checkedId && defaultCheckedId?.length) {
|
|
210
66
|
const initialCheckedState = defaultCheckedId.reduce((acc, id) => {
|
|
211
67
|
acc[id] = true;
|
|
212
68
|
return acc;
|
|
@@ -215,9 +71,13 @@ export const Tree: FC<TreeProps> = ({
|
|
|
215
71
|
}
|
|
216
72
|
}, [data, defaultCheckedId, checkedId, defaultCheckAll]);
|
|
217
73
|
|
|
218
|
-
const handleExpandChange = useCallback(
|
|
219
|
-
|
|
220
|
-
|
|
74
|
+
const handleExpandChange = useCallback(
|
|
75
|
+
(id: string, expanded: boolean) => {
|
|
76
|
+
onExpandChange?.(id, expanded);
|
|
77
|
+
setExpandedState((prev) => ({ ...prev, [id]: expanded }));
|
|
78
|
+
},
|
|
79
|
+
[onExpandChange]
|
|
80
|
+
);
|
|
221
81
|
|
|
222
82
|
const handleCheckedChange = useCallback(
|
|
223
83
|
(id: string, checked: boolean) => {
|
|
@@ -293,39 +153,40 @@ export const Tree: FC<TreeProps> = ({
|
|
|
293
153
|
[checkedId, checkedState]
|
|
294
154
|
);
|
|
295
155
|
|
|
156
|
+
const checkIsLoading = useCallback(
|
|
157
|
+
(id: string) => {
|
|
158
|
+
if (loadingId) {
|
|
159
|
+
return loadingId.includes(id);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
[loadingId]
|
|
163
|
+
);
|
|
164
|
+
|
|
296
165
|
return (
|
|
297
166
|
<div className="w-full">
|
|
298
167
|
{data.map((item, idx) => (
|
|
299
168
|
<TreeItem
|
|
300
169
|
key={item.id}
|
|
301
|
-
{
|
|
170
|
+
classes={classes}
|
|
302
171
|
isFirstLevel
|
|
303
172
|
isLastItem={idx === data.length - 1}
|
|
304
173
|
checkIsExpanded={checkIsExpanded}
|
|
305
174
|
checkIsChecked={checkIsChecked}
|
|
306
175
|
onExpandChange={handleExpandChange}
|
|
307
176
|
onCheckedChange={handleCheckedChange}
|
|
177
|
+
checkIsLoading={checkIsLoading}
|
|
178
|
+
renderIcon={renderIcon}
|
|
179
|
+
renderElement={renderElement}
|
|
180
|
+
renderTitle={renderTitle}
|
|
181
|
+
renderRightSection={renderRightSection}
|
|
182
|
+
enableSeparatorLine={enableSeparatorLine}
|
|
183
|
+
disabled={disabled}
|
|
184
|
+
showIcon={showIcon}
|
|
185
|
+
{...item}
|
|
308
186
|
/>
|
|
309
187
|
))}
|
|
310
188
|
</div>
|
|
311
189
|
);
|
|
312
190
|
};
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
* -----
|
|
316
|
-
* - Custom style
|
|
317
|
-
* - Custom icon, elm and render props -> callback with selected*expanded
|
|
318
|
-
* - OnClick item
|
|
319
|
-
* - OnClick expandButton
|
|
320
|
-
* - disabled props
|
|
321
|
-
* - right section icon
|
|
322
|
-
* - props for show icon, line
|
|
323
|
-
* - props for render item
|
|
324
|
-
* - OnLoad mode
|
|
325
|
-
* -----
|
|
326
|
-
* - props onLoad item
|
|
327
|
-
* - props for hasChildren * for check to trigger on load
|
|
328
|
-
* - animate expand
|
|
329
|
-
* - check duplicate reversive on updateChildren
|
|
330
|
-
* - write storybook
|
|
331
|
-
*/
|
|
191
|
+
|
|
192
|
+
export default Tree;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { ActionButton, Checkbox, Loading } from "@/index";
|
|
2
|
+
import { cn } from "@/utils/cn";
|
|
3
|
+
import React, { FC, ReactNode, useCallback, useEffect, useMemo } from "react";
|
|
4
|
+
import Icon from "../Icon/Icon";
|
|
5
|
+
import { TreeItemProps } from "./type";
|
|
6
|
+
|
|
7
|
+
const TreeItem: FC<TreeItemProps> = ({
|
|
8
|
+
id,
|
|
9
|
+
title,
|
|
10
|
+
classes,
|
|
11
|
+
children,
|
|
12
|
+
isFirstLevel = false,
|
|
13
|
+
disabled,
|
|
14
|
+
icon,
|
|
15
|
+
showIcon,
|
|
16
|
+
showExpandButton,
|
|
17
|
+
enableSeparatorLine = true,
|
|
18
|
+
isLastItem,
|
|
19
|
+
checkIsExpanded,
|
|
20
|
+
checkIsChecked,
|
|
21
|
+
checkIsLoading,
|
|
22
|
+
onExpandChange,
|
|
23
|
+
onCheckedChange,
|
|
24
|
+
onClickItem,
|
|
25
|
+
renderIcon,
|
|
26
|
+
renderElement,
|
|
27
|
+
renderTitle,
|
|
28
|
+
renderRightSection,
|
|
29
|
+
}) => {
|
|
30
|
+
const isLoading = useMemo(() => checkIsLoading?.(id), [checkIsLoading, id]);
|
|
31
|
+
const isChecked = useMemo(() => checkIsChecked(id), [checkIsChecked, id]);
|
|
32
|
+
const isExpanded = useMemo(() => checkIsExpanded(id), [checkIsExpanded, id]);
|
|
33
|
+
const hasChildren = useMemo(() => !!children?.length, [children]);
|
|
34
|
+
const shouldExpandButton = useMemo(
|
|
35
|
+
() => (showExpandButton !== undefined ? showExpandButton : hasChildren),
|
|
36
|
+
[hasChildren, showExpandButton]
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const handleExpandToggle = useCallback(() => {
|
|
40
|
+
onExpandChange?.(id, !isExpanded);
|
|
41
|
+
}, [id, isExpanded, onExpandChange]);
|
|
42
|
+
|
|
43
|
+
// TODO move to props
|
|
44
|
+
const lineSize = 2;
|
|
45
|
+
const horizontalLineWidth = 4;
|
|
46
|
+
const expandButtonSize = 30;
|
|
47
|
+
const spacing = 2;
|
|
48
|
+
|
|
49
|
+
const styles = {
|
|
50
|
+
branch: {
|
|
51
|
+
height: isLastItem
|
|
52
|
+
? `calc(50% + ${lineSize}px)`
|
|
53
|
+
: `calc(100% + ${lineSize}px)`,
|
|
54
|
+
width: lineSize,
|
|
55
|
+
marginTop: -lineSize,
|
|
56
|
+
borderBottomLeftRadius: lineSize / 2,
|
|
57
|
+
},
|
|
58
|
+
horizontalLine: {
|
|
59
|
+
height: lineSize,
|
|
60
|
+
width:
|
|
61
|
+
lineSize +
|
|
62
|
+
horizontalLineWidth +
|
|
63
|
+
(shouldExpandButton ? 0 : expandButtonSize + spacing),
|
|
64
|
+
marginLeft: -lineSize + 0.1,
|
|
65
|
+
borderBottomLeftRadius: lineSize / 2,
|
|
66
|
+
},
|
|
67
|
+
expandButton: {
|
|
68
|
+
width: expandButtonSize,
|
|
69
|
+
height: expandButtonSize,
|
|
70
|
+
},
|
|
71
|
+
childPadding: {
|
|
72
|
+
paddingLeft: isFirstLevel
|
|
73
|
+
? expandButtonSize / 2 - lineSize / 2
|
|
74
|
+
: expandButtonSize / 2 + horizontalLineWidth - lineSize / 2,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (isExpanded && !isLoading && !hasChildren) {
|
|
80
|
+
handleExpandToggle();
|
|
81
|
+
}
|
|
82
|
+
}, [isLoading, handleExpandToggle]);
|
|
83
|
+
|
|
84
|
+
const handleOnClickItem = useCallback(() => {
|
|
85
|
+
onClickItem?.(id);
|
|
86
|
+
}, [onClickItem, id]);
|
|
87
|
+
|
|
88
|
+
const defaultIcon = (
|
|
89
|
+
<Icon
|
|
90
|
+
name={isExpanded ? "folder-open" : "folder"}
|
|
91
|
+
className="fill-warning"
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const customIcon =
|
|
96
|
+
icon ??
|
|
97
|
+
renderIcon?.({
|
|
98
|
+
id,
|
|
99
|
+
expanded: isExpanded,
|
|
100
|
+
selected: isChecked,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const rightIcon = renderRightSection?.({
|
|
104
|
+
id,
|
|
105
|
+
expanded: isExpanded,
|
|
106
|
+
selected: isChecked,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const titleContent = renderTitle
|
|
110
|
+
? renderTitle({ id, title, expanded: isExpanded, selected: isChecked })
|
|
111
|
+
: title;
|
|
112
|
+
|
|
113
|
+
const elementWrapper = (content: ReactNode) =>
|
|
114
|
+
renderElement
|
|
115
|
+
? renderElement({
|
|
116
|
+
id,
|
|
117
|
+
expanded: isExpanded,
|
|
118
|
+
selected: isChecked,
|
|
119
|
+
children: content,
|
|
120
|
+
styles,
|
|
121
|
+
onClick: handleOnClickItem,
|
|
122
|
+
})
|
|
123
|
+
: content;
|
|
124
|
+
|
|
125
|
+
return elementWrapper(
|
|
126
|
+
<div className={cn("flex flex-row w-full", classes?.elementWrapper)}>
|
|
127
|
+
<div
|
|
128
|
+
className={cn("bg-grey-150", { "h-1/2": isLastItem }, classes?.branch)}
|
|
129
|
+
style={styles.branch}
|
|
130
|
+
/>
|
|
131
|
+
<div className={cn("flex flex-col w-full", classes?.itemWrapper)}>
|
|
132
|
+
<div
|
|
133
|
+
className={cn(
|
|
134
|
+
"flex items-center py-2 min-h-10",
|
|
135
|
+
classes?.itemContainer
|
|
136
|
+
)}
|
|
137
|
+
>
|
|
138
|
+
{!isFirstLevel && (
|
|
139
|
+
<div
|
|
140
|
+
className={cn("bg-grey-150", classes?.horizontalLine)}
|
|
141
|
+
style={styles.horizontalLine}
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
144
|
+
{isFirstLevel && !shouldExpandButton && (
|
|
145
|
+
<div
|
|
146
|
+
className={cn("flex mr-[2px]", classes?.expandButton)}
|
|
147
|
+
style={styles.expandButton}
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
150
|
+
{shouldExpandButton && (
|
|
151
|
+
<div
|
|
152
|
+
className={cn("flex mr-[2px]", classes?.expandButton)}
|
|
153
|
+
style={styles.expandButton}
|
|
154
|
+
onClick={!isLoading && handleExpandToggle}
|
|
155
|
+
>
|
|
156
|
+
<ActionButton variant="icon" size="sm">
|
|
157
|
+
{isLoading ? (
|
|
158
|
+
<Loading />
|
|
159
|
+
) : (
|
|
160
|
+
<Icon name={isExpanded ? "chevron-down" : "chevron-right"} />
|
|
161
|
+
)}
|
|
162
|
+
</ActionButton>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
<Checkbox
|
|
166
|
+
id={id}
|
|
167
|
+
className={cn("size-[16pt]", classes?.checkbox)}
|
|
168
|
+
checked={isChecked}
|
|
169
|
+
disabled={disabled}
|
|
170
|
+
onCheckedChange={(newChecked) =>
|
|
171
|
+
onCheckedChange?.(id, newChecked as boolean)
|
|
172
|
+
}
|
|
173
|
+
/>
|
|
174
|
+
<div
|
|
175
|
+
className={cn(
|
|
176
|
+
"ml-2 gap-1 flex flex-1 items-center text-foreground",
|
|
177
|
+
classes?.item
|
|
178
|
+
)}
|
|
179
|
+
onClick={handleOnClickItem}
|
|
180
|
+
>
|
|
181
|
+
{showIcon ? customIcon || defaultIcon : null}
|
|
182
|
+
<div
|
|
183
|
+
className={cn(
|
|
184
|
+
"flex flex-1 cursor-pointer text-subtitle5 text-ellipsis",
|
|
185
|
+
classes?.title
|
|
186
|
+
)}
|
|
187
|
+
>
|
|
188
|
+
{titleContent}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
{rightIcon}
|
|
192
|
+
</div>
|
|
193
|
+
{isExpanded && hasChildren && (
|
|
194
|
+
<div
|
|
195
|
+
className={cn("flex flex-col", classes?.childrenWrapper)}
|
|
196
|
+
style={styles.childPadding}
|
|
197
|
+
>
|
|
198
|
+
{children?.map((child, idx) => (
|
|
199
|
+
<TreeItem
|
|
200
|
+
key={child.id}
|
|
201
|
+
classes={classes}
|
|
202
|
+
isLastItem={idx === children.length - 1}
|
|
203
|
+
checkIsExpanded={checkIsExpanded}
|
|
204
|
+
checkIsChecked={checkIsChecked}
|
|
205
|
+
checkIsLoading={checkIsLoading}
|
|
206
|
+
onExpandChange={onExpandChange}
|
|
207
|
+
onCheckedChange={onCheckedChange}
|
|
208
|
+
renderIcon={renderIcon}
|
|
209
|
+
renderElement={renderElement}
|
|
210
|
+
renderTitle={renderTitle}
|
|
211
|
+
disabled={disabled}
|
|
212
|
+
showIcon={showIcon}
|
|
213
|
+
{...child}
|
|
214
|
+
/>
|
|
215
|
+
))}
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
{enableSeparatorLine && isFirstLevel && !isLastItem && (
|
|
219
|
+
<div
|
|
220
|
+
className={cn(
|
|
221
|
+
"bg-grey-150 w-full h-[2px] rounded",
|
|
222
|
+
classes?.separatorLine
|
|
223
|
+
)}
|
|
224
|
+
/>
|
|
225
|
+
)}
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export default TreeItem;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { CSSProperties, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type TreeData = {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onClickItem?: (id: string) => void;
|
|
9
|
+
children?: TreeData[];
|
|
10
|
+
renderIcon?: (params: {
|
|
11
|
+
id: string;
|
|
12
|
+
expanded: boolean;
|
|
13
|
+
selected: boolean;
|
|
14
|
+
}) => ReactNode;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export interface TreeItemProps extends TreeData {
|
|
18
|
+
isFirstLevel?: boolean;
|
|
19
|
+
isLastItem: boolean;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
showIcon?: boolean;
|
|
22
|
+
showExpandButton?: boolean;
|
|
23
|
+
enableSeparatorLine?: boolean;
|
|
24
|
+
checkIsExpanded: (id: string) => boolean;
|
|
25
|
+
checkIsChecked: (id: string) => boolean;
|
|
26
|
+
checkIsLoading?: (id: string) => void;
|
|
27
|
+
onExpandChange?: (id: string, expanded: boolean) => void;
|
|
28
|
+
onCheckedChange?: (id: string, checked: boolean) => void;
|
|
29
|
+
renderRightSection?: (params: {
|
|
30
|
+
id: string;
|
|
31
|
+
expanded: boolean;
|
|
32
|
+
selected: boolean;
|
|
33
|
+
}) => ReactNode;
|
|
34
|
+
renderElement?: (params: {
|
|
35
|
+
id: string;
|
|
36
|
+
expanded: boolean;
|
|
37
|
+
selected: boolean;
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
styles: {
|
|
40
|
+
branch: CSSProperties;
|
|
41
|
+
horizontalLine: CSSProperties;
|
|
42
|
+
expandButton: CSSProperties;
|
|
43
|
+
childPadding: CSSProperties;
|
|
44
|
+
};
|
|
45
|
+
onClick?: TreeItemProps["onClickItem"];
|
|
46
|
+
}) => ReactNode;
|
|
47
|
+
renderTitle?: (params: {
|
|
48
|
+
id: string;
|
|
49
|
+
title: string;
|
|
50
|
+
expanded: boolean;
|
|
51
|
+
selected: boolean;
|
|
52
|
+
}) => ReactNode;
|
|
53
|
+
classes?: Partial<{
|
|
54
|
+
elementWrapper: string;
|
|
55
|
+
branch: string;
|
|
56
|
+
itemWrapper: string;
|
|
57
|
+
itemContainer: string;
|
|
58
|
+
horizontalLine: string;
|
|
59
|
+
expandButton: string;
|
|
60
|
+
separatorLine: string;
|
|
61
|
+
checkbox: string;
|
|
62
|
+
item: string;
|
|
63
|
+
title: string;
|
|
64
|
+
childrenWrapper: string;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface TreeProps
|
|
69
|
+
extends Pick<
|
|
70
|
+
TreeItemProps,
|
|
71
|
+
| "renderIcon"
|
|
72
|
+
| "renderRightSection"
|
|
73
|
+
| "renderElement"
|
|
74
|
+
| "renderTitle"
|
|
75
|
+
| "showIcon"
|
|
76
|
+
| "disabled"
|
|
77
|
+
| "enableSeparatorLine"
|
|
78
|
+
| "classes"
|
|
79
|
+
> {
|
|
80
|
+
data: TreeData[];
|
|
81
|
+
defaultExpandedId?: string[];
|
|
82
|
+
defaultCheckedId?: string[];
|
|
83
|
+
checkedId?: string[];
|
|
84
|
+
loadingId?: string[];
|
|
85
|
+
onExpandChange?: (id: string, expanded: boolean) => void;
|
|
86
|
+
onCheckedChange?: (checkedId: string[]) => void;
|
|
87
|
+
defaultExpandAll?: boolean;
|
|
88
|
+
defaultCheckAll?: boolean;
|
|
89
|
+
hierarchicalCheck?: boolean;
|
|
90
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ export * from "./components/Tooltip/TooltipSimple";
|
|
|
39
39
|
export * from "./components/Toast/Toast";
|
|
40
40
|
export * from "./components/Toast/Toaster";
|
|
41
41
|
export * from "./components/Toast/useToast";
|
|
42
|
+
export * from "./components/Tree";
|
|
42
43
|
|
|
43
44
|
// Export component types
|
|
44
45
|
export type { ButtonProps } from "./components/Button/Button";
|