@rovula/ui 0.0.47 → 0.0.48
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 +23 -4
- package/dist/cjs/bundle.js +1 -1
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Switch/Switch.stories.d.ts +1 -6
- package/dist/cjs/types/components/Tree/Tree.d.ts +45 -0
- package/dist/cjs/types/components/Tree/Tree.stories.d.ts +5 -0
- package/dist/components/Switch/Switch.js +2 -2
- package/dist/components/Switch/Switch.stories.js +2 -7
- package/dist/components/Tree/Tree.js +138 -0
- package/dist/components/Tree/Tree.stories.js +53 -0
- package/dist/esm/bundle.css +23 -4
- package/dist/esm/bundle.js +1 -1
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Switch/Switch.stories.d.ts +1 -6
- package/dist/esm/types/components/Tree/Tree.d.ts +45 -0
- package/dist/esm/types/components/Tree/Tree.stories.d.ts +5 -0
- package/dist/src/theme/global.css +63 -14
- package/dist/theme/themes/SKL/color.css +10 -10
- package/dist/theme/themes/xspector/baseline.css +1 -0
- package/dist/theme/themes/xspector/components/switch.css +30 -0
- package/package.json +1 -1
- package/src/components/Switch/Switch.stories.tsx +2 -7
- package/src/components/Switch/Switch.tsx +2 -2
- package/src/components/Tree/Tree.stories.tsx +66 -0
- package/src/components/Tree/Tree.tsx +331 -0
- package/src/theme/themes/SKL/color.css +10 -10
- package/src/theme/themes/xspector/baseline.css +1 -0
- package/src/theme/themes/xspector/components/switch.css +30 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { ActionButton, Checkbox, Icon, Loading, cn } from "@/index";
|
|
2
|
+
import React, {
|
|
3
|
+
FC,
|
|
4
|
+
ReactNode,
|
|
5
|
+
useCallback,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState,
|
|
8
|
+
useEffect,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
export type TreeData = {
|
|
12
|
+
id: string;
|
|
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> = ({
|
|
160
|
+
data,
|
|
161
|
+
defaultExpandedId = [],
|
|
162
|
+
defaultCheckedId = [],
|
|
163
|
+
checkedId,
|
|
164
|
+
onCheckedChange,
|
|
165
|
+
defaultExpandAll = false,
|
|
166
|
+
defaultCheckAll = false,
|
|
167
|
+
hierarchicalCheck = false,
|
|
168
|
+
}) => {
|
|
169
|
+
const [checkedState, setCheckedState] = useState<Record<string, boolean>>({});
|
|
170
|
+
const [expandedState, setExpandedState] = useState<Record<string, boolean>>(
|
|
171
|
+
{}
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const traverseTree = (
|
|
175
|
+
nodes: TreeData[],
|
|
176
|
+
callback: (node: TreeData) => void
|
|
177
|
+
) => {
|
|
178
|
+
nodes.forEach((node) => {
|
|
179
|
+
callback(node);
|
|
180
|
+
if (node.children) {
|
|
181
|
+
traverseTree(node.children, callback);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
if (defaultExpandAll) {
|
|
188
|
+
const allExpanded: Record<string, boolean> = {};
|
|
189
|
+
traverseTree(data, (node) => {
|
|
190
|
+
allExpanded[node.id] = true;
|
|
191
|
+
});
|
|
192
|
+
setExpandedState(allExpanded);
|
|
193
|
+
} else {
|
|
194
|
+
const initialExpandedState = defaultExpandedId.reduce((acc, id) => {
|
|
195
|
+
acc[id] = true;
|
|
196
|
+
return acc;
|
|
197
|
+
}, {} as Record<string, boolean>);
|
|
198
|
+
setExpandedState(initialExpandedState);
|
|
199
|
+
}
|
|
200
|
+
}, [data, defaultExpandedId, defaultExpandAll]);
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
if (defaultCheckAll) {
|
|
204
|
+
const allChecked: Record<string, boolean> = {};
|
|
205
|
+
traverseTree(data, (node) => {
|
|
206
|
+
allChecked[node.id] = true;
|
|
207
|
+
});
|
|
208
|
+
setCheckedState(allChecked);
|
|
209
|
+
} else if (!checkedId) {
|
|
210
|
+
const initialCheckedState = defaultCheckedId.reduce((acc, id) => {
|
|
211
|
+
acc[id] = true;
|
|
212
|
+
return acc;
|
|
213
|
+
}, {} as Record<string, boolean>);
|
|
214
|
+
setCheckedState(initialCheckedState);
|
|
215
|
+
}
|
|
216
|
+
}, [data, defaultCheckedId, checkedId, defaultCheckAll]);
|
|
217
|
+
|
|
218
|
+
const handleExpandChange = useCallback((id: string, expanded: boolean) => {
|
|
219
|
+
setExpandedState((prev) => ({ ...prev, [id]: expanded }));
|
|
220
|
+
}, []);
|
|
221
|
+
|
|
222
|
+
const handleCheckedChange = useCallback(
|
|
223
|
+
(id: string, checked: boolean) => {
|
|
224
|
+
let newState = { ...checkedState, [id]: checked };
|
|
225
|
+
|
|
226
|
+
if (hierarchicalCheck) {
|
|
227
|
+
const updateCheckedState = (
|
|
228
|
+
nodeId: string,
|
|
229
|
+
isChecked: boolean,
|
|
230
|
+
state: Record<string, boolean>
|
|
231
|
+
) => {
|
|
232
|
+
state[nodeId] = isChecked;
|
|
233
|
+
|
|
234
|
+
// Update children recursively
|
|
235
|
+
const updateChildren = (parentId: string, isChecked: boolean) => {
|
|
236
|
+
traverseTree(data, (node) => {
|
|
237
|
+
if (node.id === parentId && node.children) {
|
|
238
|
+
node.children.forEach((child) => {
|
|
239
|
+
state[child.id] = isChecked;
|
|
240
|
+
updateChildren(child.id, isChecked);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Update parents recursively
|
|
247
|
+
const updateParents = (
|
|
248
|
+
childId: string,
|
|
249
|
+
state: Record<string, boolean>
|
|
250
|
+
) => {
|
|
251
|
+
traverseTree(data, (node) => {
|
|
252
|
+
if (node.children?.some((child) => child.id === childId)) {
|
|
253
|
+
const allChildrenChecked = node.children.every(
|
|
254
|
+
(child) => state[child.id]
|
|
255
|
+
);
|
|
256
|
+
state[node.id] = allChildrenChecked;
|
|
257
|
+
updateParents(node.id, state);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
updateChildren(nodeId, isChecked);
|
|
263
|
+
updateParents(nodeId, state);
|
|
264
|
+
|
|
265
|
+
return state;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
newState = updateCheckedState(id, checked, newState);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
setCheckedState(newState);
|
|
272
|
+
|
|
273
|
+
if (onCheckedChange) {
|
|
274
|
+
const checkedIds = Object.keys(newState).filter((key) => newState[key]);
|
|
275
|
+
onCheckedChange(checkedIds);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
[checkedState, data, onCheckedChange, hierarchicalCheck]
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const checkIsExpanded = useCallback(
|
|
282
|
+
(id: string) => !!expandedState[id],
|
|
283
|
+
[expandedState]
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const checkIsChecked = useCallback(
|
|
287
|
+
(id: string) => {
|
|
288
|
+
if (checkedId) {
|
|
289
|
+
return checkedId.includes(id);
|
|
290
|
+
}
|
|
291
|
+
return !!checkedState[id];
|
|
292
|
+
},
|
|
293
|
+
[checkedId, checkedState]
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<div className="w-full">
|
|
298
|
+
{data.map((item, idx) => (
|
|
299
|
+
<TreeItem
|
|
300
|
+
key={item.id}
|
|
301
|
+
{...item}
|
|
302
|
+
isFirstLevel
|
|
303
|
+
isLastItem={idx === data.length - 1}
|
|
304
|
+
checkIsExpanded={checkIsExpanded}
|
|
305
|
+
checkIsChecked={checkIsChecked}
|
|
306
|
+
onExpandChange={handleExpandChange}
|
|
307
|
+
onCheckedChange={handleCheckedChange}
|
|
308
|
+
/>
|
|
309
|
+
))}
|
|
310
|
+
</div>
|
|
311
|
+
);
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* TODO
|
|
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
|
+
*/
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
--input-color-error: #ed2f15;
|
|
13
13
|
|
|
14
14
|
/* Function button */
|
|
15
|
-
--function-default-solid:
|
|
16
|
-
--function-default-hover:
|
|
17
|
-
--function-default-hover-bg:
|
|
18
|
-
--function-default-stroke:
|
|
15
|
+
--function-default-solid: var(--state-color-primary-default);
|
|
16
|
+
--function-default-hover: var(--state-color-primary-hover);
|
|
17
|
+
--function-default-hover-bg: var(--state-color-primary-hover-bg);
|
|
18
|
+
--function-default-stroke: var(--state-color-primary-stroke);
|
|
19
19
|
--function-default-icon: #ffffff;
|
|
20
|
-
--function-default-outline-icon:
|
|
21
|
-
--function-active-solid:
|
|
22
|
-
--function-active-hover:
|
|
23
|
-
--function-active-hover-bg:
|
|
24
|
-
--function-active-stroke:
|
|
20
|
+
--function-default-outline-icon: var(--state-color-primary-default);
|
|
21
|
+
--function-active-solid: var(--state-color-secondary-default);
|
|
22
|
+
--function-active-hover: var(--state-color-secondary-hover);
|
|
23
|
+
--function-active-hover-bg: var(--state-color-secondary-hover-bg);
|
|
24
|
+
--function-active-stroke: var(--state-color-secondary-stroke);
|
|
25
25
|
--function-active-icon: #ffffff;
|
|
26
26
|
|
|
27
27
|
--text-black: #000000;
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
--foreground: var(--common-black);
|
|
50
50
|
|
|
51
51
|
--primary: var(--primary-ramps-primary-100);
|
|
52
|
-
--
|
|
52
|
+
--primary: var(--secondary-ramps-secondary-100);
|
|
53
53
|
--tertiary: var(--tertiary-ramps-tertiary-100);
|
|
54
54
|
--info: var(--info-info-100);
|
|
55
55
|
--success: var(--success-success-100);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
:root[data-theme="xspector"] {
|
|
2
|
+
/* ------------------------------------------------------------------ */
|
|
3
|
+
/* Switch Component Tokens */
|
|
4
|
+
/* ------------------------------------------------------------------ */
|
|
5
|
+
/* Naming Convention: --[component]-[element]-[state]-[property] */
|
|
6
|
+
/* Element: [progress, track] */
|
|
7
|
+
/* ------------------------------------------------------------------ */
|
|
8
|
+
|
|
9
|
+
/* Default State */
|
|
10
|
+
--switch-default-color: rgb(from var(--state-color-secondary-active) r g b / 0.32);
|
|
11
|
+
--switch-thumb-default-color: var(--state-color-secondary-active);
|
|
12
|
+
|
|
13
|
+
/* Hover State */
|
|
14
|
+
--switch-hover-color: rgb(from var(--state-color-secondary-active) r g b / 0.48);
|
|
15
|
+
--switch-thumb-hover-color: var(--switch-thumb-default-color);
|
|
16
|
+
--switch-thumb-hover-ring: var(--state-color-secondary-hover-bg);
|
|
17
|
+
|
|
18
|
+
/* Active State */
|
|
19
|
+
--switch-active-color: rgb(from var(--state-color-primary-active) r g b / 0.32);
|
|
20
|
+
--switch-thumb-active-color: var(--state-color-primary-active);
|
|
21
|
+
|
|
22
|
+
/* Active Hover State */
|
|
23
|
+
--switch-active-hover-color: rgb(from var(--state-color-primary-active) r g b / 0.48);
|
|
24
|
+
--switch-thumb-active-hover-color: var(--switch-thumb-active-color);
|
|
25
|
+
--switch-thumb-active-hover-ring: var(--state-color-primary-hover-bg);
|
|
26
|
+
|
|
27
|
+
/* Disabled State */
|
|
28
|
+
--switch-disabled-color: rgb(from var(--state-color-disable-solid) r g b / 0.32);
|
|
29
|
+
--switch-thumb-disabled-color: var(--state-color-disable-solid)
|
|
30
|
+
}
|