@exegia/corpora-ui 0.20.0 → 0.22.0
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-lib/components/blocks/auth/auth-flow-atom.d.ts +77 -0
- package/dist-lib/components/blocks/auth/auth-session-atom.d.ts +28 -0
- package/dist-lib/components/blocks/auth/auth-state-type.d.ts +71 -0
- package/dist-lib/components/blocks/auth/auth-state.d.ts +9 -0
- package/dist-lib/components/blocks/auth/use-auth-state.d.ts +52 -0
- package/dist-lib/components/blocks/layout.d.ts +4 -1
- package/dist-lib/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.d.ts +1 -0
- package/dist-lib/components/blocks/nav/sidebar/ai-sidebar-atom.d.ts +217 -0
- package/dist-lib/components/blocks/nav/sidebar/index.d.ts +3 -1
- package/dist-lib/components/blocks/nav/sidebar/sidebar-context.d.ts +3 -0
- package/dist-lib/components/blocks/nav/sidebar/sidebar-row.d.ts +3 -1
- package/dist-lib/components/blocks/nav/sidebar/type.d.ts +109 -29
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar-state.d.ts +29 -0
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +6 -1
- package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +1 -1
- package/dist-lib/components/blocks/profile/index.d.ts +7 -0
- package/dist-lib/components/blocks/profile/profile-card-atom.d.ts +68 -0
- package/dist-lib/components/blocks/profile/profile-card-block.d.ts +28 -39
- package/dist-lib/components/blocks/profile/type.d.ts +80 -0
- package/dist-lib/components/blocks/profile/use-profile-card-state.d.ts +24 -0
- package/dist-lib/components/blocks/profile/use-profile-card.d.ts +34 -0
- package/dist-lib/components/blocks/shell/__tests__/shell-fit-atom.test.d.ts +1 -0
- package/dist-lib/components/blocks/shell/__tests__/shell-metrics.test.d.ts +1 -0
- package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +1 -1
- package/dist-lib/components/blocks/shell/animated-panel.d.ts +1 -1
- package/dist-lib/components/blocks/shell/index.d.ts +4 -12
- package/dist-lib/components/blocks/shell/shell-fit-atom.d.ts +68 -0
- package/dist-lib/components/blocks/shell/shell-metrics.d.ts +54 -0
- package/dist-lib/components/blocks/shell/type.d.ts +115 -3
- package/dist-lib/components/blocks/shell/use-shell-fit-state.d.ts +25 -0
- package/dist-lib/components/blocks/shell/use-shell-fit.d.ts +14 -0
- package/dist-lib/components/blocks/shell/use-shell-panels.d.ts +12 -1
- package/dist-lib/components/blocks/shell/utils.d.ts +32 -0
- package/dist-lib/components/composed/__tests__/logo.test.d.ts +1 -0
- package/dist-lib/components/composed/logo.d.ts +34 -0
- package/dist-lib/components/composed/tree/__tests__/tree-atom.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/constants.d.ts +4 -4
- package/dist-lib/components/composed/tree/index.d.ts +3 -1
- package/dist-lib/components/composed/tree/tree-atom.d.ts +160 -0
- package/dist-lib/components/composed/tree/tree-node.d.ts +6 -1
- package/dist-lib/components/composed/tree/type.d.ts +116 -18
- package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +18 -7
- package/dist-lib/components/composed/tree/use-tree-state.d.ts +25 -0
- package/dist-lib/components/composed/tree/use-tree.d.ts +4 -0
- package/dist-lib/components/composed/user-avatar.d.ts +5 -28
- package/dist-lib/components/user-avatar/__tests__/user-avatar.test.d.ts +1 -0
- package/dist-lib/components/user-avatar/component.d.ts +1 -8
- package/dist-lib/components/user-avatar/index.d.ts +5 -0
- package/dist-lib/components/user-avatar/presence-badge.d.ts +16 -0
- package/dist-lib/components/user-avatar/type.d.ts +53 -0
- package/dist-lib/components/user-avatar/use-user-avatar-state.d.ts +26 -0
- package/dist-lib/components/user-avatar/use-user-avatar.d.ts +37 -0
- package/dist-lib/components/user-avatar/user-avatar-atom.d.ts +64 -0
- package/dist-lib/components/user-avatar/utils.d.ts +22 -0
- package/dist-lib/index.d.ts +6 -3
- package/dist-lib/index.js +3732 -2670
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/state/exegia-provider.d.ts +51 -0
- package/dist-lib/state/index.d.ts +4 -0
- package/dist-lib/state/store.d.ts +17 -0
- package/package.json +15 -12
- package/src/components/blocks/auth/__tests__/auth-state-atom.test.tsx +247 -0
- package/src/components/blocks/auth/auth-flow-atom.ts +238 -0
- package/src/components/blocks/auth/auth-session-atom.ts +82 -0
- package/src/components/blocks/auth/auth-state-type.ts +97 -0
- package/src/components/blocks/auth/auth-state.ts +52 -0
- package/src/components/blocks/auth/use-auth-state.ts +127 -0
- package/src/components/blocks/layout.ts +30 -2
- package/src/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.tsx +325 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar-atom.ts +698 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +48 -42
- package/src/components/blocks/nav/sidebar/index.ts +53 -1
- package/src/components/blocks/nav/sidebar/sidebar-context.ts +15 -0
- package/src/components/blocks/nav/sidebar/sidebar-row.tsx +119 -50
- package/src/components/blocks/nav/sidebar/type.ts +125 -30
- package/src/components/blocks/nav/sidebar/use-ai-sidebar-state.ts +123 -0
- package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +301 -248
- package/src/components/blocks/nav/sidebar/utils.ts +1 -1
- package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +194 -0
- package/src/components/blocks/profile/index.ts +28 -0
- package/src/components/blocks/profile/profile-card-atom.ts +247 -0
- package/src/components/blocks/profile/profile-card-block.tsx +153 -74
- package/src/components/blocks/profile/type.ts +95 -0
- package/src/components/blocks/profile/use-profile-card-state.ts +67 -0
- package/src/components/blocks/profile/use-profile-card.ts +126 -0
- package/src/components/blocks/shell/__tests__/shell-fit-atom.test.tsx +360 -0
- package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +192 -3
- package/src/components/blocks/shell/__tests__/shell-metrics.test.ts +108 -0
- package/src/components/blocks/shell/animated-panel-inset.tsx +5 -1
- package/src/components/blocks/shell/animated-panel-provider.tsx +77 -11
- package/src/components/blocks/shell/animated-panel-trigger.tsx +4 -0
- package/src/components/blocks/shell/animated-panel.tsx +126 -29
- package/src/components/blocks/shell/index.ts +18 -13
- package/src/components/blocks/shell/shell-fit-atom.ts +243 -0
- package/src/components/blocks/shell/shell-layout.tsx +55 -53
- package/src/components/blocks/shell/shell-metrics.ts +79 -0
- package/src/components/blocks/shell/type.ts +130 -3
- package/src/components/blocks/shell/use-shell-fit-state.ts +49 -0
- package/src/components/blocks/shell/use-shell-fit.ts +135 -0
- package/src/components/blocks/shell/use-shell-panels.ts +57 -4
- package/src/components/blocks/shell/utils.ts +44 -4
- package/src/components/composed/__tests__/logo.test.tsx +60 -0
- package/src/components/composed/logo.tsx +159 -0
- package/src/components/composed/tree/CLAUDE.md +132 -0
- package/src/components/composed/tree/__tests__/tree-atom.test.tsx +217 -0
- package/src/components/composed/tree/__tests__/tree.test.tsx +162 -37
- package/src/components/composed/tree/__tests__/use-tree.test.tsx +2 -1
- package/src/components/composed/tree/constants.ts +4 -4
- package/src/components/composed/tree/index.ts +38 -0
- package/src/components/composed/tree/tree-atom.ts +590 -0
- package/src/components/composed/tree/tree-node.tsx +117 -78
- package/src/components/composed/tree/tree.tsx +24 -16
- package/src/components/composed/tree/type.ts +125 -15
- package/src/components/composed/tree/use-tree-dnd.ts +82 -51
- package/src/components/composed/tree/use-tree-state.ts +105 -0
- package/src/components/composed/tree/use-tree.ts +185 -184
- package/src/components/composed/user-avatar.tsx +11 -99
- package/src/components/user-avatar/__tests__/user-avatar.test.tsx +286 -0
- package/src/components/user-avatar/component.tsx +151 -22
- package/src/components/user-avatar/fallback.tsx +3 -1
- package/src/components/user-avatar/index.ts +19 -0
- package/src/components/user-avatar/presence-badge.tsx +56 -0
- package/src/components/user-avatar/type.ts +58 -0
- package/src/components/user-avatar/use-user-avatar-state.ts +60 -0
- package/src/components/user-avatar/use-user-avatar.ts +180 -0
- package/src/components/user-avatar/user-avatar-atom.ts +218 -0
- package/src/components/user-avatar/utils.ts +94 -0
- package/src/index.ts +35 -3
- package/src/state/exegia-provider.tsx +79 -0
- package/src/state/index.ts +4 -0
- package/src/state/store.ts +19 -0
- package/dist-lib/components/blocks/nav/sidebar-block.d.ts +0 -14
- package/dist-lib/components/blocks/nav/sidebar-nav-row.d.ts +0 -3
- package/dist-lib/components/blocks/nav/types.d.ts +0 -60
- package/dist-lib/components/blocks/nav/utils.d.ts +0 -1
- package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +0 -3
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +0 -3
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +0 -2
- package/src/components/blocks/nav/__tests__/sidebar-block.test.tsx +0 -125
- package/src/components/blocks/nav/sidebar-block.tsx +0 -142
- package/src/components/blocks/nav/sidebar-nav-row.tsx +0 -78
- package/src/components/blocks/nav/types.ts +0 -65
- package/src/components/blocks/nav/utils.ts +0 -4
- package/src/components/blocks/shell/animated-sidebar-content.tsx +0 -19
- package/src/components/blocks/shell/animated-sidebar-footer.tsx +0 -19
- package/src/components/blocks/shell/animated-sidebar-group-content.tsx +0 -16
- package/src/components/blocks/shell/animated-sidebar-group-label.tsx +0 -29
- package/src/components/blocks/shell/animated-sidebar-group.tsx +0 -16
- package/src/components/blocks/shell/animated-sidebar-header.tsx +0 -16
- package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +0 -151
- package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +0 -20
- package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +0 -85
- package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +0 -19
- package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +0 -44
- package/src/components/blocks/shell/animated-sidebar-menu.tsx +0 -26
- /package/dist-lib/components/blocks/{nav/__tests__/sidebar-block.test.d.ts → auth/__tests__/auth-state-atom.test.d.ts} +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { LucideChevronLeft, LucideChevronRight } from "lucide-react"
|
|
4
4
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react"
|
|
5
5
|
import * as React from "react"
|
|
6
|
+
import { useAtomValue, useSetAtom } from "jotai"
|
|
6
7
|
|
|
7
8
|
import { cn } from "@/lib/utils"
|
|
8
9
|
import {
|
|
@@ -18,9 +19,27 @@ import {
|
|
|
18
19
|
TREE_MICRO_TRANSITION,
|
|
19
20
|
TREE_ROW_VARIANTS,
|
|
20
21
|
} from "./constants"
|
|
22
|
+
import {
|
|
23
|
+
cancelTreeRenameAtom,
|
|
24
|
+
renameTreeNodeAtom,
|
|
25
|
+
selectTreeNodeAtom,
|
|
26
|
+
startTreeRenameAtom,
|
|
27
|
+
toggleTreeNodeAtom,
|
|
28
|
+
treeCanRenameAtom,
|
|
29
|
+
treeCollapsedAtom,
|
|
30
|
+
treeNodeActiveAtom,
|
|
31
|
+
treeNodeDraggingAtom,
|
|
32
|
+
treeNodeDropAtom,
|
|
33
|
+
treeNodeExpandedAtom,
|
|
34
|
+
treeNodeRenamingAtom,
|
|
35
|
+
treeSectionedAtom,
|
|
36
|
+
treeSoundAtom,
|
|
37
|
+
treeVariantAtom,
|
|
38
|
+
} from "./tree-atom"
|
|
21
39
|
import { useTreeContext } from "./tree-context"
|
|
22
40
|
import type { TreeNode } from "./type"
|
|
23
41
|
import { EASE_OUT } from "@/lib/ease.ts"
|
|
42
|
+
import { Button } from "@/components/ui/button"
|
|
24
43
|
|
|
25
44
|
/** How a row behaves, resolved from variant + depth + shape. */
|
|
26
45
|
type RowKind =
|
|
@@ -48,71 +67,94 @@ function rowKindOf(
|
|
|
48
67
|
return variant === "toc" ? "link" : "toggle"
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
const {
|
|
70
|
+
function TreeRowImpl({ node, depth }: TreeRowProps): React.ReactElement {
|
|
71
|
+
const { treeId, renderTrailing, dnd } = useTreeContext()
|
|
53
72
|
const reduce = useReducedMotion()
|
|
54
|
-
|
|
73
|
+
|
|
74
|
+
// One subscription per field, per node. A row re-renders when its own
|
|
75
|
+
// branch, selection, rename or drag state changes and sits still while a
|
|
76
|
+
// sibling's does — reading the whole controller off context instead
|
|
77
|
+
// re-rendered every row in the tree on every toggle.
|
|
78
|
+
const variant = useAtomValue(treeVariantAtom(treeId))
|
|
79
|
+
const sectioned = useAtomValue(treeSectionedAtom(treeId))
|
|
80
|
+
const collapsed = useAtomValue(treeCollapsedAtom(treeId))
|
|
81
|
+
const canRename = useAtomValue(treeCanRenameAtom(treeId))
|
|
82
|
+
const sound = useAtomValue(treeSoundAtom(treeId))
|
|
83
|
+
const open = useAtomValue(treeNodeExpandedAtom(treeId, node.id))
|
|
84
|
+
const active = useAtomValue(treeNodeActiveAtom(treeId, node.id))
|
|
85
|
+
const renaming = useAtomValue(treeNodeRenamingAtom(treeId, node.id))
|
|
86
|
+
const dragging = useAtomValue(treeNodeDraggingAtom(treeId, node.id))
|
|
87
|
+
const drop = useAtomValue(treeNodeDropAtom(treeId, node.id))
|
|
88
|
+
|
|
89
|
+
const select = useSetAtom(selectTreeNodeAtom(treeId))
|
|
90
|
+
const toggleExpanded = useSetAtom(toggleTreeNodeAtom(treeId))
|
|
91
|
+
const startRename = useSetAtom(startTreeRenameAtom(treeId))
|
|
92
|
+
const cancelRename = useSetAtom(cancelTreeRenameAtom(treeId))
|
|
93
|
+
const rename = useSetAtom(renameTreeNodeAtom(treeId))
|
|
94
|
+
|
|
95
|
+
const kind = rowKindOf(node, depth, variant, sectioned)
|
|
55
96
|
|
|
56
97
|
// The sidebar rail is single-level by contract — nested children are
|
|
57
98
|
// ignored rather than rendered somewhere misleading.
|
|
58
|
-
const children =
|
|
99
|
+
const children = variant === "sidebar" ? [] : (node.children ?? [])
|
|
59
100
|
const canExpand =
|
|
60
|
-
kind !== "link" || (
|
|
61
|
-
const open = ctx.isExpanded(node.id)
|
|
62
|
-
const active = ctx.activeId === node.id
|
|
63
|
-
const renaming = ctx.renamingId === node.id
|
|
101
|
+
kind !== "link" || (variant === "toc" && children.length > 0)
|
|
64
102
|
|
|
65
|
-
const
|
|
66
|
-
const dragging = dnd.draggedId === node.id
|
|
67
|
-
const drop = dnd.dropTarget?.id === node.id ? dnd.dropTarget.position : null
|
|
103
|
+
const files = variant === "files"
|
|
68
104
|
|
|
69
|
-
// toc rows below the route level
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
105
|
+
// toc rows below the route level are in-page anchors: they scroll to the
|
|
106
|
+
// heading sharing their id (an explicit `#hash` href wins). Rows are
|
|
107
|
+
// buttons, so the anchor default is reproduced by setting location.hash
|
|
108
|
+
// after `select` — the same order the old <a> gave (handlers first, then
|
|
109
|
+
// the browser's jump), with the same :target/history semantics.
|
|
110
|
+
const anchorHash =
|
|
111
|
+
variant === "toc" && kind === "link" && !node.disabled
|
|
112
|
+
? node.href?.startsWith("#")
|
|
113
|
+
? node.href
|
|
114
|
+
: depth > 0 && !node.href
|
|
115
|
+
? `#${node.id}`
|
|
116
|
+
: undefined
|
|
75
117
|
: undefined
|
|
76
118
|
|
|
77
|
-
const files = ctx.variant === "files"
|
|
78
|
-
const collapsed = ctx.collapsed
|
|
79
|
-
|
|
80
119
|
function handlePress(event: React.MouseEvent) {
|
|
81
120
|
if (node.disabled) {
|
|
82
121
|
event.preventDefault()
|
|
83
122
|
return
|
|
84
123
|
}
|
|
85
124
|
if (kind === "link") {
|
|
86
|
-
|
|
125
|
+
select(node.id)
|
|
126
|
+
if (anchorHash !== undefined && typeof window !== "undefined") {
|
|
127
|
+
window.location.hash = anchorHash
|
|
128
|
+
}
|
|
87
129
|
return
|
|
88
130
|
}
|
|
89
|
-
|
|
131
|
+
toggleExpanded(node.id)
|
|
90
132
|
}
|
|
91
133
|
|
|
92
|
-
function
|
|
93
|
-
if (!
|
|
134
|
+
function handleStartRename(event: React.MouseEvent) {
|
|
135
|
+
if (!canRename || node.disabled) return
|
|
94
136
|
event.preventDefault()
|
|
95
|
-
|
|
137
|
+
startRename(node.id)
|
|
96
138
|
}
|
|
97
139
|
|
|
98
140
|
const label = renaming ? (
|
|
99
141
|
<input
|
|
100
142
|
autoFocus
|
|
101
143
|
className={cn(
|
|
102
|
-
"h-5 min-w-0 flex-1 rounded-sm border
|
|
103
|
-
"text-[length:inherit] text-foreground outline-none focus-visible:ring-
|
|
144
|
+
"h-5 min-w-0 flex-1 rounded-sm border-none bg-transparent px-1",
|
|
145
|
+
"text-[length:inherit] text-foreground outline-none focus-visible:ring-0"
|
|
104
146
|
)}
|
|
105
147
|
data-slot="tree-rename-input"
|
|
106
148
|
defaultValue={node.label}
|
|
107
|
-
onBlur={(event) =>
|
|
149
|
+
onBlur={(event) => rename(node.id, event.currentTarget.value)}
|
|
108
150
|
onClick={(event) => event.stopPropagation()}
|
|
109
151
|
onKeyDown={(event) => {
|
|
110
152
|
event.stopPropagation()
|
|
111
|
-
if (event.key === "Enter")
|
|
112
|
-
if (event.key === "Escape")
|
|
153
|
+
if (event.key === "Enter") rename(node.id, event.currentTarget.value)
|
|
154
|
+
if (event.key === "Escape") cancelRename()
|
|
113
155
|
}}
|
|
114
156
|
/>
|
|
115
|
-
) :
|
|
157
|
+
) : variant === "sidebar" ? (
|
|
116
158
|
// The rail label folds away with the rail instead of unmounting rows.
|
|
117
159
|
<AnimatePresence initial={false} mode="wait">
|
|
118
160
|
<motion.span
|
|
@@ -176,7 +218,7 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
176
218
|
) : (
|
|
177
219
|
<span
|
|
178
220
|
className="min-w-0 flex-1 truncate"
|
|
179
|
-
onDoubleClick={
|
|
221
|
+
onDoubleClick={canRename ? handleStartRename : undefined}
|
|
180
222
|
>
|
|
181
223
|
{node.label}
|
|
182
224
|
</span>
|
|
@@ -184,31 +226,31 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
184
226
|
|
|
185
227
|
const chevron = canExpand ? (
|
|
186
228
|
<motion.span
|
|
187
|
-
animate={{ rotate: open ? (
|
|
229
|
+
animate={{ rotate: open ? (variant === "navigation" ? -90 : 90) : 0 }}
|
|
188
230
|
aria-hidden
|
|
189
231
|
className="flex size-4 shrink-0 items-center justify-center text-muted-foreground/70"
|
|
190
232
|
transition={reduce ? { duration: 0 } : TREE_MICRO_TRANSITION}
|
|
191
233
|
>
|
|
192
|
-
{
|
|
234
|
+
{variant === "sidebar" && (
|
|
193
235
|
<LucideChevronLeft className="size-3.5 stroke-3" />
|
|
194
236
|
)}
|
|
195
|
-
{
|
|
237
|
+
{variant === "toc" && (
|
|
196
238
|
<LucideChevronRight className="size-3.5 stroke-3" />
|
|
197
239
|
)}
|
|
198
|
-
{
|
|
240
|
+
{variant === "navigation" && (
|
|
199
241
|
<LucideChevronLeft className="size-3.5 stroke-3" />
|
|
200
242
|
)}
|
|
201
243
|
</motion.span>
|
|
202
244
|
) : null
|
|
203
245
|
|
|
204
246
|
// The trailing slot is consumer-rendered and usually holds buttons, so it
|
|
205
|
-
// cannot live inside the row —
|
|
206
|
-
//
|
|
207
|
-
//
|
|
247
|
+
// cannot live inside the row — every row is itself a <button>, and a
|
|
248
|
+
// button nesting a button is invalid HTML. It sits beside the row
|
|
249
|
+
// instead, overlaid like the toc toggle.
|
|
208
250
|
const showTrailing = files && !!renderTrailing && !renaming
|
|
209
251
|
|
|
210
252
|
const rowClassName = cn(
|
|
211
|
-
"group/row relative flex w-full min-w-0 items-center gap-2 rounded-md px-2",
|
|
253
|
+
"group/row relative flex w-full min-w-0 cursor-pointer items-center gap-2 rounded-md px-2",
|
|
212
254
|
"text-left text-muted-foreground transition-colors duration-150 outline-none",
|
|
213
255
|
"hover:bg-accent hover:text-accent-foreground",
|
|
214
256
|
"focus-visible:ring-2 focus-visible:ring-ring",
|
|
@@ -217,9 +259,9 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
217
259
|
active && "bg-accent font-medium text-accent-foreground",
|
|
218
260
|
dragging && "opacity-50",
|
|
219
261
|
drop === "inside" && "bg-accent/60 ring-1 ring-primary/50 ring-inset",
|
|
220
|
-
|
|
262
|
+
variant === "sidebar" && collapsed && "justify-center px-0",
|
|
221
263
|
// Room for the overlay expand toggle on toc parent rows.
|
|
222
|
-
|
|
264
|
+
variant === "toc" && kind === "link" && children.length > 0 && "pr-7",
|
|
223
265
|
// …and for the overlay trailing actions on files rows. Sized for one
|
|
224
266
|
// icon button plus its inset; a wider slot clips the truncated label,
|
|
225
267
|
// which is why the prop is documented as row actions, not free content.
|
|
@@ -232,7 +274,7 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
232
274
|
"data-branch": canExpand ? "" : undefined,
|
|
233
275
|
"data-expanded": canExpand && open ? "" : undefined,
|
|
234
276
|
"data-active": active ? "" : undefined,
|
|
235
|
-
"data-cuelume-press":
|
|
277
|
+
"data-cuelume-press": sound ? "" : undefined,
|
|
236
278
|
draggable: dnd.enabled && !renaming ? true : undefined,
|
|
237
279
|
onDragStart: dnd.enabled
|
|
238
280
|
? (event: React.DragEvent) => dnd.onRowDragStart(event, node.id)
|
|
@@ -261,12 +303,11 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
261
303
|
</span>
|
|
262
304
|
)}
|
|
263
305
|
{label}
|
|
264
|
-
{node.badge !== undefined &&
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
)}
|
|
306
|
+
{node.badge !== undefined && !(variant === "sidebar" && collapsed) && (
|
|
307
|
+
<span className="ml-auto shrink-0 text-xs text-muted-foreground/80">
|
|
308
|
+
{node.badge}
|
|
309
|
+
</span>
|
|
310
|
+
)}
|
|
270
311
|
{/* nav toggle chevrons trail like the sidebar block; files lead;
|
|
271
312
|
toc link rows get a separate overlay toggle instead. */}
|
|
272
313
|
{kind === "toggle" && !files && (
|
|
@@ -289,10 +330,10 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
289
330
|
{...rowInteractionProps}
|
|
290
331
|
aria-expanded={open}
|
|
291
332
|
className={cn(
|
|
292
|
-
"group/row flex w-full items-center gap-1 rounded-md px-2
|
|
333
|
+
"group/row flex w-full cursor-pointer items-center gap-1 rounded-md px-2 py-1",
|
|
293
334
|
"text-xs font-medium tracking-wide text-muted-foreground/80 uppercase",
|
|
294
335
|
"transition-colors duration-150 outline-none hover:text-foreground",
|
|
295
|
-
"focus-visible:ring-
|
|
336
|
+
"focus-visible:ring-0 focus-visible:ring-ring"
|
|
296
337
|
)}
|
|
297
338
|
onClick={handlePress}
|
|
298
339
|
type="button"
|
|
@@ -301,38 +342,30 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
301
342
|
{chevron}
|
|
302
343
|
</button>
|
|
303
344
|
)
|
|
304
|
-
} else if (href !== undefined) {
|
|
305
|
-
row = (
|
|
306
|
-
<a
|
|
307
|
-
{...rowInteractionProps}
|
|
308
|
-
aria-current={active ? "page" : undefined}
|
|
309
|
-
aria-label={
|
|
310
|
-
ctx.variant === "sidebar" && collapsed ? node.label : undefined
|
|
311
|
-
}
|
|
312
|
-
className={rowClassName}
|
|
313
|
-
href={href}
|
|
314
|
-
onClick={handlePress}
|
|
315
|
-
target={node.target}
|
|
316
|
-
>
|
|
317
|
-
{content}
|
|
318
|
-
</a>
|
|
319
|
-
)
|
|
320
345
|
} else {
|
|
346
|
+
// Every non-section row is the shared Button — link rows included. An
|
|
347
|
+
// `href` no longer renders an anchor: navigation runs through
|
|
348
|
+
// `selectTreeNodeAtom` (onSelect → onNavigate), and the consumer routes
|
|
349
|
+
// from the node it receives. `sound` is forwarded so Button's own
|
|
350
|
+
// press/release cues follow the tree's setting rather than its default.
|
|
321
351
|
row = (
|
|
322
|
-
<
|
|
352
|
+
<Button
|
|
323
353
|
{...rowInteractionProps}
|
|
354
|
+
variant={variant === "sidebar" && active ? "secondary" : "ghost"}
|
|
324
355
|
aria-current={active ? "page" : undefined}
|
|
325
356
|
aria-expanded={kind === "toggle" ? open : undefined}
|
|
326
|
-
aria-label={
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
357
|
+
aria-label={variant === "sidebar" && collapsed ? node.label : undefined}
|
|
358
|
+
className={cn(
|
|
359
|
+
"transform cursor-pointer! justify-start transition-transform ease-smooth-out",
|
|
360
|
+
variant === "sidebar" && collapsed ? "h-10! rounded-xl!" : undefined,
|
|
361
|
+
rowClassName
|
|
362
|
+
)}
|
|
330
363
|
disabled={node.disabled}
|
|
331
364
|
onClick={handlePress}
|
|
332
|
-
|
|
365
|
+
sound={sound}
|
|
333
366
|
>
|
|
334
367
|
{content}
|
|
335
|
-
</
|
|
368
|
+
</Button>
|
|
336
369
|
)
|
|
337
370
|
}
|
|
338
371
|
|
|
@@ -340,7 +373,7 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
340
373
|
// Expanded, the visible label already does, so the tooltip is disabled
|
|
341
374
|
// rather than unmounted: swapping the wrapper in and out with `collapsed`
|
|
342
375
|
// would remount the row and cut the label's fold animation short.
|
|
343
|
-
if (
|
|
376
|
+
if (variant === "sidebar") {
|
|
344
377
|
row = (
|
|
345
378
|
<Tooltip disabled={!collapsed || node.disabled}>
|
|
346
379
|
<TooltipTrigger render={row} />
|
|
@@ -352,18 +385,18 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
352
385
|
// toc parents navigate on the row itself, so their chevron lives beside
|
|
353
386
|
// the row as its own toggle.
|
|
354
387
|
const tocToggle =
|
|
355
|
-
|
|
388
|
+
variant === "toc" && kind === "link" && children.length > 0 ? (
|
|
356
389
|
<button
|
|
357
390
|
aria-expanded={open}
|
|
358
391
|
aria-label={`${open ? "Collapse" : "Expand"} ${node.label}`}
|
|
359
392
|
className={cn(
|
|
360
|
-
"absolute top-1/2 right-1 flex size-5 -translate-y-1/2 items-center",
|
|
393
|
+
"absolute top-1/2 right-1 flex size-5 -translate-y-1/2 cursor-pointer items-center",
|
|
361
394
|
"justify-center rounded-sm text-muted-foreground/70 outline-none",
|
|
362
395
|
"hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
|
|
363
396
|
)}
|
|
364
397
|
onClick={(event) => {
|
|
365
398
|
event.stopPropagation()
|
|
366
|
-
|
|
399
|
+
toggleExpanded(node.id)
|
|
367
400
|
}}
|
|
368
401
|
type="button"
|
|
369
402
|
>
|
|
@@ -436,6 +469,12 @@ export function TreeRow({ node, depth }: TreeRowProps): React.ReactElement {
|
|
|
436
469
|
)
|
|
437
470
|
}
|
|
438
471
|
|
|
472
|
+
/** Rows are memoized so a re-render of the root — a rail measurement, a new
|
|
473
|
+
* `renderTrailing` — does not walk the whole tree. Each row's own state
|
|
474
|
+
* reaches it through its atoms instead. */
|
|
475
|
+
export const TreeRow = React.memo(TreeRowImpl)
|
|
476
|
+
TreeRow.displayName = "TreeRow"
|
|
477
|
+
|
|
439
478
|
/** Insertion indicator above or below a row while a drag hovers it. */
|
|
440
479
|
function TreeDropLine({ side }: { side: "top" | "bottom" }) {
|
|
441
480
|
return (
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
TREE_EASE,
|
|
20
20
|
} from "./constants"
|
|
21
21
|
import { useTree } from "./use-tree"
|
|
22
|
+
import { useTreeDndHandlers } from "./use-tree-dnd"
|
|
22
23
|
import { motion, useReducedMotion } from "motion/react"
|
|
23
24
|
|
|
24
25
|
const DEFAULT_LABELS: Record<TreeController["variant"], string> = {
|
|
@@ -48,14 +49,16 @@ export function Tree(props: TreeProps): React.ReactElement {
|
|
|
48
49
|
// Two components rather than one: hooks may not be called conditionally,
|
|
49
50
|
// and the controller form has no props to build a fallback controller
|
|
50
51
|
// from. Nobody switches a tree between the two forms at runtime.
|
|
51
|
-
return props.tree
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
return props.tree ? (
|
|
53
|
+
<TreeView
|
|
54
|
+
ariaLabel={props.ariaLabel}
|
|
55
|
+
className={props.className}
|
|
56
|
+
renderTrailing={props.renderTrailing}
|
|
57
|
+
tree={props.tree}
|
|
58
|
+
/>
|
|
59
|
+
) : (
|
|
60
|
+
<UncontrolledTree {...props} />
|
|
61
|
+
)
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
/** The props form: builds its own controller and renders through it. */
|
|
@@ -70,6 +73,7 @@ function UncontrolledTree(props: TreeDataProps): React.ReactElement {
|
|
|
70
73
|
className,
|
|
71
74
|
} = props
|
|
72
75
|
const tree = useTree({
|
|
76
|
+
treeId: props.treeId,
|
|
73
77
|
variant,
|
|
74
78
|
items,
|
|
75
79
|
activeId,
|
|
@@ -112,7 +116,7 @@ function TreeView({
|
|
|
112
116
|
// as wide as whatever holds it, which can change on resize.
|
|
113
117
|
//
|
|
114
118
|
// A container with no width of its own sizes to the list — so it shrinks
|
|
115
|
-
// around the
|
|
119
|
+
// around the 40px rail once collapsed and follows the list mid-tween. Both
|
|
116
120
|
// would feed the list's own width back in as its target and pin it. So:
|
|
117
121
|
// no observing while collapsed (the last expanded width stands), and each
|
|
118
122
|
// sample is taken with the list's inline width cleared, so the classes'
|
|
@@ -210,9 +214,13 @@ function TreeView({
|
|
|
210
214
|
}
|
|
211
215
|
}
|
|
212
216
|
|
|
217
|
+
// Rows take the tree's id and the stable drag handlers, never the
|
|
218
|
+
// controller — so this value keeps its identity and a toggle re-renders only
|
|
219
|
+
// the rows whose own atoms changed.
|
|
220
|
+
const dnd = useTreeDndHandlers(tree.treeId)
|
|
213
221
|
const context = React.useMemo<TreeContextValue>(
|
|
214
|
-
() => ({ tree, renderTrailing }),
|
|
215
|
-
[tree, renderTrailing]
|
|
222
|
+
() => ({ treeId: tree.treeId, renderTrailing, dnd }),
|
|
223
|
+
[tree.treeId, renderTrailing, dnd]
|
|
216
224
|
)
|
|
217
225
|
|
|
218
226
|
const label = ariaLabel ?? DEFAULT_LABELS[variant]
|
|
@@ -220,15 +228,15 @@ function TreeView({
|
|
|
220
228
|
<motion.ul
|
|
221
229
|
aria-label={label}
|
|
222
230
|
className={cn(
|
|
223
|
-
"flex min-w-0 flex-col
|
|
231
|
+
"flex h-full min-w-0 flex-col",
|
|
224
232
|
// Resting widths, and the fallback until the rail is measured. The
|
|
225
233
|
// motion target below overrides these with an inline width.
|
|
226
|
-
collapsed ? "w-
|
|
234
|
+
collapsed ? "w-10 gap-y-2!" : "w-full",
|
|
227
235
|
variant === "files" ? "gap-px" : sectioned ? "gap-3" : "gap-0.5"
|
|
228
236
|
)}
|
|
229
237
|
// Both endpoints must be px: motion cannot interpolate a number
|
|
230
|
-
// against "100%", and animating
|
|
231
|
-
// at
|
|
238
|
+
// against "100%", and animating 40 -> "100%" pinned the inline width
|
|
239
|
+
// at 40px, so the rail collapsed once and never reopened. The
|
|
232
240
|
// expanded target is the rail's own measured width.
|
|
233
241
|
initial={false}
|
|
234
242
|
animate={railTarget}
|
|
@@ -266,7 +274,7 @@ function TreeView({
|
|
|
266
274
|
) : (
|
|
267
275
|
<nav
|
|
268
276
|
aria-label={label}
|
|
269
|
-
className={cn("min-w-0
|
|
277
|
+
className={cn("flex h-full min-w-0 justify-center", className)}
|
|
270
278
|
data-slot="tree-root"
|
|
271
279
|
ref={railRef}
|
|
272
280
|
>
|
|
@@ -8,8 +8,11 @@ export interface TreeNode {
|
|
|
8
8
|
/** Leading icon. Required in practice for `sidebar` — it is all that
|
|
9
9
|
* remains of a row while the rail is collapsed. */
|
|
10
10
|
icon?: React.ReactNode
|
|
11
|
-
/**
|
|
12
|
-
*
|
|
11
|
+
/** Route metadata for the consumer's `onNavigate` — every row renders as
|
|
12
|
+
* a button, so this is never turned into an anchor element. Read it (with
|
|
13
|
+
* `target`) off the node your handler receives. One native behaviour is
|
|
14
|
+
* kept: `toc` rows below the route level jump to `#{id}` after select
|
|
15
|
+
* (a `#hash` href overrides the id; a non-hash href disables the jump). */
|
|
13
16
|
href?: string
|
|
14
17
|
target?: "_blank" | "_self" | "_parent" | "_top"
|
|
15
18
|
disabled?: boolean
|
|
@@ -27,14 +30,31 @@ export type TreeVariant = "navigation" | "toc" | "sidebar" | "files"
|
|
|
27
30
|
/** Where a dragged row lands relative to its drop target. */
|
|
28
31
|
export type TreeDropPosition = "before" | "after" | "inside"
|
|
29
32
|
|
|
33
|
+
/** Key for one tree’s state in the store. Any stable string; `useTree`
|
|
34
|
+
* generates one when you don't pass it. */
|
|
35
|
+
export type TreeInstanceId = string
|
|
36
|
+
|
|
37
|
+
/** Row a drag hovers, and where the drop would land. */
|
|
38
|
+
export interface TreeDropTarget {
|
|
39
|
+
id: string
|
|
40
|
+
position: TreeDropPosition
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
interface TreeBaseProps {
|
|
31
44
|
items: TreeNode[]
|
|
45
|
+
/** Key this tree's state under a name your app can address —
|
|
46
|
+
* `useTreeState("app-nav")` / `useTreeActions("app-nav")` reach it from
|
|
47
|
+
* anywhere under `ExegiaProvider`. Without one the tree generates a key
|
|
48
|
+
* and its state is dropped when it unmounts. */
|
|
49
|
+
treeId?: TreeInstanceId
|
|
32
50
|
/** Never set on the data form — `tree` selects the controller form. */
|
|
33
51
|
tree?: never
|
|
34
52
|
/** `id` of the current entry — matches any depth. Its ancestors expand. */
|
|
35
53
|
activeId?: string
|
|
36
|
-
/** Fires for every selection (rows and leaves alike), after the
|
|
37
|
-
*
|
|
54
|
+
/** Fires for every selection (rows and leaves alike), after the node's
|
|
55
|
+
* own `onSelect`. Rows are buttons, so this is the routing path — wire
|
|
56
|
+
* your router's navigate here. `toc` rows below the route level still jump
|
|
57
|
+
* to `#{id}` natively, after this fires. */
|
|
38
58
|
onNavigate?: (node: TreeNode) => void
|
|
39
59
|
/** Expand/collapse cues. Silent until `bindSounds()`. */
|
|
40
60
|
sound?: boolean
|
|
@@ -72,12 +92,14 @@ export type TreeDataProps = TreeBaseProps &
|
|
|
72
92
|
| ({
|
|
73
93
|
/** Nested app navigation. With 3 levels of nodes the top level
|
|
74
94
|
* becomes collapsible section names (styled as headings, not
|
|
75
|
-
*
|
|
95
|
+
* rows); with 2 it renders plain selectable rows. */
|
|
76
96
|
variant: "navigation"
|
|
77
97
|
} & TreeReadonlyProps)
|
|
78
98
|
| ({
|
|
79
|
-
/** Table of contents: top-level nodes are routes
|
|
80
|
-
*
|
|
99
|
+
/** Table of contents: top-level nodes are routes (handled in
|
|
100
|
+
* `onNavigate`); rows below jump to `#{id}` after select, like an
|
|
101
|
+
* anchor would. Parents select on the row and expand from a
|
|
102
|
+
* separate overlay chevron. */
|
|
81
103
|
variant: "toc"
|
|
82
104
|
} & TreeReadonlyProps)
|
|
83
105
|
| ({
|
|
@@ -106,6 +128,11 @@ export type TreeDataProps = TreeBaseProps &
|
|
|
106
128
|
* controlled when passed and hook-owned when its `default*` twin is used
|
|
107
129
|
* instead. */
|
|
108
130
|
export interface UseTreeOptions {
|
|
131
|
+
/** Key this tree's state under a name your app can address —
|
|
132
|
+
* `useTreeState("app-nav")` / `useTreeActions("app-nav")` reach it from
|
|
133
|
+
* anywhere under `ExegiaProvider`. Without one the hook generates a key
|
|
134
|
+
* and the state is dropped when the component unmounts. */
|
|
135
|
+
treeId?: TreeInstanceId
|
|
109
136
|
/** Which shape the tree takes — gates rename/reorder (`files`) and the
|
|
110
137
|
* collapsible rail (`sidebar`). */
|
|
111
138
|
variant: TreeVariant
|
|
@@ -143,6 +170,8 @@ export interface UseTreeOptions {
|
|
|
143
170
|
/** Everything the tree can do, callable from outside the component.
|
|
144
171
|
* Returned by `useTree` and accepted by `<Tree tree={…} />`. */
|
|
145
172
|
export interface TreeController {
|
|
173
|
+
/** The key this tree's state is stored under. */
|
|
174
|
+
treeId: TreeInstanceId
|
|
146
175
|
variant: TreeVariant
|
|
147
176
|
items: TreeNode[]
|
|
148
177
|
/** `navigation` with 3 levels of nodes — depth 0 renders as sections. */
|
|
@@ -187,26 +216,107 @@ export interface TreeController {
|
|
|
187
216
|
canMove: boolean
|
|
188
217
|
move: (id: string, parentId: string | null, index: number) => void
|
|
189
218
|
|
|
219
|
+
/** Back to the values the tree mounted with. */
|
|
220
|
+
reset: () => void
|
|
221
|
+
|
|
190
222
|
dnd: TreeDndContextValue
|
|
191
223
|
}
|
|
192
224
|
|
|
193
225
|
/** @internal What a row needs from the root, threaded through context so
|
|
194
|
-
* the recursive rows stay prop-light.
|
|
226
|
+
* the recursive rows stay prop-light.
|
|
227
|
+
*
|
|
228
|
+
* Deliberately free of tree state. Rows read state from per-node atoms, so
|
|
229
|
+
* this value keeps its identity for the life of the tree and expanding one
|
|
230
|
+
* branch no longer re-renders every row through context. */
|
|
195
231
|
export interface TreeContextValue {
|
|
196
|
-
|
|
232
|
+
treeId: TreeInstanceId
|
|
197
233
|
renderTrailing?: (node: TreeNode) => React.ReactNode
|
|
234
|
+
dnd: TreeDndHandlers
|
|
198
235
|
}
|
|
199
236
|
|
|
200
|
-
/** @internal
|
|
201
|
-
*
|
|
202
|
-
|
|
237
|
+
/** @internal The drag event handlers, stable for the life of the tree —
|
|
238
|
+
* they read drag state out of the store instead of closing over it, so
|
|
239
|
+
* handing them to every row costs no re-renders. */
|
|
240
|
+
export interface TreeDndHandlers {
|
|
203
241
|
enabled: boolean
|
|
204
|
-
draggedId: string | null
|
|
205
|
-
/** Row the pointer is over and where the drop would land. */
|
|
206
|
-
dropTarget: { id: string; position: TreeDropPosition } | null
|
|
207
242
|
onRowDragStart: (event: React.DragEvent, id: string) => void
|
|
208
243
|
onRowDragOver: (event: React.DragEvent, node: TreeNode) => void
|
|
209
244
|
onRowDragLeave: (event: React.DragEvent) => void
|
|
210
245
|
onRowDrop: (event: React.DragEvent, node: TreeNode) => void
|
|
211
246
|
onRowDragEnd: () => void
|
|
212
247
|
}
|
|
248
|
+
|
|
249
|
+
/** @internal Handlers plus live drag state — what `TreeController.dnd`
|
|
250
|
+
* exposes (all `null`/no-op outside `files`). */
|
|
251
|
+
export interface TreeDndContextValue extends TreeDndHandlers {
|
|
252
|
+
draggedId: string | null
|
|
253
|
+
/** Row the pointer is over and where the drop would land. */
|
|
254
|
+
dropTarget: TreeDropTarget | null
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Everything observable about one tree, for consumers reading it by id. */
|
|
258
|
+
export interface TreeState {
|
|
259
|
+
variant: TreeVariant
|
|
260
|
+
items: TreeNode[]
|
|
261
|
+
activeId?: string
|
|
262
|
+
expandedIds: ReadonlySet<string>
|
|
263
|
+
collapsed: boolean
|
|
264
|
+
renamingId: string | null
|
|
265
|
+
sectioned: boolean
|
|
266
|
+
sectionIds: string[]
|
|
267
|
+
canRename: boolean
|
|
268
|
+
canMove: boolean
|
|
269
|
+
draggedId: string | null
|
|
270
|
+
dropTarget: TreeDropTarget | null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Everything doable to one tree from outside its component. */
|
|
274
|
+
export interface TreeActions {
|
|
275
|
+
expand: (id: string) => void
|
|
276
|
+
collapse: (id: string) => void
|
|
277
|
+
toggleExpanded: (id: string) => void
|
|
278
|
+
expandAll: () => void
|
|
279
|
+
collapseAll: () => void
|
|
280
|
+
reveal: (id: string) => void
|
|
281
|
+
setCollapsed: (collapsed: boolean) => void
|
|
282
|
+
toggleCollapsed: () => void
|
|
283
|
+
select: (id: string) => void
|
|
284
|
+
startRename: (id: string) => void
|
|
285
|
+
cancelRename: () => void
|
|
286
|
+
rename: (id: string, label: string) => void
|
|
287
|
+
move: (id: string, parentId: string | null, index: number) => void
|
|
288
|
+
setItems: (items: TreeNode[]) => void
|
|
289
|
+
reset: () => void
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** @internal Projection of `useTree`'s options — primitives only, so the
|
|
293
|
+
* store write runs once per real change instead of once per render. */
|
|
294
|
+
export interface TreeConfig {
|
|
295
|
+
variant: TreeVariant
|
|
296
|
+
sound: boolean
|
|
297
|
+
controlsItems: boolean
|
|
298
|
+
controlsActiveId: boolean
|
|
299
|
+
controlsCollapsed: boolean
|
|
300
|
+
managesItems: boolean
|
|
301
|
+
hasRenameHandler: boolean
|
|
302
|
+
hasMoveHandler: boolean
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** @internal Latest option callbacks. Only write atoms read this, so it
|
|
306
|
+
* can be refreshed every commit without re-rendering anything. */
|
|
307
|
+
export interface TreeHandlers {
|
|
308
|
+
onNavigate?: (node: TreeNode) => void
|
|
309
|
+
onItemsChange?: (items: TreeNode[]) => void
|
|
310
|
+
onExpandedChange?: (ids: string[]) => void
|
|
311
|
+
onCollapsedChange?: (collapsed: boolean) => void
|
|
312
|
+
onRename?: (id: string, label: string) => void
|
|
313
|
+
onMove?: (id: string, parentId: string | null, index: number) => void
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** @internal What an instance starts from, replayed by `resetTreeAtom`. */
|
|
317
|
+
export interface TreeSeed {
|
|
318
|
+
items: TreeNode[]
|
|
319
|
+
activeId?: string
|
|
320
|
+
collapsed: boolean
|
|
321
|
+
expandedIds?: string[]
|
|
322
|
+
}
|