@rovula/ui 0.0.48 → 0.0.50
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 -3
- 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 +9 -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 +20 -52
- package/dist/components/Tree/Tree.stories.js +2210 -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 -3
- 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 +9 -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 -4
- package/package.json +1 -1
- package/src/components/Tree/Tree.stories.tsx +2397 -8
- package/src/components/Tree/Tree.tsx +52 -188
- package/src/components/Tree/TreeItem.tsx +232 -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
|
-
defaultExpandedId
|
|
162
|
-
defaultCheckedId
|
|
8
|
+
defaultExpandedId,
|
|
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>>(
|
|
@@ -184,13 +40,13 @@ export const Tree: FC<TreeProps> = ({
|
|
|
184
40
|
};
|
|
185
41
|
|
|
186
42
|
useEffect(() => {
|
|
187
|
-
if (defaultExpandAll) {
|
|
43
|
+
if (data.length && defaultExpandAll) {
|
|
188
44
|
const allExpanded: Record<string, boolean> = {};
|
|
189
45
|
traverseTree(data, (node) => {
|
|
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;
|
|
@@ -200,24 +56,31 @@ export const Tree: FC<TreeProps> = ({
|
|
|
200
56
|
}, [data, defaultExpandedId, defaultExpandAll]);
|
|
201
57
|
|
|
202
58
|
useEffect(() => {
|
|
203
|
-
if (defaultCheckAll) {
|
|
59
|
+
if (data.length && defaultCheckAll) {
|
|
204
60
|
const allChecked: Record<string, boolean> = {};
|
|
205
61
|
traverseTree(data, (node) => {
|
|
206
62
|
allChecked[node.id] = true;
|
|
207
63
|
});
|
|
208
64
|
setCheckedState(allChecked);
|
|
209
|
-
} else if (
|
|
65
|
+
} else if (
|
|
66
|
+
Object.keys(checkedState)?.length === 0 &&
|
|
67
|
+
defaultCheckedId?.length
|
|
68
|
+
) {
|
|
210
69
|
const initialCheckedState = defaultCheckedId.reduce((acc, id) => {
|
|
211
70
|
acc[id] = true;
|
|
212
71
|
return acc;
|
|
213
72
|
}, {} as Record<string, boolean>);
|
|
214
73
|
setCheckedState(initialCheckedState);
|
|
215
74
|
}
|
|
216
|
-
}, [data, defaultCheckedId,
|
|
75
|
+
}, [data, defaultCheckedId, defaultCheckAll]);
|
|
217
76
|
|
|
218
|
-
const handleExpandChange = useCallback(
|
|
219
|
-
|
|
220
|
-
|
|
77
|
+
const handleExpandChange = useCallback(
|
|
78
|
+
(id: string, expanded: boolean) => {
|
|
79
|
+
onExpandChange?.(id, expanded);
|
|
80
|
+
setExpandedState((prev) => ({ ...prev, [id]: expanded }));
|
|
81
|
+
},
|
|
82
|
+
[onExpandChange]
|
|
83
|
+
);
|
|
221
84
|
|
|
222
85
|
const handleCheckedChange = useCallback(
|
|
223
86
|
(id: string, checked: boolean) => {
|
|
@@ -293,39 +156,40 @@ export const Tree: FC<TreeProps> = ({
|
|
|
293
156
|
[checkedId, checkedState]
|
|
294
157
|
);
|
|
295
158
|
|
|
159
|
+
const checkIsLoading = useCallback(
|
|
160
|
+
(id: string) => {
|
|
161
|
+
if (loadingId) {
|
|
162
|
+
return loadingId.includes(id);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
[loadingId]
|
|
166
|
+
);
|
|
167
|
+
|
|
296
168
|
return (
|
|
297
169
|
<div className="w-full">
|
|
298
170
|
{data.map((item, idx) => (
|
|
299
171
|
<TreeItem
|
|
300
172
|
key={item.id}
|
|
301
|
-
{
|
|
173
|
+
classes={classes}
|
|
302
174
|
isFirstLevel
|
|
303
175
|
isLastItem={idx === data.length - 1}
|
|
304
176
|
checkIsExpanded={checkIsExpanded}
|
|
305
177
|
checkIsChecked={checkIsChecked}
|
|
306
178
|
onExpandChange={handleExpandChange}
|
|
307
179
|
onCheckedChange={handleCheckedChange}
|
|
180
|
+
checkIsLoading={checkIsLoading}
|
|
181
|
+
renderIcon={renderIcon}
|
|
182
|
+
renderElement={renderElement}
|
|
183
|
+
renderTitle={renderTitle}
|
|
184
|
+
renderRightSection={renderRightSection}
|
|
185
|
+
enableSeparatorLine={enableSeparatorLine}
|
|
186
|
+
disabled={disabled}
|
|
187
|
+
showIcon={showIcon}
|
|
188
|
+
{...item}
|
|
308
189
|
/>
|
|
309
190
|
))}
|
|
310
191
|
</div>
|
|
311
192
|
);
|
|
312
193
|
};
|
|
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
|
-
*/
|
|
194
|
+
|
|
195
|
+
export default Tree;
|
|
@@ -0,0 +1,232 @@
|
|
|
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:
|
|
52
|
+
isLastItem && !(isExpanded && hasChildren)
|
|
53
|
+
? `calc(50% + ${lineSize}px)`
|
|
54
|
+
: `calc(100% + ${lineSize}px)`,
|
|
55
|
+
width: lineSize,
|
|
56
|
+
marginTop: -lineSize,
|
|
57
|
+
borderBottomLeftRadius: lineSize / 2,
|
|
58
|
+
},
|
|
59
|
+
horizontalLine: {
|
|
60
|
+
height: lineSize,
|
|
61
|
+
width:
|
|
62
|
+
lineSize +
|
|
63
|
+
horizontalLineWidth +
|
|
64
|
+
(shouldExpandButton ? 0 : expandButtonSize + spacing),
|
|
65
|
+
marginLeft: -lineSize + 0.1,
|
|
66
|
+
borderBottomLeftRadius: lineSize / 2,
|
|
67
|
+
},
|
|
68
|
+
expandButton: {
|
|
69
|
+
width: expandButtonSize,
|
|
70
|
+
height: expandButtonSize,
|
|
71
|
+
},
|
|
72
|
+
childPadding: {
|
|
73
|
+
paddingLeft: isFirstLevel
|
|
74
|
+
? expandButtonSize / 2 - lineSize / 2
|
|
75
|
+
: expandButtonSize / 2 + horizontalLineWidth - lineSize / 2,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (isExpanded && !isLoading && !hasChildren) {
|
|
81
|
+
handleExpandToggle();
|
|
82
|
+
}
|
|
83
|
+
}, [isLoading, handleExpandToggle]);
|
|
84
|
+
|
|
85
|
+
const handleOnClickItem = useCallback(() => {
|
|
86
|
+
onClickItem?.(id);
|
|
87
|
+
}, [onClickItem, id]);
|
|
88
|
+
|
|
89
|
+
const defaultIcon = (
|
|
90
|
+
<Icon
|
|
91
|
+
name={isExpanded ? "folder-open" : "folder"}
|
|
92
|
+
className="fill-warning"
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const customIcon =
|
|
97
|
+
icon ??
|
|
98
|
+
renderIcon?.({
|
|
99
|
+
id,
|
|
100
|
+
expanded: isExpanded,
|
|
101
|
+
selected: isChecked,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const rightIcon = renderRightSection?.({
|
|
105
|
+
id,
|
|
106
|
+
expanded: isExpanded,
|
|
107
|
+
selected: isChecked,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const titleContent = renderTitle
|
|
111
|
+
? renderTitle({ id, title, expanded: isExpanded, selected: isChecked })
|
|
112
|
+
: title;
|
|
113
|
+
|
|
114
|
+
const elementWrapper = (content: ReactNode) =>
|
|
115
|
+
renderElement
|
|
116
|
+
? renderElement({
|
|
117
|
+
id,
|
|
118
|
+
expanded: isExpanded,
|
|
119
|
+
selected: isChecked,
|
|
120
|
+
children: content,
|
|
121
|
+
styles,
|
|
122
|
+
onClick: handleOnClickItem,
|
|
123
|
+
})
|
|
124
|
+
: content;
|
|
125
|
+
|
|
126
|
+
return elementWrapper(
|
|
127
|
+
<div className={cn("flex flex-row w-full ", classes?.elementWrapper)}>
|
|
128
|
+
<div
|
|
129
|
+
className={cn("bg-grey-150", classes?.branch)}
|
|
130
|
+
style={styles.branch}
|
|
131
|
+
/>
|
|
132
|
+
<div className={cn("flex flex-col w-full", classes?.itemWrapper)}>
|
|
133
|
+
<div
|
|
134
|
+
className={cn(
|
|
135
|
+
"flex items-center py-2 min-h-10",
|
|
136
|
+
classes?.itemContainer
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
{!isFirstLevel && (
|
|
140
|
+
<div
|
|
141
|
+
className={cn("bg-grey-150", classes?.horizontalLine)}
|
|
142
|
+
style={styles.horizontalLine}
|
|
143
|
+
/>
|
|
144
|
+
)}
|
|
145
|
+
{isFirstLevel && !shouldExpandButton && (
|
|
146
|
+
<div
|
|
147
|
+
className={cn("flex mr-[2px]", classes?.expandButton)}
|
|
148
|
+
style={styles.expandButton}
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
151
|
+
{shouldExpandButton && (
|
|
152
|
+
<div
|
|
153
|
+
className={cn("flex mr-[2px]", classes?.expandButton)}
|
|
154
|
+
style={styles.expandButton}
|
|
155
|
+
onClick={!isLoading && handleExpandToggle}
|
|
156
|
+
>
|
|
157
|
+
<ActionButton variant="icon" size="sm">
|
|
158
|
+
{isLoading ? (
|
|
159
|
+
<Loading />
|
|
160
|
+
) : (
|
|
161
|
+
<Icon name={isExpanded ? "chevron-down" : "chevron-right"} />
|
|
162
|
+
)}
|
|
163
|
+
</ActionButton>
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
<Checkbox
|
|
167
|
+
id={id}
|
|
168
|
+
className={cn("size-[16pt]", classes?.checkbox)}
|
|
169
|
+
checked={isChecked}
|
|
170
|
+
disabled={disabled}
|
|
171
|
+
onCheckedChange={(newChecked) =>
|
|
172
|
+
onCheckedChange?.(id, newChecked as boolean)
|
|
173
|
+
}
|
|
174
|
+
/>
|
|
175
|
+
<div
|
|
176
|
+
className={cn(
|
|
177
|
+
"ml-2 gap-1 flex flex-1 items-center text-foreground",
|
|
178
|
+
classes?.item
|
|
179
|
+
)}
|
|
180
|
+
onClick={handleOnClickItem}
|
|
181
|
+
>
|
|
182
|
+
{showIcon ? customIcon || defaultIcon : null}
|
|
183
|
+
<div
|
|
184
|
+
className={cn(
|
|
185
|
+
"flex flex-1 cursor-pointer text-subtitle5 text-ellipsis",
|
|
186
|
+
classes?.title
|
|
187
|
+
)}
|
|
188
|
+
>
|
|
189
|
+
{titleContent}
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
{rightIcon}
|
|
193
|
+
</div>
|
|
194
|
+
{isExpanded && hasChildren && (
|
|
195
|
+
<div
|
|
196
|
+
className={cn("flex flex-col", classes?.childrenWrapper)}
|
|
197
|
+
style={styles.childPadding}
|
|
198
|
+
>
|
|
199
|
+
{children?.map((child, idx) => (
|
|
200
|
+
<TreeItem
|
|
201
|
+
key={child.id}
|
|
202
|
+
classes={classes}
|
|
203
|
+
isLastItem={idx === children.length - 1}
|
|
204
|
+
checkIsExpanded={checkIsExpanded}
|
|
205
|
+
checkIsChecked={checkIsChecked}
|
|
206
|
+
checkIsLoading={checkIsLoading}
|
|
207
|
+
onExpandChange={onExpandChange}
|
|
208
|
+
onCheckedChange={onCheckedChange}
|
|
209
|
+
renderIcon={renderIcon}
|
|
210
|
+
renderElement={renderElement}
|
|
211
|
+
renderTitle={renderTitle}
|
|
212
|
+
disabled={disabled}
|
|
213
|
+
showIcon={showIcon}
|
|
214
|
+
{...child}
|
|
215
|
+
/>
|
|
216
|
+
))}
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
{enableSeparatorLine && isFirstLevel && !isLastItem && (
|
|
220
|
+
<div
|
|
221
|
+
className={cn(
|
|
222
|
+
"bg-grey-150 w-full h-[2px] rounded",
|
|
223
|
+
classes?.separatorLine
|
|
224
|
+
)}
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
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";
|