@exegia/corpora-ui 0.18.0 → 0.20.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/nav/sidebar/__tests__/use-ai-sidebar.test.d.ts +1 -0
- package/dist-lib/components/blocks/nav/sidebar/ai-sidebar.d.ts +11 -2
- package/dist-lib/components/blocks/nav/sidebar/type.d.ts +83 -1
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +22 -31
- package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +5 -0
- package/dist-lib/components/blocks/scaffold/constants.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/index.d.ts +3 -1
- package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +4 -3
- package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +5 -4
- package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +2 -0
- package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +1 -1
- package/dist-lib/components/blocks/scaffold/scaffold-tab.d.ts +14 -0
- package/dist-lib/components/blocks/scaffold/type.d.ts +58 -28
- package/dist-lib/components/blocks/scaffold/use-panel-visibility.d.ts +19 -0
- package/dist-lib/components/blocks/scaffold/utils.d.ts +12 -0
- package/dist-lib/components/composed/tree/__tests__/tree.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/__tests__/use-tree.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/constants.d.ts +35 -0
- package/dist-lib/components/composed/tree/index.d.ts +4 -0
- package/dist-lib/components/composed/tree/tree-context.d.ts +4 -0
- package/dist-lib/components/composed/tree/tree-node.d.ts +7 -0
- package/dist-lib/components/composed/tree/tree.d.ts +19 -0
- package/dist-lib/components/composed/tree/type.d.ts +192 -0
- package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +7 -0
- package/dist-lib/components/composed/tree/use-tree.d.ts +20 -0
- package/dist-lib/components/composed/tree/utils.d.ts +28 -0
- package/dist-lib/components/user-avatar/audio-wave.d.ts +2 -0
- package/dist-lib/components/user-avatar/component.d.ts +10 -0
- package/dist-lib/components/user-avatar/fallback.d.ts +5 -0
- package/dist-lib/components/user-avatar/index.d.ts +13 -0
- package/dist-lib/components/user-avatar/type.d.ts +26 -0
- package/dist-lib/components/user-avatar/utils.d.ts +2 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.js +2309 -1285
- package/dist-lib/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/beste/piece/browser-frame.tsx +9 -6
- package/src/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.tsx +310 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +90 -60
- package/src/components/blocks/nav/sidebar/type.ts +105 -1
- package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +293 -116
- package/src/components/blocks/nav/sidebar/utils.ts +28 -0
- package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +268 -26
- package/src/components/blocks/scaffold/constants.ts +11 -0
- package/src/components/blocks/scaffold/index.ts +4 -1
- package/src/components/blocks/scaffold/panel-menu-button.tsx +96 -115
- package/src/components/blocks/scaffold/scaffold-actions.tsx +41 -50
- package/src/components/blocks/scaffold/scaffold-canvas.tsx +48 -5
- package/src/components/blocks/scaffold/scaffold-inspector.tsx +27 -3
- package/src/components/blocks/scaffold/scaffold-main.tsx +1 -1
- package/src/components/blocks/scaffold/scaffold-panel.tsx +56 -30
- package/src/components/blocks/scaffold/scaffold-root.tsx +18 -3
- package/src/components/blocks/scaffold/scaffold-sidebar.tsx +1 -1
- package/src/components/blocks/scaffold/scaffold-tab.tsx +104 -0
- package/src/components/blocks/scaffold/type.ts +62 -33
- package/src/components/blocks/scaffold/use-panel-visibility.ts +180 -0
- package/src/components/blocks/scaffold/utils.ts +35 -0
- package/src/components/composed/tree/__tests__/tree.test.tsx +400 -0
- package/src/components/composed/tree/__tests__/use-tree.test.tsx +332 -0
- package/src/components/composed/tree/constants.ts +60 -0
- package/src/components/composed/tree/index.ts +13 -0
- package/src/components/composed/tree/tree-context.ts +13 -0
- package/src/components/composed/tree/tree-node.tsx +451 -0
- package/src/components/composed/tree/tree.tsx +278 -0
- package/src/components/composed/tree/type.ts +212 -0
- package/src/components/composed/tree/use-tree-dnd.ts +148 -0
- package/src/components/composed/tree/use-tree.ts +306 -0
- package/src/components/composed/tree/utils.ts +172 -0
- package/src/components/composed/user-avatar.tsx +4 -1
- package/src/components/docs/component-preview.tsx +11 -11
- package/src/components/user-avatar/audio-wave.tsx +22 -0
- package/src/components/user-avatar/component.tsx +53 -0
- package/src/components/user-avatar/fallback.tsx +26 -0
- package/src/components/user-avatar/index.ts +15 -0
- package/src/components/user-avatar/type.ts +35 -0
- package/src/components/user-avatar/utils.ts +8 -0
- package/src/index.css +211 -153
- package/src/index.ts +1 -0
|
@@ -2,61 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
AnimatePresence,
|
|
7
|
-
motion,
|
|
8
|
-
useReducedMotion,
|
|
9
|
-
type Variants,
|
|
10
|
-
} from "motion/react"
|
|
11
5
|
import { More } from "iconsax-reactjs"
|
|
12
|
-
import { LucideArrowUpDown, LucideChevronDown } from "lucide-react"
|
|
6
|
+
import { LucideArrowUpDown, LucideChevronDown, LucideX } from "lucide-react"
|
|
13
7
|
|
|
14
8
|
import { cn } from "@/lib/utils"
|
|
15
9
|
import type { PanelMenuButtonProps } from "./type"
|
|
16
10
|
import { floatingButtonClass, revealOnPanelHoverClass } from "./utils"
|
|
17
|
-
import { Button } from "@/components/ui/button"
|
|
11
|
+
import { Button } from "@/components/ui/button"
|
|
18
12
|
import { GlassButtonGroup } from "@/components/ui/glasscn/glass-button-group.tsx"
|
|
19
|
-
import { EASE_OUT, SPRING_LAYOUT } from "@/lib/ease.ts"
|
|
20
13
|
import { useHoverCapable } from "@/lib/hooks/use-hover-capable"
|
|
21
14
|
import { SCAFFOLD_MENU_COLLAPSE_DELAY } from "./constants"
|
|
22
15
|
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
visible: { opacity: 1, filter: "blur(0px)" },
|
|
30
|
-
exit: {
|
|
31
|
-
opacity: 0,
|
|
32
|
-
filter: "blur(4px)",
|
|
33
|
-
transition: { duration: 0.18, ease: EASE_OUT },
|
|
34
|
-
},
|
|
35
|
-
}
|
|
16
|
+
/** One side of the seam menu. Both sides stay mounted; the pill morphs by
|
|
17
|
+
* collapsing one side's grid column to 0fr while the other grows — pure
|
|
18
|
+
* CSS, driven by `data-expanded` on the group. The hidden side is inert
|
|
19
|
+
* and aria-hidden so it can't be clicked, focused or found by queries. */
|
|
20
|
+
const menuCellClass =
|
|
21
|
+
"grid transition-[grid-template-columns,opacity] duration-300 ease-smooth-out motion-reduce:transition-none"
|
|
36
22
|
|
|
37
23
|
/**
|
|
38
|
-
* Floating
|
|
39
|
-
* surface and its secondary strip
|
|
24
|
+
* Floating menu affordance centered on the seam between a panel's primary
|
|
25
|
+
* surface and its secondary strip: a compact ⋯ toggle that morphs into the
|
|
26
|
+
* Expand | Swap (| Close) actions.
|
|
40
27
|
*/
|
|
41
28
|
export function PanelMenuButton({
|
|
42
29
|
onClick,
|
|
43
30
|
onExpand,
|
|
31
|
+
onCloseSecondary,
|
|
44
32
|
secondaryExpanded = false,
|
|
33
|
+
swapped = false,
|
|
45
34
|
label,
|
|
46
35
|
sound = true,
|
|
47
36
|
className,
|
|
48
37
|
}: PanelMenuButtonProps): React.ReactElement {
|
|
49
|
-
|
|
50
|
-
const reduce = useReducedMotion()
|
|
51
38
|
const canHover = useHoverCapable()
|
|
52
39
|
const actionsId = React.useId()
|
|
53
40
|
const groupRef = React.useRef<HTMLDivElement>(null)
|
|
54
41
|
const actionsRef = React.useRef<HTMLDivElement>(null)
|
|
55
|
-
const actionsLeftRef = React.useRef(0)
|
|
56
42
|
const [expanded, setExpanded] = React.useState(false);
|
|
57
43
|
|
|
58
|
-
const transition = reduce ? { duration: 0 } : SPRING_LAYOUT
|
|
59
|
-
|
|
60
44
|
const collapseTimerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined)
|
|
61
45
|
const suppressHoverOpenUntilRef = React.useRef(0)
|
|
62
46
|
const focusActionsOnOpenRef = React.useRef(false)
|
|
@@ -94,7 +78,7 @@ export function PanelMenuButton({
|
|
|
94
78
|
}, [expanded, canHover, armCollapse])
|
|
95
79
|
|
|
96
80
|
// Click-opens replace the toggle with the actions — hand focus over so
|
|
97
|
-
// keyboard users aren't dropped onto
|
|
81
|
+
// keyboard users aren't dropped onto an inert control.
|
|
98
82
|
React.useEffect(() => {
|
|
99
83
|
if (!expanded || !focusActionsOnOpenRef.current) return
|
|
100
84
|
focusActionsOnOpenRef.current = false
|
|
@@ -130,111 +114,108 @@ export function PanelMenuButton({
|
|
|
130
114
|
setExpanded(true)
|
|
131
115
|
}
|
|
132
116
|
|
|
133
|
-
// popLayout pops the exiting actions group out of the flow — pin it back to
|
|
134
|
-
// where it sat so it stays visually attached to the toggle while leaving.
|
|
135
|
-
React.useLayoutEffect(() => {
|
|
136
|
-
const actionsNode = actionsRef.current
|
|
137
|
-
if (!actionsNode) return
|
|
138
|
-
|
|
139
|
-
if (!expanded) {
|
|
140
|
-
actionsNode.style.left = `${
|
|
141
|
-
actionsLeftRef.current - actionsNode.getBoundingClientRect().left
|
|
142
|
-
}px`
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
actionsNode.style.left = ""
|
|
147
|
-
actionsLeftRef.current = actionsNode.getBoundingClientRect().left
|
|
148
|
-
}, [expanded])
|
|
149
|
-
|
|
150
117
|
return (
|
|
151
118
|
<GlassButtonGroup
|
|
152
119
|
ref={groupRef}
|
|
153
|
-
layout
|
|
154
|
-
transition={transition}
|
|
155
120
|
onPointerEnter={cancelCollapse}
|
|
156
121
|
onPointerLeave={armCollapse}
|
|
157
122
|
glassVariant="subtle"
|
|
123
|
+
data-expanded={expanded ? "" : undefined}
|
|
158
124
|
className={cn(
|
|
159
125
|
floatingButtonClass,
|
|
160
126
|
revealOnPanelHoverClass,
|
|
161
|
-
|
|
162
|
-
"absolute top-1/2 z-10 -translate-y-1/2 overflow-visible!",
|
|
127
|
+
"group/menu absolute top-1/2 z-10 -translate-y-1/2",
|
|
163
128
|
className
|
|
164
129
|
)}
|
|
165
130
|
>
|
|
166
|
-
<
|
|
167
|
-
{expanded
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
131
|
+
<div
|
|
132
|
+
aria-hidden={expanded || undefined}
|
|
133
|
+
inert={expanded || undefined}
|
|
134
|
+
className={cn(
|
|
135
|
+
menuCellClass,
|
|
136
|
+
"grid-cols-[1fr] opacity-100 group-data-expanded/menu:grid-cols-[0fr] group-data-expanded/menu:opacity-0"
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
<div className="flex min-w-0 overflow-hidden">
|
|
140
|
+
<Button
|
|
141
|
+
id="scaffold-panel-menu-toggle-button"
|
|
142
|
+
aria-label={label}
|
|
143
|
+
aria-expanded={expanded}
|
|
144
|
+
aria-controls={actionsId}
|
|
145
|
+
size="icon-sm"
|
|
146
|
+
variant="ghost"
|
|
147
|
+
sound={sound}
|
|
148
|
+
// The hidden Expand|Swap|Close side stretches the pill to the sm
|
|
149
|
+
// button height — match it so the collapsed toggle stays square.
|
|
150
|
+
className="size-8 sm:size-7"
|
|
151
|
+
data-slot="scaffold-panel-menu-toggle"
|
|
152
|
+
onClick={handleOnExpand}
|
|
153
|
+
onPointerEnter={handleToggleHover}
|
|
179
154
|
>
|
|
155
|
+
<More size={16} />
|
|
156
|
+
</Button>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
<div
|
|
160
|
+
id={actionsId}
|
|
161
|
+
ref={actionsRef}
|
|
162
|
+
aria-hidden={!expanded || undefined}
|
|
163
|
+
inert={!expanded || undefined}
|
|
164
|
+
className={cn(
|
|
165
|
+
menuCellClass,
|
|
166
|
+
"grid-cols-[0fr] opacity-0 group-data-expanded/menu:grid-cols-[1fr] group-data-expanded/menu:opacity-100"
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
<div className="flex min-w-0 overflow-hidden">
|
|
170
|
+
<Button
|
|
171
|
+
id="scaffold-panel-menu-expand"
|
|
172
|
+
size="sm"
|
|
173
|
+
variant="ghost"
|
|
174
|
+
sound={sound}
|
|
175
|
+
data-slot="scaffold-panel-menu-expand"
|
|
176
|
+
className="rounded-r-none"
|
|
177
|
+
onClick={() => handleAction(onExpand)}
|
|
178
|
+
>
|
|
179
|
+
{/* Points at the sub-panel that grows next, in VISUAL space —
|
|
180
|
+
a swapped panel is column-reverse, so the direction mirrors.
|
|
181
|
+
Button's [&_svg]:transition-transform eases the flip. */}
|
|
182
|
+
<LucideChevronDown
|
|
183
|
+
size={16}
|
|
184
|
+
className={cn(secondaryExpanded !== swapped && "rotate-180")}
|
|
185
|
+
/>
|
|
186
|
+
Expand
|
|
187
|
+
</Button>
|
|
188
|
+
<Button
|
|
189
|
+
id="scaffold-panel-menu-swap"
|
|
190
|
+
size="sm"
|
|
191
|
+
variant="ghost"
|
|
192
|
+
sound={sound}
|
|
193
|
+
data-slot="scaffold-panel-menu-swap"
|
|
194
|
+
className={cn(
|
|
195
|
+
"border-l-0",
|
|
196
|
+
onCloseSecondary ? "rounded-none" : "rounded-l-none"
|
|
197
|
+
)}
|
|
198
|
+
onClick={() => handleAction(onClick)}
|
|
199
|
+
>
|
|
200
|
+
<LucideArrowUpDown size={16} />
|
|
201
|
+
Swap
|
|
202
|
+
</Button>
|
|
203
|
+
{onCloseSecondary && (
|
|
180
204
|
<Button
|
|
181
|
-
id="scaffold-panel-menu-
|
|
182
|
-
size="sm"
|
|
183
|
-
variant="ghost"
|
|
184
|
-
sound={sound}
|
|
185
|
-
data-slot="scaffold-panel-menu-swap"
|
|
186
|
-
// The motion wrapper hides these from the group's corner
|
|
187
|
-
// selectors — restore what ButtonGroup would have applied.
|
|
188
|
-
className="rounded-r-none"
|
|
189
|
-
onClick={() => handleAction(onExpand)}
|
|
190
|
-
>
|
|
191
|
-
{/* Button's [&_svg]:transition-transform eases the flip. */}
|
|
192
|
-
<LucideChevronDown
|
|
193
|
-
size={16}
|
|
194
|
-
className={cn(secondaryExpanded && "rotate-180")}
|
|
195
|
-
/>
|
|
196
|
-
Expand
|
|
197
|
-
</Button>
|
|
198
|
-
<Button
|
|
199
|
-
id="scaffold-panel-menu-swap"
|
|
205
|
+
id="scaffold-panel-menu-close"
|
|
200
206
|
size="sm"
|
|
201
207
|
variant="ghost"
|
|
202
208
|
sound={sound}
|
|
203
|
-
data-slot="scaffold-panel-menu-
|
|
209
|
+
data-slot="scaffold-panel-menu-close"
|
|
204
210
|
className="rounded-l-none border-l-0"
|
|
205
|
-
onClick={() => handleAction(
|
|
206
|
-
>
|
|
207
|
-
<LucideArrowUpDown size={16} />
|
|
208
|
-
Swap
|
|
209
|
-
</Button>
|
|
210
|
-
</motion.div>
|
|
211
|
-
) : (
|
|
212
|
-
<motion.div
|
|
213
|
-
key="menu-toggle"
|
|
214
|
-
layout
|
|
215
|
-
variants={MENU_SEGMENT_VARIANTS}
|
|
216
|
-
initial={reduce ? { opacity: 0 } : "hidden"}
|
|
217
|
-
animate={reduce ? { opacity: 1 } : "visible"}
|
|
218
|
-
exit={reduce ? { opacity: 0 } : "exit"}
|
|
219
|
-
transition={transition}
|
|
220
|
-
className="flex shrink-0"
|
|
221
|
-
>
|
|
222
|
-
<Button
|
|
223
|
-
id="scaffold-panel-menu-toggle-button"
|
|
224
|
-
aria-label={label}
|
|
225
|
-
aria-expanded={expanded}
|
|
226
|
-
size="icon-sm"
|
|
227
|
-
variant="ghost"
|
|
228
|
-
sound={sound}
|
|
229
|
-
data-slot="scaffold-panel-menu-toggle"
|
|
230
|
-
onClick={handleOnExpand}
|
|
231
|
-
onPointerEnter={handleToggleHover}
|
|
211
|
+
onClick={() => handleAction(onCloseSecondary)}
|
|
232
212
|
>
|
|
233
|
-
<
|
|
213
|
+
<LucideX size={16} />
|
|
214
|
+
Close
|
|
234
215
|
</Button>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
</
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
238
219
|
</GlassButtonGroup>
|
|
239
220
|
)
|
|
240
221
|
}
|
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { motion, useReducedMotion } from "motion/react"
|
|
3
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react"
|
|
4
4
|
import * as React from "react"
|
|
5
5
|
|
|
6
6
|
import { cn } from "@/lib/utils"
|
|
7
7
|
import { SCAFFOLD_EASE, SCAFFOLD_EDGE_GUTTER, SCAFFOLD_MORPH_DURATION } from "./constants"
|
|
8
8
|
import { useScaffoldContext } from "./scaffold-context"
|
|
9
9
|
import type { ScaffoldActionsProps } from "./type"
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { Button } from "@/components/ui/button";
|
|
10
|
+
import { segmentVariants } from "./utils"
|
|
11
|
+
import { Button } from "@/components/ui/button"
|
|
12
|
+
import { SPRING_LAYOUT } from "@/lib/ease.ts"
|
|
14
13
|
import { LucidePlus } from "lucide-react"
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* The floating pill cluster in the main region's top-right corner — the
|
|
18
|
-
* design's "Panel Context Menu":
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* design's "Panel Context Menu": one tab per open panel (`Scaffold.Tab`
|
|
18
|
+
* children) plus an Add segment. Add renders only while `onAdd` is
|
|
19
|
+
* present — omit it once the canvas holds `SCAFFOLD_PANEL_CAPACITY`
|
|
20
|
+
* panels. Slides left in step with the inspector opening.
|
|
21
21
|
*/
|
|
22
22
|
export function ScaffoldActions({
|
|
23
23
|
onAdd,
|
|
24
24
|
addLabel = "Panel",
|
|
25
25
|
addIcon,
|
|
26
|
-
overflowCount,
|
|
27
|
-
onBrowse,
|
|
28
|
-
browseLabel = "Browse panels",
|
|
29
26
|
sound = true,
|
|
30
27
|
className,
|
|
31
28
|
children,
|
|
@@ -33,12 +30,7 @@ export function ScaffoldActions({
|
|
|
33
30
|
}: ScaffoldActionsProps): React.ReactElement {
|
|
34
31
|
const { inspectorOpen, inspectorWidth } = useScaffoldContext()
|
|
35
32
|
const reducedMotion = useReducedMotion()
|
|
36
|
-
|
|
37
|
-
const soundProps = {
|
|
38
|
-
"data-cuelume-press": sound ? "" : undefined,
|
|
39
|
-
"data-cuelume-release": sound ? "" : undefined,
|
|
40
|
-
}
|
|
41
|
-
const showBrowse = overflowCount !== undefined || onBrowse !== undefined
|
|
33
|
+
const transition = reducedMotion ? { duration: 0 } : SPRING_LAYOUT
|
|
42
34
|
|
|
43
35
|
return (
|
|
44
36
|
<motion.div
|
|
@@ -47,7 +39,7 @@ export function ScaffoldActions({
|
|
|
47
39
|
x: inspectorOpen ? -(inspectorWidth + SCAFFOLD_EDGE_GUTTER) : 0,
|
|
48
40
|
}}
|
|
49
41
|
className={cn(
|
|
50
|
-
"sticky inline-flex items-center gap-0.5
|
|
42
|
+
"sticky inline-flex h-12 items-center gap-0.5",
|
|
51
43
|
"justify-end",
|
|
52
44
|
className
|
|
53
45
|
)}
|
|
@@ -59,39 +51,38 @@ export function ScaffoldActions({
|
|
|
59
51
|
}}
|
|
60
52
|
{...rest}
|
|
61
53
|
>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
type="button"
|
|
79
|
-
{...soundProps}
|
|
54
|
+
<motion.div
|
|
55
|
+
layout
|
|
56
|
+
transition={transition}
|
|
57
|
+
className="flex items-center gap-1.5"
|
|
58
|
+
>
|
|
59
|
+
<AnimatePresence mode="popLayout" initial={false}>
|
|
60
|
+
{onAdd && (
|
|
61
|
+
<motion.div
|
|
62
|
+
key="scaffold-add"
|
|
63
|
+
layout
|
|
64
|
+
animate={reducedMotion ? { opacity: 1 } : "visible"}
|
|
65
|
+
className="flex shrink-0"
|
|
66
|
+
exit={reducedMotion ? { opacity: 0 } : "exit"}
|
|
67
|
+
initial={reducedMotion ? { opacity: 0 } : "hidden"}
|
|
68
|
+
transition={transition}
|
|
69
|
+
variants={segmentVariants}
|
|
80
70
|
>
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
71
|
+
<Button
|
|
72
|
+
onClick={onAdd}
|
|
73
|
+
size="sm"
|
|
74
|
+
sound={sound}
|
|
75
|
+
variant="ghost"
|
|
76
|
+
className="h-full gap-2 rounded-sm py-1.5 pr-3 pl-2"
|
|
77
|
+
>
|
|
78
|
+
{addIcon ?? <LucidePlus className="stroke-2" />}
|
|
79
|
+
{addLabel}
|
|
80
|
+
</Button>
|
|
81
|
+
</motion.div>
|
|
82
|
+
)}
|
|
83
|
+
{children}
|
|
84
|
+
</AnimatePresence>
|
|
85
|
+
</motion.div>
|
|
95
86
|
</motion.div>
|
|
96
87
|
)
|
|
97
88
|
}
|
|
@@ -4,26 +4,69 @@ import { AnimatePresence } from "motion/react"
|
|
|
4
4
|
import * as React from "react"
|
|
5
5
|
|
|
6
6
|
import { cn } from "@/lib/utils"
|
|
7
|
-
import
|
|
7
|
+
import { useScaffoldContext } from "./scaffold-context"
|
|
8
|
+
import type { ScaffoldCanvasProps, ScaffoldPanelProps } from "./type"
|
|
9
|
+
|
|
10
|
+
/** A canvas child's panel id when it opted into responsive hiding. */
|
|
11
|
+
function panelIdOf(child: React.ReactNode): string | undefined {
|
|
12
|
+
if (!React.isValidElement<Pick<ScaffoldPanelProps, "id">>(child))
|
|
13
|
+
return undefined
|
|
14
|
+
const id = child.props.id
|
|
15
|
+
return typeof id === "string" ? id : undefined
|
|
16
|
+
}
|
|
8
17
|
|
|
9
18
|
/**
|
|
10
19
|
* The panel row below the action cluster: 8px gutters, 8px gaps, panels
|
|
11
20
|
* side by side. Key each `Scaffold.Panel` child so closes animate out.
|
|
21
|
+
* Measures its own width and hides id'd panels (oldest activation first)
|
|
22
|
+
* whenever it can't grant each one `SCAFFOLD_PANEL_MIN_WIDTH`.
|
|
12
23
|
*/
|
|
13
24
|
export function ScaffoldCanvas({
|
|
14
25
|
className,
|
|
15
26
|
children,
|
|
16
27
|
...rest
|
|
17
28
|
}: ScaffoldCanvasProps): React.ReactElement {
|
|
29
|
+
const { hiddenPanelIds, registerPanelIds, setCanvasWidth } =
|
|
30
|
+
useScaffoldContext()
|
|
31
|
+
const ref = React.useRef<HTMLDivElement>(null)
|
|
32
|
+
|
|
33
|
+
React.useLayoutEffect(() => {
|
|
34
|
+
const element = ref.current
|
|
35
|
+
if (!element || typeof ResizeObserver === "undefined") return
|
|
36
|
+
const observer = new ResizeObserver(([entry]) =>
|
|
37
|
+
setCanvasWidth(entry.contentRect.width)
|
|
38
|
+
)
|
|
39
|
+
observer.observe(element)
|
|
40
|
+
return () => observer.disconnect()
|
|
41
|
+
}, [setCanvasWidth])
|
|
42
|
+
|
|
43
|
+
const childArray = React.Children.toArray(children)
|
|
44
|
+
const panelIds = childArray
|
|
45
|
+
.map(panelIdOf)
|
|
46
|
+
.filter((id): id is string => id !== undefined)
|
|
47
|
+
const idsKey = panelIds.join(" ")
|
|
48
|
+
|
|
49
|
+
React.useLayoutEffect(() => {
|
|
50
|
+
registerPanelIds(panelIds)
|
|
51
|
+
// panelIds is rebuilt each render; idsKey carries its identity.
|
|
52
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
53
|
+
}, [idsKey, registerPanelIds])
|
|
54
|
+
|
|
55
|
+
const visibleChildren = childArray.filter((child) => {
|
|
56
|
+
const id = panelIdOf(child)
|
|
57
|
+
return id === undefined || !hiddenPanelIds.includes(id)
|
|
58
|
+
})
|
|
59
|
+
|
|
18
60
|
return (
|
|
19
61
|
<div
|
|
20
|
-
className={cn("relative flex w-full flex-1 gap-2", className)}
|
|
62
|
+
className={cn("relative flex min-h-0 w-full flex-1 gap-2", className)}
|
|
21
63
|
data-slot="scaffold-canvas"
|
|
64
|
+
ref={ref}
|
|
22
65
|
{...rest}
|
|
23
66
|
>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
</AnimatePresence>
|
|
67
|
+
{/* Sync mode (no `mode="wait"`): an exiting panel shrinks in place
|
|
68
|
+
while its siblings grow into the freed room in the same beat. */}
|
|
69
|
+
<AnimatePresence initial={false}>{visibleChildren}</AnimatePresence>
|
|
27
70
|
</div>
|
|
28
71
|
)
|
|
29
72
|
}
|
|
@@ -8,6 +8,8 @@ import { SCAFFOLD_EASE, SCAFFOLD_EDGE_GUTTER, SCAFFOLD_MORPH_DURATION } from "./
|
|
|
8
8
|
import { useScaffoldContext } from "./scaffold-context"
|
|
9
9
|
import type { ScaffoldInspectorProps } from "./type"
|
|
10
10
|
import { GlassContainer } from "@/components/ui/glasscn/glass-container.tsx"
|
|
11
|
+
import { Button } from "@/components/ui/button"
|
|
12
|
+
import { LucideX } from "lucide-react"
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* The right-hand drawer: a brighter card that slides in over the canvas,
|
|
@@ -21,7 +23,8 @@ export function ScaffoldInspector({
|
|
|
21
23
|
className,
|
|
22
24
|
...rest
|
|
23
25
|
}: ScaffoldInspectorProps): React.ReactElement {
|
|
24
|
-
const { inspectorOpen, inspectorWidth } =
|
|
26
|
+
const { inspectorOpen, inspectorWidth, toggleInspector } =
|
|
27
|
+
useScaffoldContext()
|
|
25
28
|
const reducedMotion = useReducedMotion()
|
|
26
29
|
|
|
27
30
|
// Slide fully past the edge gutter and the drop shadow's blur radius.
|
|
@@ -34,7 +37,7 @@ export function ScaffoldInspector({
|
|
|
34
37
|
aria-hidden={inspectorOpen ? undefined : true}
|
|
35
38
|
aria-label={name}
|
|
36
39
|
className={cn(
|
|
37
|
-
"absolute right-0
|
|
40
|
+
"absolute right-0 z-20 my-2 flex h-full max-h-[calc(100%-0.5rem)] w-96 flex-col",
|
|
38
41
|
!inspectorOpen && "pointer-events-none",
|
|
39
42
|
className
|
|
40
43
|
)}
|
|
@@ -49,7 +52,28 @@ export function ScaffoldInspector({
|
|
|
49
52
|
}}
|
|
50
53
|
{...rest}
|
|
51
54
|
>
|
|
52
|
-
<GlassContainer
|
|
55
|
+
<GlassContainer
|
|
56
|
+
className="flex flex-1 flex-col rounded-md border-t border-white bg-neutral-50/20 shadow-inner shadow-lg dark:border-neutral-800 dark:bg-black/5"
|
|
57
|
+
glassVariant="subtle"
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
id="scaffold-inspector-header"
|
|
61
|
+
className="flex h-12 items-center justify-between gap-2 border-b pr-3 pl-4"
|
|
62
|
+
>
|
|
63
|
+
<span className="text-base font-normal text-card-foreground/70">
|
|
64
|
+
{name}
|
|
65
|
+
</span>
|
|
66
|
+
<Button
|
|
67
|
+
size="icon-xs"
|
|
68
|
+
variant="glass"
|
|
69
|
+
glassVariant="subtle"
|
|
70
|
+
className="scale-85"
|
|
71
|
+
aria-label="Close inspector"
|
|
72
|
+
onClick={toggleInspector}
|
|
73
|
+
>
|
|
74
|
+
<LucideX className="stroke-3" />
|
|
75
|
+
</Button>
|
|
76
|
+
</div>
|
|
53
77
|
{children}
|
|
54
78
|
</GlassContainer>
|
|
55
79
|
</motion.aside>
|
|
@@ -18,7 +18,7 @@ export function ScaffoldMain({
|
|
|
18
18
|
return (
|
|
19
19
|
<div
|
|
20
20
|
id="scaffold-main"
|
|
21
|
-
className={cn("relative flex
|
|
21
|
+
className={cn("relative flex min-w-0 flex-1 flex-col", className)}
|
|
22
22
|
data-slot="scaffold-main"
|
|
23
23
|
{...rest}
|
|
24
24
|
>
|