@boxcustodia/library 2.0.0-alpha.13 → 2.0.0-alpha.15
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/index.cjs.js +1 -138
- package/dist/index.d.ts +1083 -717
- package/dist/index.es.js +7059 -56179
- package/dist/theme.css +1 -1
- package/package.json +34 -26
- package/src/__doc__/Changelog.mdx +6 -6
- package/src/__doc__/Examples.tsx +1 -1
- package/src/__doc__/Intro.mdx +3 -3
- package/src/__doc__/Tabs.mdx +112 -0
- package/src/__doc__/V2.mdx +1245 -0
- package/src/components/accordion/accordion.stories.tsx +143 -0
- package/src/components/accordion/accordion.tsx +135 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.stories.tsx +24 -4
- package/src/components/alert/alert.tsx +17 -9
- package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
- package/src/components/alert-dialog/alert-dialog.tsx +58 -10
- package/src/components/auto-complete/auto-complete.stories.tsx +615 -200
- package/src/components/auto-complete/auto-complete.tsx +420 -68
- package/src/components/auto-complete/index.ts +0 -1
- package/src/components/avatar/avatar.stories.tsx +162 -21
- package/src/components/avatar/avatar.tsx +79 -20
- package/src/components/button/button.stories.tsx +236 -294
- package/src/components/button/button.test.tsx +10 -17
- package/src/components/button/button.tsx +53 -18
- package/src/components/button/components/base-button.tsx +25 -53
- package/src/components/button/index.ts +0 -1
- package/src/components/calendar/calendar.stories.tsx +1 -1
- package/src/components/calendar/calendar.tsx +4 -4
- package/src/components/card/card.stories.tsx +140 -69
- package/src/components/card/card.tsx +155 -54
- package/src/components/center/center.stories.tsx +22 -39
- package/src/components/checkbox/checkbox.stories.tsx +25 -5
- package/src/components/checkbox/checkbox.tsx +76 -15
- package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
- package/src/components/checkbox-group/checkbox-group.tsx +84 -3
- package/src/components/combobox/combobox.stories.tsx +33 -23
- package/src/components/combobox/combobox.tsx +120 -104
- package/src/components/date-picker/date-input.stories.tsx +14 -6
- package/src/components/date-picker/date-input.tsx +3 -3
- package/src/components/date-picker/date-picker.model.ts +13 -4
- package/src/components/date-picker/date-picker.stories.tsx +38 -12
- package/src/components/date-picker/date-picker.tsx +29 -15
- package/src/components/dialog/dialog.stories.tsx +18 -0
- package/src/components/dialog/dialog.test.tsx +1 -1
- package/src/components/dialog/dialog.tsx +51 -20
- package/src/components/divider/divider.stories.tsx +6 -0
- package/src/components/dropzone/dropzone.stories.tsx +70 -90
- package/src/components/dropzone/dropzone.tsx +383 -105
- package/src/components/dropzone/index.ts +0 -1
- package/src/components/empty/empty.stories.tsx +164 -0
- package/src/components/empty/empty.tsx +156 -0
- package/src/components/empty/index.ts +1 -0
- package/src/components/field/field.stories.tsx +226 -3
- package/src/components/field/field.tsx +77 -42
- package/src/components/form/form.stories.tsx +320 -197
- package/src/components/form/form.tsx +3 -23
- package/src/components/index.ts +2 -6
- package/src/components/input/input.stories.tsx +5 -5
- package/src/components/input/input.tsx +5 -5
- package/src/components/kbd/kbd.stories.tsx +1 -0
- package/src/components/label/label.stories.tsx +16 -0
- package/src/components/label/label.tsx +13 -2
- package/src/components/loader/loader.stories.tsx +7 -5
- package/src/components/loader/loader.tsx +8 -3
- package/src/components/menu/menu-primitives.tsx +207 -196
- package/src/components/menu/menu.stories.tsx +275 -146
- package/src/components/menu/menu.tsx +146 -54
- package/src/components/number-input/number-input.stories.tsx +27 -4
- package/src/components/number-input/number-input.test.tsx +2 -2
- package/src/components/number-input/number-input.tsx +29 -33
- package/src/components/otp/index.ts +1 -0
- package/src/components/otp/otp.stories.tsx +209 -0
- package/src/components/otp/otp.tsx +100 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.model.ts +2 -0
- package/src/components/pagination/pagination.stories.tsx +153 -59
- package/src/components/pagination/pagination.test.tsx +122 -57
- package/src/components/pagination/pagination.tsx +575 -77
- package/src/components/password/password.stories.tsx +18 -3
- package/src/components/password/password.tsx +26 -10
- package/src/components/popover/popover.stories.tsx +26 -5
- package/src/components/popover/popover.tsx +15 -23
- package/src/components/progress/progress.stories.tsx +1 -0
- package/src/components/radio-group/index.ts +1 -0
- package/src/components/radio-group/radio-group.stories.tsx +251 -0
- package/src/components/radio-group/radio-group.tsx +212 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
- package/src/components/select/select.stories.tsx +118 -19
- package/src/components/select/select.tsx +67 -62
- package/src/components/skeleton/skeleton.stories.tsx +1 -0
- package/src/components/stack/stack.stories.tsx +179 -89
- package/src/components/stack/stack.tsx +2 -2
- package/src/components/stepper/index.ts +1 -1
- package/src/components/stepper/stepper.stories.tsx +766 -83
- package/src/components/stepper/stepper.test.tsx +18 -18
- package/src/components/stepper/stepper.tsx +554 -0
- package/src/components/switch/switch.stories.tsx +15 -1
- package/src/components/switch/switch.tsx +17 -4
- package/src/components/table/index.ts +0 -2
- package/src/components/table/table.stories.tsx +131 -18
- package/src/components/table/table.test.tsx +1 -1
- package/src/components/table/table.tsx +183 -77
- package/src/components/tabs/tabs.stories.tsx +372 -155
- package/src/components/tabs/tabs.test.tsx +12 -12
- package/src/components/tabs/tabs.tsx +72 -149
- package/src/components/tag/index.ts +0 -1
- package/src/components/tag/tag.stories.tsx +147 -120
- package/src/components/tag/tag.tsx +47 -95
- package/src/components/textarea/textarea.stories.tsx +8 -22
- package/src/components/textarea/textarea.tsx +17 -79
- package/src/components/timeline/timeline.stories.tsx +322 -42
- package/src/components/timeline/timeline.tsx +359 -132
- package/src/components/toast/toast.stories.tsx +1 -0
- package/src/components/tooltip/tooltip.tsx +11 -9
- package/src/components/tree/index.ts +0 -1
- package/src/components/tree/tree.stories.tsx +364 -408
- package/src/components/tree/tree.test.tsx +163 -0
- package/src/components/tree/tree.tsx +212 -36
- package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
- package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
- package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
- package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
- package/src/hooks/usePagination/usePagination.tsx +36 -24
- package/src/styles/theme.css +1 -1
- package/src/utils/form.tsx +69 -37
- package/src/utils/index.ts +1 -1
- package/src/__doc__/Migration.mdx +0 -451
- package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
- package/src/components/background-image/background-image.stories.tsx +0 -21
- package/src/components/background-image/background-image.test.tsx +0 -29
- package/src/components/background-image/background-image.tsx +0 -23
- package/src/components/background-image/index.ts +0 -1
- package/src/components/button/button.variants.ts +0 -44
- package/src/components/button/components/loader-overlay.tsx +0 -21
- package/src/components/button/components/loading-icon.tsx +0 -47
- package/src/components/dropzone/upload-primitives.tsx +0 -310
- package/src/components/dropzone/use-dropzone.ts +0 -122
- package/src/components/empty-state/empty-state.stories.tsx +0 -56
- package/src/components/empty-state/empty-state.tsx +0 -39
- package/src/components/empty-state/index.ts +0 -1
- package/src/components/heading/heading.stories.tsx +0 -74
- package/src/components/heading/heading.tsx +0 -28
- package/src/components/heading/heading.variants.ts +0 -27
- package/src/components/heading/index.ts +0 -1
- package/src/components/kbd/kbd.variants.ts +0 -26
- package/src/components/menu/util/render-menu-item.tsx +0 -54
- package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
- package/src/components/multi-select/index.ts +0 -1
- package/src/components/multi-select/multi-select.stories.tsx +0 -294
- package/src/components/multi-select/multi-select.tsx +0 -300
- package/src/components/multi-select/multi-select.variants.ts +0 -22
- package/src/components/pagination/components/pagination-option.tsx +0 -27
- package/src/components/show/index.ts +0 -1
- package/src/components/show/show.stories.tsx +0 -197
- package/src/components/show/show.test.tsx +0 -41
- package/src/components/show/show.tsx +0 -16
- package/src/components/stepper/Stepper.tsx +0 -190
- package/src/components/stepper/context/stepper-context.tsx +0 -11
- package/src/components/table/table-primitives.tsx +0 -122
- package/src/components/table/table.model.ts +0 -20
- package/src/components/table-pagination/index.ts +0 -2
- package/src/components/table-pagination/table-pagination.model.ts +0 -2
- package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
- package/src/components/table-pagination/table-pagination.test.tsx +0 -32
- package/src/components/table-pagination/table-pagination.tsx +0 -108
- package/src/components/tabs/context/tabs-context.tsx +0 -14
- package/src/components/tag/tag.variants.ts +0 -31
- package/src/components/timeline/timeline-status.ts +0 -5
- package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
- package/src/components/tree/tree-primitives.tsx +0 -126
|
@@ -1,468 +1,424 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
createOnDropHandler,
|
|
3
|
+
dragAndDropFeature,
|
|
4
|
+
hotkeysCoreFeature,
|
|
5
|
+
type ItemInstance,
|
|
6
|
+
keyboardDragAndDropFeature,
|
|
7
|
+
searchFeature,
|
|
8
|
+
selectionFeature,
|
|
9
|
+
syncDataLoaderFeature,
|
|
10
|
+
} from "@headless-tree/core";
|
|
11
|
+
import { useTree } from "@headless-tree/react";
|
|
12
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
13
|
+
import {
|
|
14
|
+
FileTextIcon,
|
|
15
|
+
FolderIcon,
|
|
16
|
+
FolderOpenIcon,
|
|
17
|
+
MoreHorizontalIcon,
|
|
18
|
+
} from "lucide-react";
|
|
19
|
+
import { type ComponentType, useState } from "react";
|
|
5
20
|
|
|
6
|
-
|
|
21
|
+
import { Input } from "../input/input";
|
|
22
|
+
import { Menu } from "../menu/menu";
|
|
23
|
+
import { TreeDragLine, TreeItem, TreeItemLabel, TreeRoot } from "./tree";
|
|
7
24
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
TreeItemIndicator,
|
|
13
|
-
TreeItemLabel,
|
|
14
|
-
TreeRoot,
|
|
15
|
-
} from "./tree-primitives";
|
|
25
|
+
interface Item {
|
|
26
|
+
name: string;
|
|
27
|
+
children?: string[];
|
|
28
|
+
}
|
|
16
29
|
|
|
17
|
-
const
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
],
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "1-2",
|
|
44
|
-
title: "Presentations",
|
|
45
|
-
children: [
|
|
46
|
-
{
|
|
47
|
-
id: "1-2-1",
|
|
48
|
-
title: "2023 Projects",
|
|
49
|
-
children: [
|
|
50
|
-
{
|
|
51
|
-
id: "1-2-1-1",
|
|
52
|
-
title: "Project A",
|
|
53
|
-
children: [
|
|
54
|
-
{ id: "1-2-1-1-1", title: "Draft", children: [] },
|
|
55
|
-
{ id: "1-2-1-1-2", title: "Final", children: [] },
|
|
56
|
-
],
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
id: "1-2-1-2",
|
|
60
|
-
title: "Project B",
|
|
61
|
-
children: [
|
|
62
|
-
{ id: "1-2-1-2-1", title: "Research", children: [] },
|
|
63
|
-
],
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: "2",
|
|
73
|
-
title: "Personal",
|
|
74
|
-
children: [
|
|
75
|
-
{
|
|
76
|
-
id: "2-1",
|
|
77
|
-
title: "Hobbies",
|
|
78
|
-
children: [
|
|
79
|
-
{
|
|
80
|
-
id: "2-1-1",
|
|
81
|
-
title: "Photography",
|
|
82
|
-
children: [
|
|
83
|
-
{ id: "2-1-1-1", title: "Travel", children: [] },
|
|
84
|
-
{ id: "2-1-1-2", title: "Portraits", children: [] },
|
|
85
|
-
],
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
id: "2-1-2",
|
|
89
|
-
title: "Cooking",
|
|
90
|
-
children: [{ id: "2-1-2-1", title: "Recipes", children: [] }],
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: "3",
|
|
98
|
-
title: "Projects",
|
|
99
|
-
children: [
|
|
100
|
-
{
|
|
101
|
-
id: "3-1",
|
|
102
|
-
title: "Web Development",
|
|
103
|
-
children: [
|
|
104
|
-
{
|
|
105
|
-
id: "3-1-1",
|
|
106
|
-
title: "Portfolio",
|
|
107
|
-
children: [
|
|
108
|
-
{ id: "3-1-1-1", title: "Images", children: [] },
|
|
109
|
-
{ id: "3-1-1-2", title: "CSS", children: [] },
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
{ id: "3-1-2", title: "Landing Page", children: [] },
|
|
113
|
-
],
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
id: "3-2",
|
|
117
|
-
title: "Mobile Apps",
|
|
118
|
-
children: [{ id: "3-2-1", title: "Weather App", children: [] }],
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
id: "4",
|
|
124
|
-
title: "Finance",
|
|
125
|
-
children: [
|
|
126
|
-
{
|
|
127
|
-
id: "4-1",
|
|
128
|
-
title: "Budget",
|
|
129
|
-
children: [
|
|
130
|
-
{
|
|
131
|
-
id: "4-1-1",
|
|
132
|
-
title: "2023",
|
|
133
|
-
children: [
|
|
134
|
-
{ id: "4-1-1-1", title: "January", children: [] },
|
|
135
|
-
{ id: "4-1-1-2", title: "February", children: [] },
|
|
136
|
-
],
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
id: "5",
|
|
144
|
-
title: "Health",
|
|
145
|
-
children: [
|
|
146
|
-
{
|
|
147
|
-
id: "5-1",
|
|
148
|
-
title: "Fitness",
|
|
149
|
-
children: [
|
|
150
|
-
{ id: "5-1-1", title: "Workouts", children: [] },
|
|
151
|
-
{ id: "5-1-2", title: "Nutrition", children: [] },
|
|
152
|
-
],
|
|
153
|
-
},
|
|
154
|
-
{ id: "5-2", title: "Wellness", children: [] },
|
|
155
|
-
],
|
|
30
|
+
const items: Record<string, Item> = {
|
|
31
|
+
root: { name: "root", children: ["docs", "src", "tests"] },
|
|
32
|
+
docs: { name: "docs", children: ["readme", "api"] },
|
|
33
|
+
readme: { name: "README.md" },
|
|
34
|
+
api: { name: "API.md" },
|
|
35
|
+
src: { name: "src", children: ["components", "lib"] },
|
|
36
|
+
components: { name: "components", children: ["button", "tree"] },
|
|
37
|
+
button: { name: "button.tsx" },
|
|
38
|
+
tree: { name: "tree.tsx" },
|
|
39
|
+
lib: { name: "lib", children: ["utils"] },
|
|
40
|
+
utils: { name: "utils.ts" },
|
|
41
|
+
tests: { name: "tests", children: ["spec"] },
|
|
42
|
+
spec: { name: "tree.test.tsx" },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const baseConfig = {
|
|
46
|
+
rootItemId: "root",
|
|
47
|
+
getItemName: (item: ItemInstance<Item>) => item.getItemData().name,
|
|
48
|
+
isItemFolder: (item: ItemInstance<Item>) =>
|
|
49
|
+
(item.getItemData().children?.length ?? 0) > 0,
|
|
50
|
+
dataLoader: {
|
|
51
|
+
getItem: (itemId: string) => items[itemId],
|
|
52
|
+
getChildren: (itemId: string) => items[itemId]?.children ?? [],
|
|
156
53
|
},
|
|
157
|
-
]
|
|
54
|
+
initialState: { expandedItems: ["src", "components", "docs"] },
|
|
55
|
+
};
|
|
158
56
|
|
|
159
57
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
58
|
+
* Accessible hierarchical tree built on top of
|
|
59
|
+
* [@headless-tree](https://headless-tree.lukasbach.com/). The consumer creates
|
|
60
|
+
* a tree instance with `useTree(config)`, registers the features they need,
|
|
61
|
+
* and iterates `tree.getItems()` to render rows.
|
|
163
62
|
*
|
|
164
|
-
*
|
|
165
|
-
* Los nodos del árbol deben tener las siguientes propiedades:
|
|
63
|
+
* Setup:
|
|
166
64
|
*
|
|
167
65
|
* ```tsx
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
66
|
+
* const tree = useTree<Item>({
|
|
67
|
+
* rootItemId: "root",
|
|
68
|
+
* getItemName: (item) => item.getItemData().name,
|
|
69
|
+
* isItemFolder: (item) => (item.getItemData().children?.length ?? 0) > 0,
|
|
70
|
+
* dataLoader: {
|
|
71
|
+
* getItem: (itemId) => items[itemId],
|
|
72
|
+
* getChildren: (itemId) => items[itemId]?.children ?? [],
|
|
73
|
+
* },
|
|
74
|
+
* features: [syncDataLoaderFeature, selectionFeature, hotkeysCoreFeature],
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* <TreeRoot tree={tree}>
|
|
78
|
+
* {tree.getItems().map((item) => (
|
|
79
|
+
* <TreeItem
|
|
80
|
+
* key={item.getId()}
|
|
81
|
+
* item={item}
|
|
82
|
+
* leafIcon={<FileIcon />}
|
|
83
|
+
* parentIcon={(isExpanded) =>
|
|
84
|
+
* isExpanded ? <FolderOpenIcon /> : <FolderIcon />
|
|
85
|
+
* }
|
|
86
|
+
* />
|
|
87
|
+
* ))}
|
|
88
|
+
* </TreeRoot>
|
|
173
89
|
* ```
|
|
90
|
+
*
|
|
91
|
+
* Features (from `@headless-tree/core` — register only what you use):
|
|
92
|
+
* - `syncDataLoaderFeature` — sync data via `dataLoader.getItem` /
|
|
93
|
+
* `dataLoader.getChildren`.
|
|
94
|
+
* - `asyncDataLoaderFeature` — returns Promises; children load on expand.
|
|
95
|
+
* - `selectionFeature` — single/multi selection, exposes `item.isSelected()`.
|
|
96
|
+
* - `hotkeysCoreFeature` — Arrow keys to navigate, Enter to activate, Space to
|
|
97
|
+
* select, Right/Left to expand/collapse.
|
|
98
|
+
* - `dragAndDropFeature` — drag/drop with `onDrop`. Pair with `TreeDragLine`.
|
|
99
|
+
* - `keyboardDragAndDropFeature` — keyboard-driven reorder.
|
|
100
|
+
* - `searchFeature` — drive with `tree.setSearch(value)`; matches expose
|
|
101
|
+
* `item.isMatchingSearch()`.
|
|
102
|
+
* - `expandAllFeature` — `tree.expandAll()` / `tree.collapseAll()`.
|
|
103
|
+
* - `propMemoizationFeature` — memoizes `getProps()` (large trees).
|
|
104
|
+
*
|
|
105
|
+
* Key behaviors:
|
|
106
|
+
* - The consumer owns the tree instance, the data shape (`T`), and the
|
|
107
|
+
* iteration. There is no `items` composite prop — that pattern doesn't fit
|
|
108
|
+
* the breadth of real tree use cases.
|
|
109
|
+
* - `TreeItem` accepts `leafIcon` / `parentIcon` to render an icon before the
|
|
110
|
+
* item's name without writing a custom label per row. `parentIcon` defaults
|
|
111
|
+
* to a rotating chevron — pass your own `ReactNode` (use CSS via
|
|
112
|
+
* `in-data-[expanded=true]:…` for state-driven styles) or a function
|
|
113
|
+
* `(isExpanded) => ReactNode` to swap elements based on state. Pass
|
|
114
|
+
* `parentIcon={null}` to opt out of the default. `leafIcon` defaults to no
|
|
115
|
+
* icon. When `children` are provided, the icon props are ignored — children
|
|
116
|
+
* take over the entire row.
|
|
117
|
+
* - `TreeItem` is the `<button>` row. It handles focus, selection, indent
|
|
118
|
+
* (via the `--tree-indent` CSS variable on `TreeRoot`, default `20px`) and
|
|
119
|
+
* exposes state through data attributes: `data-folder`, `data-expanded`,
|
|
120
|
+
* `data-selected`, `data-drag-target`, `data-search-match`, `data-focused`.
|
|
121
|
+
* - `TreeItemLabel` is the visual zone (background, hover, selected,
|
|
122
|
+
* search-match). It defaults to `item.getItemName()` when no children are
|
|
123
|
+
* provided. It renders no toggle icon on its own — the icon comes from
|
|
124
|
+
* `TreeItem`'s `leafIcon` / `parentIcon`, or from custom children.
|
|
125
|
+
* - `TreeDragLine` is optional; only render it when `dragAndDropFeature` is
|
|
126
|
+
* registered. It positions itself absolutely inside `TreeRoot`.
|
|
127
|
+
* - State binding (`expandedItems`, `selectedItems`, etc.) is controlled via
|
|
128
|
+
* `state` + `setState` on the `useTree` config, or left to internal state
|
|
129
|
+
* with `initialState`.
|
|
130
|
+
*
|
|
131
|
+
* Reference: [headless-tree docs](https://headless-tree.lukasbach.com/docs/)
|
|
174
132
|
*/
|
|
175
|
-
const meta: Meta<typeof
|
|
176
|
-
title: "
|
|
177
|
-
component:
|
|
133
|
+
const meta: Meta<typeof TreeRoot> = {
|
|
134
|
+
title: "Components/Tree",
|
|
135
|
+
component: TreeRoot,
|
|
136
|
+
parameters: { layout: "centered" },
|
|
178
137
|
subcomponents: {
|
|
179
138
|
TreeRoot: TreeRoot as ComponentType<unknown>,
|
|
180
139
|
TreeItem: TreeItem as ComponentType<unknown>,
|
|
181
|
-
TreeItemContent: TreeItemContent as ComponentType<unknown>,
|
|
182
|
-
TreeItemIndicator: TreeItemIndicator as ComponentType<unknown>,
|
|
183
140
|
TreeItemLabel: TreeItemLabel as ComponentType<unknown>,
|
|
184
|
-
|
|
185
|
-
},
|
|
186
|
-
args: {
|
|
187
|
-
items: files,
|
|
188
|
-
selectionMode: "single",
|
|
141
|
+
TreeDragLine: TreeDragLine as ComponentType<unknown>,
|
|
189
142
|
},
|
|
190
143
|
argTypes: {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
table: {
|
|
195
|
-
type: { summary: "'all' | Iterable<Key>" },
|
|
196
|
-
defaultValue: { summary: "—" },
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
defaultSelectedKeys: {
|
|
200
|
-
description: "Las claves inicialmente seleccionadas en la colección",
|
|
201
|
-
control: "object",
|
|
202
|
-
table: {
|
|
203
|
-
type: { summary: "'all' | Iterable<Key>" },
|
|
204
|
-
defaultValue: { summary: "—" },
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
expandedKeys: {
|
|
208
|
-
description: "Las claves actualmente expandidas en la colección",
|
|
209
|
-
control: "object",
|
|
210
|
-
table: {
|
|
211
|
-
type: { summary: "Iterable<Key>" },
|
|
212
|
-
defaultValue: { summary: "—" },
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
defaultExpandedKeys: {
|
|
216
|
-
description: "Las claves inicialmente expandidas en la colección",
|
|
217
|
-
control: "object",
|
|
218
|
-
table: {
|
|
219
|
-
type: { summary: "Iterable<Key>" },
|
|
220
|
-
defaultValue: { summary: "—" },
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
selectionMode: {
|
|
224
|
-
description: "El tipo de selección que está permitida en el árbol",
|
|
225
|
-
control: "select",
|
|
226
|
-
options: ["none", "single", "multiple"],
|
|
227
|
-
table: {
|
|
228
|
-
type: { summary: "'none' | 'single' | 'multiple'" },
|
|
229
|
-
defaultValue: { summary: "'none'" },
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
disabledKeys: {
|
|
233
|
-
description: "Las claves de los elementos que están deshabilitados",
|
|
234
|
-
control: "object",
|
|
235
|
-
table: {
|
|
236
|
-
type: { summary: "Iterable<Key>" },
|
|
237
|
-
defaultValue: { summary: "—" },
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
disallowEmptySelection: {
|
|
241
|
-
description:
|
|
242
|
-
"Si se debe permitir que no haya ningún elemento seleccionado",
|
|
243
|
-
control: "boolean",
|
|
244
|
-
table: {
|
|
245
|
-
type: { summary: "boolean" },
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
onSelectionChange: {
|
|
249
|
-
description: "Manejador llamado cuando cambia la selección",
|
|
250
|
-
table: {
|
|
251
|
-
type: { summary: "(keys: 'all' | Iterable<Key>) => void" },
|
|
252
|
-
defaultValue: { summary: "—" },
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
onExpandedChange: {
|
|
256
|
-
description: "Manejador llamado cuando cambian los elementos expandidos",
|
|
257
|
-
table: {
|
|
258
|
-
type: { summary: "(keys: Iterable<Key>) => void" },
|
|
259
|
-
defaultValue: { summary: "—" },
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
onAction: {
|
|
263
|
-
description:
|
|
264
|
-
"Manejador llamado cuando un usuario realiza una acción en un elemento. El evento exacto depende del prop selectionBehavior y la modalidad de interacción",
|
|
265
|
-
table: {
|
|
266
|
-
type: { summary: "(key: Key) => void" },
|
|
267
|
-
defaultValue: { summary: "—" },
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
onScroll: {
|
|
271
|
-
description: "Manejador llamado cuando un usuario hace scroll",
|
|
272
|
-
table: {
|
|
273
|
-
type: { summary: "(e: UIEvent<Element>) => void" },
|
|
274
|
-
defaultValue: { summary: "—" },
|
|
275
|
-
},
|
|
276
|
-
},
|
|
144
|
+
children: { control: false },
|
|
145
|
+
tree: { control: false },
|
|
146
|
+
render: { control: false },
|
|
277
147
|
},
|
|
278
148
|
};
|
|
279
149
|
|
|
280
150
|
export default meta;
|
|
281
|
-
type Story = StoryObj<typeof
|
|
151
|
+
type Story = StoryObj<typeof TreeRoot>;
|
|
282
152
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
153
|
+
export const Default: Story = {
|
|
154
|
+
render: () => {
|
|
155
|
+
const tree = useTree<Item>({
|
|
156
|
+
...baseConfig,
|
|
157
|
+
features: [syncDataLoaderFeature, selectionFeature, hotkeysCoreFeature],
|
|
158
|
+
});
|
|
288
159
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
160
|
+
return (
|
|
161
|
+
<TreeRoot tree={tree} className="w-72">
|
|
162
|
+
{tree.getItems().map((item) => (
|
|
163
|
+
<TreeItem key={item.getId()} item={item} />
|
|
164
|
+
))}
|
|
165
|
+
</TreeRoot>
|
|
166
|
+
);
|
|
296
167
|
},
|
|
297
168
|
};
|
|
298
169
|
|
|
299
170
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
171
|
+
* Most real trees show distinct icons for folders and files. The function form
|
|
172
|
+
* of `parentIcon` swaps between open and closed folder icons based on the
|
|
173
|
+
* current expanded state.
|
|
303
174
|
*/
|
|
304
|
-
export const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
175
|
+
export const WithIcons: Story = {
|
|
176
|
+
render: () => {
|
|
177
|
+
const tree = useTree<Item>({
|
|
178
|
+
...baseConfig,
|
|
179
|
+
features: [syncDataLoaderFeature, selectionFeature, hotkeysCoreFeature],
|
|
180
|
+
});
|
|
309
181
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
182
|
+
return (
|
|
183
|
+
<TreeRoot tree={tree} className="w-72">
|
|
184
|
+
{tree.getItems().map((item) => (
|
|
185
|
+
<TreeItem
|
|
186
|
+
key={item.getId()}
|
|
187
|
+
item={item}
|
|
188
|
+
leafIcon={<FileTextIcon className="size-4 text-muted-foreground" />}
|
|
189
|
+
parentIcon={(isExpanded) =>
|
|
190
|
+
isExpanded ? (
|
|
191
|
+
<FolderOpenIcon className="size-4 text-muted-foreground" />
|
|
192
|
+
) : (
|
|
193
|
+
<FolderIcon className="size-4 text-muted-foreground" />
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
/>
|
|
197
|
+
))}
|
|
198
|
+
</TreeRoot>
|
|
199
|
+
);
|
|
322
200
|
},
|
|
323
201
|
};
|
|
324
202
|
|
|
325
203
|
/**
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* `disabledBehavior="selection"` - Solo deshabilita la selección
|
|
330
|
-
*
|
|
331
|
-
* `disabledKeys` - Array de IDs de elementos a deshabilitar
|
|
204
|
+
* `selectionFeature` is multi-select by default. Click to select a single
|
|
205
|
+
* item, `Shift`-click for a range, `Ctrl`/`Cmd`-click to toggle. Read the
|
|
206
|
+
* current selection via `tree.getSelectedItems()`.
|
|
332
207
|
*/
|
|
333
|
-
export const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
};
|
|
208
|
+
export const MultipleSelection: Story = {
|
|
209
|
+
render: () => {
|
|
210
|
+
const tree = useTree<Item>({
|
|
211
|
+
...baseConfig,
|
|
212
|
+
features: [syncDataLoaderFeature, selectionFeature, hotkeysCoreFeature],
|
|
213
|
+
});
|
|
339
214
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
215
|
+
const selected = tree.getSelectedItems();
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div className="flex w-72 flex-col gap-3">
|
|
219
|
+
<div className="flex items-center justify-between text-sm">
|
|
220
|
+
<span className="text-muted-foreground">
|
|
221
|
+
{selected.length} selected
|
|
222
|
+
</span>
|
|
223
|
+
{selected.length > 0 && (
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
onClick={() => tree.setSelectedItems([])}
|
|
227
|
+
className="text-muted-foreground underline-offset-2 hover:underline"
|
|
228
|
+
>
|
|
229
|
+
Clear
|
|
230
|
+
</button>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
<TreeRoot tree={tree}>
|
|
234
|
+
{tree.getItems().map((item) => (
|
|
235
|
+
<TreeItem
|
|
236
|
+
key={item.getId()}
|
|
237
|
+
item={item}
|
|
238
|
+
leafIcon={
|
|
239
|
+
<FileTextIcon className="size-4 text-muted-foreground" />
|
|
240
|
+
}
|
|
241
|
+
parentIcon={
|
|
242
|
+
<FolderIcon className="size-4 text-muted-foreground" />
|
|
243
|
+
}
|
|
244
|
+
/>
|
|
245
|
+
))}
|
|
246
|
+
</TreeRoot>
|
|
247
|
+
</div>
|
|
248
|
+
);
|
|
349
249
|
},
|
|
350
250
|
};
|
|
351
251
|
|
|
352
252
|
/**
|
|
353
|
-
*
|
|
354
|
-
*
|
|
253
|
+
* Drop the `leafIcon` / `parentIcon` shorthand and pass `children` directly to
|
|
254
|
+
* place action triggers **outside** `TreeItemLabel`. Wrap the trigger in a
|
|
255
|
+
* `stopPropagation` container so clicks on the menu don't activate the row.
|
|
355
256
|
*
|
|
356
|
-
* `
|
|
257
|
+
* Note: `TreeItem` renders as `<button>` (headless-tree requires it for
|
|
258
|
+
* focus/keyboard), so the nested `Menu` trigger must be a non-button element
|
|
259
|
+
* (e.g. a `<span role="button">`) to keep the HTML valid.
|
|
357
260
|
*/
|
|
358
|
-
export const
|
|
359
|
-
|
|
360
|
-
|
|
261
|
+
export const WithActions: Story = {
|
|
262
|
+
render: () => {
|
|
263
|
+
const tree = useTree<Item>({
|
|
264
|
+
...baseConfig,
|
|
265
|
+
features: [syncDataLoaderFeature, selectionFeature, hotkeysCoreFeature],
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<TreeRoot tree={tree} className="w-80">
|
|
270
|
+
{tree.getItems().map((item) => (
|
|
271
|
+
<TreeItem
|
|
272
|
+
key={item.getId()}
|
|
273
|
+
item={item}
|
|
274
|
+
className="group/row flex items-center"
|
|
275
|
+
>
|
|
276
|
+
<TreeItemLabel className="flex-1">
|
|
277
|
+
{item.isFolder() ? (
|
|
278
|
+
<FolderIcon className="size-4 text-muted-foreground" />
|
|
279
|
+
) : (
|
|
280
|
+
<FileTextIcon className="size-4 text-muted-foreground" />
|
|
281
|
+
)}
|
|
282
|
+
{item.getItemName()}
|
|
283
|
+
</TreeItemLabel>
|
|
284
|
+
<span
|
|
285
|
+
onClick={(event) => event.stopPropagation()}
|
|
286
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
287
|
+
className="opacity-0 transition-opacity group-hover/row:opacity-100"
|
|
288
|
+
>
|
|
289
|
+
<Menu
|
|
290
|
+
trigger={
|
|
291
|
+
<span
|
|
292
|
+
role="button"
|
|
293
|
+
tabIndex={-1}
|
|
294
|
+
className="flex rounded-sm p-1 hover:bg-accent"
|
|
295
|
+
>
|
|
296
|
+
<MoreHorizontalIcon className="size-4 text-muted-foreground" />
|
|
297
|
+
</span>
|
|
298
|
+
}
|
|
299
|
+
items={[
|
|
300
|
+
{
|
|
301
|
+
type: "item",
|
|
302
|
+
label: "Rename",
|
|
303
|
+
onSelect: () => console.log("Rename", item.getItemName()),
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
type: "item",
|
|
307
|
+
label: "Delete",
|
|
308
|
+
onSelect: () => console.log("Delete", item.getItemName()),
|
|
309
|
+
},
|
|
310
|
+
]}
|
|
311
|
+
/>
|
|
312
|
+
</span>
|
|
313
|
+
</TreeItem>
|
|
314
|
+
))}
|
|
315
|
+
</TreeRoot>
|
|
316
|
+
);
|
|
361
317
|
},
|
|
362
318
|
};
|
|
363
319
|
|
|
364
320
|
/**
|
|
365
|
-
*
|
|
321
|
+
* Drag-and-drop with reordering. `createOnDropHandler` (from
|
|
322
|
+
* `@headless-tree/core`) writes the reordered child ids back to your data
|
|
323
|
+
* source — set `children` on the parent item and the tree rebuilds itself.
|
|
324
|
+
* `TreeDragLine` renders the visual indicator and must live inside `TreeRoot`
|
|
325
|
+
* (it positions absolutely against the root).
|
|
326
|
+
*
|
|
327
|
+
* Features needed: `dragAndDropFeature` (mouse) and
|
|
328
|
+
* `keyboardDragAndDropFeature` (keyboard accessibility).
|
|
366
329
|
*
|
|
367
|
-
*
|
|
330
|
+
* Reference: [headless-tree drag-and-drop docs](https://headless-tree.lukasbach.com/dnd/overview)
|
|
368
331
|
*/
|
|
369
|
-
export const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
defaultSelectedKeys: ["4"],
|
|
373
|
-
},
|
|
374
|
-
};
|
|
332
|
+
export const DragAndDrop: Story = {
|
|
333
|
+
render: () => {
|
|
334
|
+
const [data] = useState(() => structuredClone(items));
|
|
375
335
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
onExpandedChange: (keys: any) =>
|
|
387
|
-
toastManager.add({
|
|
388
|
-
variant: "success",
|
|
389
|
-
description: `Expanded: ${Array.from(keys)}`,
|
|
390
|
-
}),
|
|
391
|
-
onSelectionChange: (keys: any) =>
|
|
392
|
-
toastManager.add({
|
|
393
|
-
variant: "success",
|
|
394
|
-
description: `Selected: ${Array.from(keys)}`,
|
|
336
|
+
const tree = useTree<Item>({
|
|
337
|
+
...baseConfig,
|
|
338
|
+
dataLoader: {
|
|
339
|
+
getItem: (itemId: string) => data[itemId],
|
|
340
|
+
getChildren: (itemId: string) => data[itemId]?.children ?? [],
|
|
341
|
+
},
|
|
342
|
+
canReorder: true,
|
|
343
|
+
onDrop: createOnDropHandler<Item>((parent, newChildrenIds) => {
|
|
344
|
+
const node = data[parent.getId()];
|
|
345
|
+
if (node) node.children = newChildrenIds;
|
|
395
346
|
}),
|
|
396
|
-
|
|
397
|
-
|
|
347
|
+
features: [
|
|
348
|
+
syncDataLoaderFeature,
|
|
349
|
+
selectionFeature,
|
|
350
|
+
hotkeysCoreFeature,
|
|
351
|
+
dragAndDropFeature,
|
|
352
|
+
keyboardDragAndDropFeature,
|
|
353
|
+
],
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<TreeRoot tree={tree} className="relative w-72">
|
|
358
|
+
{tree.getItems().map((item) => (
|
|
359
|
+
<TreeItem
|
|
360
|
+
key={item.getId()}
|
|
361
|
+
item={item}
|
|
362
|
+
leafIcon={<FileTextIcon className="size-4 text-muted-foreground" />}
|
|
363
|
+
parentIcon={(isExpanded) =>
|
|
364
|
+
isExpanded ? (
|
|
365
|
+
<FolderOpenIcon className="size-4 text-muted-foreground" />
|
|
366
|
+
) : (
|
|
367
|
+
<FolderIcon className="size-4 text-muted-foreground" />
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
/>
|
|
371
|
+
))}
|
|
372
|
+
<TreeDragLine />
|
|
373
|
+
</TreeRoot>
|
|
374
|
+
);
|
|
398
375
|
},
|
|
399
|
-
decorators: [
|
|
400
|
-
(Story: any) => (
|
|
401
|
-
<ToastProvider toastManager={toastManager}>
|
|
402
|
-
<Story />
|
|
403
|
-
</ToastProvider>
|
|
404
|
-
),
|
|
405
|
-
],
|
|
406
376
|
};
|
|
407
377
|
|
|
408
378
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
* const Estructura = () => {
|
|
414
|
-
* return (
|
|
415
|
-
* <TreeRoot>
|
|
416
|
-
* <TreeItem>
|
|
417
|
-
* <TreeItemContent>
|
|
418
|
-
* <TreeItemIndicator />
|
|
419
|
-
* <TreeItemLabel>Item Label</TreeItemLabel>
|
|
420
|
-
* </TreeItemContent>
|
|
421
|
-
* <Collection>
|
|
422
|
-
* Child items
|
|
423
|
-
* </Collection>
|
|
424
|
-
* </TreeItem>
|
|
425
|
-
* </TreeRoot>
|
|
426
|
-
* )
|
|
427
|
-
* }
|
|
428
|
-
* ```
|
|
429
|
-
*
|
|
430
|
-
* Los componentes primitivos del Tree son:
|
|
431
|
-
*
|
|
432
|
-
* - `TreeRoot`: Contenedor principal que maneja la selección y expansión
|
|
433
|
-
* - `TreeItem`: Representa cada elemento del árbol
|
|
434
|
-
* - `TreeItemContent`: Contenedor del contenido del item
|
|
435
|
-
* - `TreeItemIndicator`: Indicador de expansión (chevron)
|
|
436
|
-
* - `TreeItemLabel`: Etiqueta del item
|
|
437
|
-
* - `Collection`: Contenedor de items hijos que permite manejo con teclado
|
|
379
|
+
* Drive the search through `tree.setSearch(value)` and read the current
|
|
380
|
+
* query via `tree.getSearchValue()`. Matching rows expose
|
|
381
|
+
* `item.isMatchingSearch()` and receive `data-search-match="true"` on
|
|
382
|
+
* `TreeItem`, which `TreeItemLabel` styles automatically.
|
|
438
383
|
*/
|
|
439
|
-
|
|
440
|
-
export const Primitive: Story = {
|
|
384
|
+
export const Search: Story = {
|
|
441
385
|
render: () => {
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
);
|
|
456
|
-
};
|
|
386
|
+
const tree = useTree<Item>({
|
|
387
|
+
...baseConfig,
|
|
388
|
+
initialState: {
|
|
389
|
+
expandedItems: ["docs", "src", "components", "lib", "tests"],
|
|
390
|
+
search: "tree",
|
|
391
|
+
},
|
|
392
|
+
features: [
|
|
393
|
+
syncDataLoaderFeature,
|
|
394
|
+
selectionFeature,
|
|
395
|
+
hotkeysCoreFeature,
|
|
396
|
+
searchFeature,
|
|
397
|
+
],
|
|
398
|
+
});
|
|
457
399
|
|
|
458
400
|
return (
|
|
459
|
-
<
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
401
|
+
<div className="flex w-72 flex-col gap-3">
|
|
402
|
+
<Input
|
|
403
|
+
placeholder="Search…"
|
|
404
|
+
value={tree.getSearchValue()}
|
|
405
|
+
onChange={(value) => tree.setSearch(value || null)}
|
|
406
|
+
/>
|
|
407
|
+
<TreeRoot tree={tree}>
|
|
408
|
+
{tree.getItems().map((item) => (
|
|
409
|
+
<TreeItem
|
|
410
|
+
key={item.getId()}
|
|
411
|
+
item={item}
|
|
412
|
+
leafIcon={
|
|
413
|
+
<FileTextIcon className="size-4 text-muted-foreground" />
|
|
414
|
+
}
|
|
415
|
+
parentIcon={
|
|
416
|
+
<FolderIcon className="size-4 text-muted-foreground" />
|
|
417
|
+
}
|
|
418
|
+
/>
|
|
419
|
+
))}
|
|
420
|
+
</TreeRoot>
|
|
421
|
+
</div>
|
|
466
422
|
);
|
|
467
423
|
},
|
|
468
424
|
};
|