@exegia/corpora-ui 0.16.0 → 0.18.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-block.d.ts +1 -1
- package/dist-lib/components/blocks/scaffold/__tests__/scaffold.test.d.ts +1 -0
- package/dist-lib/components/blocks/scaffold/constants.d.ts +20 -0
- package/dist-lib/components/blocks/scaffold/index.d.ts +25 -0
- package/dist-lib/components/blocks/scaffold/panel-close-button.d.ts +4 -0
- package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +9 -0
- package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-context.d.ts +5 -0
- package/dist-lib/components/blocks/scaffold/scaffold-inspector.d.ts +9 -0
- package/dist-lib/components/blocks/scaffold/scaffold-main.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +10 -0
- package/dist-lib/components/blocks/scaffold/scaffold-root.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/scaffold-sidebar.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-sub-panel.d.ts +3 -0
- package/dist-lib/components/blocks/scaffold/type.d.ts +133 -0
- package/dist-lib/components/blocks/scaffold/use-scaffold.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/utils.d.ts +15 -0
- package/dist-lib/components/blocks/shell/animated-panel-inset.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel-trigger.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +3 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +3 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +2 -0
- package/dist-lib/components/blocks/shell/index.d.ts +16 -0
- package/dist-lib/components/blocks/shell/shell-layout.d.ts +1 -1
- package/dist-lib/components/blocks/shell/type.d.ts +91 -4
- package/dist-lib/components/blocks/shell/utils.d.ts +37 -0
- package/dist-lib/components/motion/index.d.ts +1 -0
- package/dist-lib/components/motion/overflow-actions.d.ts +35 -0
- package/dist-lib/components/ui/button-group.d.ts +11 -0
- package/dist-lib/components/ui/glasscn/glass-button-group.d.ts +5 -0
- package/dist-lib/components/ui/glasscn/glass-container.d.ts +7 -0
- package/dist-lib/components/ui/glasscn/glass-separator.d.ts +5 -0
- package/dist-lib/components/ui/group.d.ts +17 -0
- package/dist-lib/components/ui/separator.d.ts +2 -3
- package/dist-lib/components/ui/sheet.d.ts +14 -0
- package/dist-lib/components/ui/tooltip.d.ts +6 -0
- package/dist-lib/index.d.ts +2 -1
- package/dist-lib/index.js +1432 -932
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/lib/ease.d.ts +3 -0
- package/dist-lib/lib/hooks/use-hover-capable.d.ts +6 -0
- package/package.json +2 -1
- package/src/components/beste/piece/browser-frame.tsx +2 -2
- package/src/components/blocks/nav/sidebar-block.tsx +5 -5
- package/src/components/blocks/nav/sidebar-nav-row.tsx +4 -5
- package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +203 -0
- package/src/components/blocks/scaffold/constants.ts +28 -0
- package/src/components/blocks/scaffold/index.ts +38 -0
- package/src/components/blocks/scaffold/panel-close-button.tsx +36 -0
- package/src/components/blocks/scaffold/panel-menu-button.tsx +240 -0
- package/src/components/blocks/scaffold/scaffold-actions.tsx +97 -0
- package/src/components/blocks/scaffold/scaffold-canvas.tsx +29 -0
- package/src/components/blocks/scaffold/scaffold-context.ts +18 -0
- package/src/components/blocks/scaffold/scaffold-inspector.tsx +57 -0
- package/src/components/blocks/scaffold/scaffold-main.tsx +28 -0
- package/src/components/blocks/scaffold/scaffold-panel.tsx +98 -0
- package/src/components/blocks/scaffold/scaffold-root.tsx +68 -0
- package/src/components/blocks/scaffold/scaffold-sidebar.tsx +31 -0
- package/src/components/blocks/scaffold/scaffold-sub-panel.tsx +51 -0
- package/src/components/blocks/scaffold/type.ts +171 -0
- package/src/components/blocks/scaffold/use-scaffold.ts +48 -0
- package/src/components/blocks/scaffold/utils.ts +30 -0
- package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +3 -3
- package/src/components/blocks/shell/animated-panel-inset.tsx +23 -0
- package/src/components/blocks/shell/animated-panel-provider.tsx +155 -0
- package/src/components/blocks/shell/animated-panel-trigger.tsx +42 -0
- package/src/components/blocks/shell/animated-panel.tsx +135 -0
- package/src/components/blocks/shell/animated-sidebar-content.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-footer.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-group-content.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-group-label.tsx +29 -0
- package/src/components/blocks/shell/animated-sidebar-group.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-header.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +151 -0
- package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +20 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +85 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +44 -0
- package/src/components/blocks/shell/animated-sidebar-menu.tsx +26 -0
- package/src/components/blocks/shell/index.ts +18 -0
- package/src/components/blocks/shell/shell-layout.tsx +44 -64
- package/src/components/blocks/shell/type.ts +113 -6
- package/src/components/blocks/shell/use-shell-panels.ts +1 -1
- package/src/components/blocks/shell/utils.ts +142 -1
- package/src/components/docs/demo-controls.tsx +1 -1
- package/src/components/motion/index.ts +0 -0
- package/src/components/motion/overflow-actions.tsx +376 -0
- package/src/components/ui/button-group.tsx +88 -0
- package/src/components/ui/button.tsx +3 -3
- package/src/components/ui/glasscn/glass-button-group.tsx +48 -0
- package/src/components/ui/glasscn/glass-container.tsx +125 -0
- package/src/components/ui/glasscn/glass-separator.tsx +39 -0
- package/src/components/ui/glasscn/liquid-glass.tsx +250 -210
- package/src/components/ui/group.tsx +92 -0
- package/src/components/ui/separator.tsx +11 -11
- package/src/components/ui/sheet.tsx +135 -0
- package/src/components/ui/tooltip.tsx +66 -0
- package/src/index.css +1 -1
- package/src/index.ts +13 -1
- package/src/lib/ease.ts +26 -0
- package/src/lib/hooks/use-hover-capable.ts +23 -0
- package/dist-lib/components/motion/animated-sidebar.d.ts +0 -109
- package/src/components/motion/animated-sidebar.tsx +0 -1196
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { motion, useReducedMotion } from "motion/react"
|
|
4
|
+
import * as React from "react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { SCAFFOLD_EASE, SCAFFOLD_EDGE_GUTTER, SCAFFOLD_MORPH_DURATION } from "./constants"
|
|
8
|
+
import { useScaffoldContext } from "./scaffold-context"
|
|
9
|
+
import type { ScaffoldActionsProps } from "./type"
|
|
10
|
+
import { actionSegmentClass } from "./utils"
|
|
11
|
+
import { GroupSeparator } from "@/components/ui/group"
|
|
12
|
+
import { GlassButtonGroup } from "@/components/ui/glasscn/glass-button-group"
|
|
13
|
+
import { Button } from "@/components/ui/button";
|
|
14
|
+
import { LucidePlus } from "lucide-react"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The floating pill cluster in the main region's top-right corner — the
|
|
18
|
+
* design's "Panel Context Menu": an Add segment plus an overflow segment
|
|
19
|
+
* whose badge counts the non-visible panels. Slides left in step with the
|
|
20
|
+
* inspector opening.
|
|
21
|
+
*/
|
|
22
|
+
export function ScaffoldActions({
|
|
23
|
+
onAdd,
|
|
24
|
+
addLabel = "Panel",
|
|
25
|
+
addIcon,
|
|
26
|
+
overflowCount,
|
|
27
|
+
onBrowse,
|
|
28
|
+
browseLabel = "Browse panels",
|
|
29
|
+
sound = true,
|
|
30
|
+
className,
|
|
31
|
+
children,
|
|
32
|
+
...rest
|
|
33
|
+
}: ScaffoldActionsProps): React.ReactElement {
|
|
34
|
+
const { inspectorOpen, inspectorWidth } = useScaffoldContext()
|
|
35
|
+
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
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<motion.div
|
|
45
|
+
id="scaffold-actions"
|
|
46
|
+
animate={{
|
|
47
|
+
x: inspectorOpen ? -(inspectorWidth + SCAFFOLD_EDGE_GUTTER) : 0,
|
|
48
|
+
}}
|
|
49
|
+
className={cn(
|
|
50
|
+
"sticky inline-flex items-center gap-0.5 h-12",
|
|
51
|
+
"justify-end",
|
|
52
|
+
className
|
|
53
|
+
)}
|
|
54
|
+
data-slot="scaffold-actions"
|
|
55
|
+
initial={false}
|
|
56
|
+
transition={{
|
|
57
|
+
duration: reducedMotion ? 0 : SCAFFOLD_MORPH_DURATION,
|
|
58
|
+
ease: SCAFFOLD_EASE,
|
|
59
|
+
}}
|
|
60
|
+
{...rest}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
<GlassButtonGroup glassVariant="liquid-refract" className="rounded-md">
|
|
64
|
+
<Button
|
|
65
|
+
onClick={onAdd}
|
|
66
|
+
size="sm"
|
|
67
|
+
variant="ghost"
|
|
68
|
+
>
|
|
69
|
+
{addIcon ?? <LucidePlus className="stroke-3" />}
|
|
70
|
+
{addLabel}
|
|
71
|
+
</Button>
|
|
72
|
+
{!showBrowse && <GroupSeparator />}
|
|
73
|
+
{showBrowse && (
|
|
74
|
+
<button
|
|
75
|
+
aria-label={browseLabel}
|
|
76
|
+
className={cn(actionSegmentClass, "gap-0.5 px-2")}
|
|
77
|
+
onClick={onBrowse}
|
|
78
|
+
type="button"
|
|
79
|
+
{...soundProps}
|
|
80
|
+
>
|
|
81
|
+
<span className="relative">
|
|
82
|
+
|
|
83
|
+
{overflowCount !== undefined && overflowCount > 0 && (
|
|
84
|
+
<span className="absolute -top-1.5 -right-1.5 flex size-3.5 items-center justify-center rounded-full bg-neutral-700 text-[9px] font-semibold text-white dark:bg-neutral-200 dark:text-neutral-900">
|
|
85
|
+
{overflowCount}
|
|
86
|
+
</span>
|
|
87
|
+
)}
|
|
88
|
+
</span>
|
|
89
|
+
|
|
90
|
+
</button>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{children}
|
|
94
|
+
</GlassButtonGroup>
|
|
95
|
+
</motion.div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { AnimatePresence } from "motion/react"
|
|
4
|
+
import * as React from "react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import type { ScaffoldCanvasProps } from "./type"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The panel row below the action cluster: 8px gutters, 8px gaps, panels
|
|
11
|
+
* side by side. Key each `Scaffold.Panel` child so closes animate out.
|
|
12
|
+
*/
|
|
13
|
+
export function ScaffoldCanvas({
|
|
14
|
+
className,
|
|
15
|
+
children,
|
|
16
|
+
...rest
|
|
17
|
+
}: ScaffoldCanvasProps): React.ReactElement {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className={cn("relative flex w-full flex-1 gap-2", className)}
|
|
21
|
+
data-slot="scaffold-canvas"
|
|
22
|
+
{...rest}
|
|
23
|
+
>
|
|
24
|
+
<AnimatePresence initial={false} mode="popLayout">
|
|
25
|
+
{children}
|
|
26
|
+
</AnimatePresence>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import type { ScaffoldContextValue } from "./type"
|
|
6
|
+
|
|
7
|
+
export const ScaffoldContext = React.createContext<ScaffoldContextValue | null>(
|
|
8
|
+
null
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
/** Read the scaffold's shared state; must run under `Scaffold.Root`. */
|
|
12
|
+
export function useScaffoldContext(): ScaffoldContextValue {
|
|
13
|
+
const context = React.useContext(ScaffoldContext)
|
|
14
|
+
if (!context) {
|
|
15
|
+
throw new Error("useScaffoldContext must be used within <Scaffold.Root>")
|
|
16
|
+
}
|
|
17
|
+
return context
|
|
18
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { motion, useReducedMotion } from "motion/react"
|
|
4
|
+
import * as React from "react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { SCAFFOLD_EASE, SCAFFOLD_EDGE_GUTTER, SCAFFOLD_MORPH_DURATION } from "./constants"
|
|
8
|
+
import { useScaffoldContext } from "./scaffold-context"
|
|
9
|
+
import type { ScaffoldInspectorProps } from "./type"
|
|
10
|
+
import { GlassContainer } from "@/components/ui/glasscn/glass-container.tsx"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The right-hand drawer: a brighter card that slides in over the canvas,
|
|
14
|
+
* inset from the viewport edges. Open state lives on `Scaffold.Root` (or
|
|
15
|
+
* `useScaffold().providerProps`); the drawer stays mounted so it can slide
|
|
16
|
+
* back out.
|
|
17
|
+
*/
|
|
18
|
+
export function ScaffoldInspector({
|
|
19
|
+
children,
|
|
20
|
+
name = "Inspector",
|
|
21
|
+
className,
|
|
22
|
+
...rest
|
|
23
|
+
}: ScaffoldInspectorProps): React.ReactElement {
|
|
24
|
+
const { inspectorOpen, inspectorWidth } = useScaffoldContext()
|
|
25
|
+
const reducedMotion = useReducedMotion()
|
|
26
|
+
|
|
27
|
+
// Slide fully past the edge gutter and the drop shadow's blur radius.
|
|
28
|
+
const offcanvasX = inspectorWidth + SCAFFOLD_EDGE_GUTTER + 12
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<motion.aside
|
|
32
|
+
id="scaffold-inspector"
|
|
33
|
+
animate={{ x: inspectorOpen ? 0 : offcanvasX }}
|
|
34
|
+
aria-hidden={inspectorOpen ? undefined : true}
|
|
35
|
+
aria-label={name}
|
|
36
|
+
className={cn(
|
|
37
|
+
"absolute right-0 w-80 z-20 my-2 flex h-full max-h-[calc(100%-1rem)] flex-col",
|
|
38
|
+
!inspectorOpen && "pointer-events-none",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
data-slot="scaffold-inspector"
|
|
42
|
+
inert={inspectorOpen ? undefined : true}
|
|
43
|
+
initial={false}
|
|
44
|
+
role="complementary"
|
|
45
|
+
style={{ width: inspectorWidth }}
|
|
46
|
+
transition={{
|
|
47
|
+
duration: reducedMotion ? 0 : SCAFFOLD_MORPH_DURATION,
|
|
48
|
+
ease: SCAFFOLD_EASE,
|
|
49
|
+
}}
|
|
50
|
+
{...rest}
|
|
51
|
+
>
|
|
52
|
+
<GlassContainer className="flex flex-col shadow-inner shadow-md rounded-md flex-1 border-t border-white dark:border-neutral-900 bg-neutral-50/10 dark:bg-black/10" glassVariant="subtle">
|
|
53
|
+
{children}
|
|
54
|
+
</GlassContainer>
|
|
55
|
+
</motion.aside>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
import type { ScaffoldMainProps } from "./type"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The region right of the rail. Hosts `Scaffold.Actions` (top-right),
|
|
10
|
+
* `Scaffold.Canvas` and the `Scaffold.Inspector` overlay — all positioned
|
|
11
|
+
* against this box.
|
|
12
|
+
*/
|
|
13
|
+
export function ScaffoldMain({
|
|
14
|
+
className,
|
|
15
|
+
children,
|
|
16
|
+
...rest
|
|
17
|
+
}: ScaffoldMainProps): React.ReactElement {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
id="scaffold-main"
|
|
21
|
+
className={cn("relative flex h-full min-w-0 flex-1 flex-col", className)}
|
|
22
|
+
data-slot="scaffold-main"
|
|
23
|
+
{...rest}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { motion, useReducedMotion } from "motion/react"
|
|
4
|
+
import * as React from "react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { SCAFFOLD_EASE, SCAFFOLD_MORPH_DURATION } from "./constants"
|
|
8
|
+
import { PanelCloseButton } from "./panel-close-button"
|
|
9
|
+
import { PanelMenuButton } from "./panel-menu-button.tsx"
|
|
10
|
+
import type { ScaffoldPanelProps, TSubPanelPosition } from "./type"
|
|
11
|
+
import { ScaffoldSubPanel } from "@/components/blocks/scaffold/scaffold-sub-panel.tsx"
|
|
12
|
+
import { useState } from "react"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A canvas panel: a primary card plus an optional secondary strip below it,
|
|
16
|
+
* separated by a slice of the desktop backdrop. Floating close and swap
|
|
17
|
+
* buttons reveal on hover when their callbacks are provided. Swapping trades
|
|
18
|
+
* the two cards with a layout morph — the strip glides up and grows into the
|
|
19
|
+
* primary slot while the primary card shrinks down into the strip.
|
|
20
|
+
*/
|
|
21
|
+
export function ScaffoldPanel({
|
|
22
|
+
children,
|
|
23
|
+
SecondaryPanel,
|
|
24
|
+
onClose,
|
|
25
|
+
onSwap,
|
|
26
|
+
name,
|
|
27
|
+
closeLabel = "Close panel",
|
|
28
|
+
swapLabel = "Swap panel content",
|
|
29
|
+
sound = true,
|
|
30
|
+
className,
|
|
31
|
+
}: ScaffoldPanelProps): React.ReactElement {
|
|
32
|
+
|
|
33
|
+
const reducedMotion = useReducedMotion()
|
|
34
|
+
|
|
35
|
+
const transition = {
|
|
36
|
+
duration: reducedMotion ? 0 : SCAFFOLD_MORPH_DURATION,
|
|
37
|
+
ease: SCAFFOLD_EASE,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const [subPanelPosition, setSubPanelPosition] = useState<TSubPanelPosition>("top")
|
|
41
|
+
const [isSwapped, setIsSwapped] = useState<boolean>(false)
|
|
42
|
+
const handleSwap = () => {
|
|
43
|
+
setIsSwapped((prev) => !prev);
|
|
44
|
+
onSwap?.();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Trades which sub-panel holds the flexible slot — the collapsed card grows
|
|
48
|
+
// into `flex-1` while the expanded one shrinks to the `min-h-14` strip.
|
|
49
|
+
const handleExpand = () => {
|
|
50
|
+
setSubPanelPosition((prev) => (prev === "top" ? "bottom" : "top"));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<motion.section
|
|
55
|
+
id="scaffold-panel"
|
|
56
|
+
animate={{ flexDirection: isSwapped ? "column-reverse" : "column" }}
|
|
57
|
+
aria-label={name}
|
|
58
|
+
className={cn(
|
|
59
|
+
"group/panel relative flex min-w-80 flex-1 flex-col",
|
|
60
|
+
className
|
|
61
|
+
)}
|
|
62
|
+
layout
|
|
63
|
+
data-slot="scaffold-panel"
|
|
64
|
+
data-swapped={isSwapped ? "" : undefined}
|
|
65
|
+
transition={transition}
|
|
66
|
+
>
|
|
67
|
+
<ScaffoldSubPanel
|
|
68
|
+
id="top"
|
|
69
|
+
expanded={subPanelPosition === "top"}
|
|
70
|
+
primary
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
</ScaffoldSubPanel>
|
|
74
|
+
{onSwap && SecondaryPanel && (
|
|
75
|
+
<div className="relative flex items-center justify-center h-2">
|
|
76
|
+
<PanelMenuButton
|
|
77
|
+
label={swapLabel}
|
|
78
|
+
onClick={handleSwap}
|
|
79
|
+
onExpand={handleExpand}
|
|
80
|
+
secondaryExpanded={subPanelPosition === "bottom"}
|
|
81
|
+
sound={sound}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
{SecondaryPanel && (
|
|
86
|
+
<ScaffoldSubPanel
|
|
87
|
+
id="bottom"
|
|
88
|
+
expanded={subPanelPosition === "bottom"}
|
|
89
|
+
>
|
|
90
|
+
{SecondaryPanel}
|
|
91
|
+
</ScaffoldSubPanel>
|
|
92
|
+
)}
|
|
93
|
+
{onClose && (
|
|
94
|
+
<PanelCloseButton label={closeLabel} onClick={onClose} sound={sound} />
|
|
95
|
+
)}
|
|
96
|
+
</motion.section>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
import { SCAFFOLD_INSPECTOR_WIDTH } from "./constants"
|
|
7
|
+
import { ScaffoldContext } from "./scaffold-context"
|
|
8
|
+
import type { ScaffoldContextValue, ScaffoldRootProps } from "./type"
|
|
9
|
+
import type { ClassNameValue } from "tailwind-merge"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The scaffold's viewport: desktop backdrop + horizontal row of rail and
|
|
13
|
+
* main region, and the provider for the shared inspector state. Fills its
|
|
14
|
+
* container — size it from the outside (e.g. `h-svh` for a full page).
|
|
15
|
+
*/
|
|
16
|
+
export function ScaffoldRoot({
|
|
17
|
+
inspectorOpen: inspectorOpenProp,
|
|
18
|
+
defaultInspectorOpen,
|
|
19
|
+
onInspectorOpenChange,
|
|
20
|
+
inspectorWidth = SCAFFOLD_INSPECTOR_WIDTH,
|
|
21
|
+
className,
|
|
22
|
+
children,
|
|
23
|
+
...rest
|
|
24
|
+
}: ScaffoldRootProps): React.ReactElement {
|
|
25
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(
|
|
26
|
+
defaultInspectorOpen ?? false
|
|
27
|
+
)
|
|
28
|
+
const inspectorOpen = inspectorOpenProp ?? uncontrolledOpen
|
|
29
|
+
const background: ClassNameValue = `bg-linear-to-tr/increasing from-neutral-200 via-neutral-100 to-stone-200 dark:from-neutral-900 dark:via-neutral-950 dark:to-stone-950`
|
|
30
|
+
|
|
31
|
+
const setInspectorOpen = React.useCallback(
|
|
32
|
+
(open: boolean) => {
|
|
33
|
+
if (inspectorOpenProp === undefined) setUncontrolledOpen(open)
|
|
34
|
+
onInspectorOpenChange?.(open)
|
|
35
|
+
},
|
|
36
|
+
[inspectorOpenProp, onInspectorOpenChange]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const toggleInspector = React.useCallback(
|
|
40
|
+
() => setInspectorOpen(!inspectorOpen),
|
|
41
|
+
[inspectorOpen, setInspectorOpen]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const value = React.useMemo<ScaffoldContextValue>(
|
|
45
|
+
() => ({ inspectorOpen, inspectorWidth, setInspectorOpen, toggleInspector }),
|
|
46
|
+
[inspectorOpen, inspectorWidth, setInspectorOpen, toggleInspector]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<ScaffoldContext.Provider value={value}>
|
|
51
|
+
<div
|
|
52
|
+
id={"scaffold-root"}
|
|
53
|
+
className={cn(
|
|
54
|
+
// `clip`, not `hidden`: the off canvas inspector extends the
|
|
55
|
+
// scrollable overflow, and focus/scrollIntoView would scroll a
|
|
56
|
+
// hidden-overflow root sideways to reveal it.
|
|
57
|
+
"relative isolate flex h-full w-full flex-1 overflow-clip pr-2 pb-2",
|
|
58
|
+
background,
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
data-slot="scaffold"
|
|
62
|
+
{...rest}
|
|
63
|
+
>
|
|
64
|
+
{children}
|
|
65
|
+
</div>
|
|
66
|
+
</ScaffoldContext.Provider>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
import type { ScaffoldSidebarProps } from "./type"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The 64px icon rail on the left edge. Transparent — it sits directly on
|
|
10
|
+
* the desktop backdrop; fill it with icon buttons.
|
|
11
|
+
*/
|
|
12
|
+
export function ScaffoldSidebar({
|
|
13
|
+
className,
|
|
14
|
+
children,
|
|
15
|
+
...rest
|
|
16
|
+
}: ScaffoldSidebarProps): React.ReactElement {
|
|
17
|
+
return (
|
|
18
|
+
<nav
|
|
19
|
+
id="scaffold-sidebar"
|
|
20
|
+
aria-label="Primary"
|
|
21
|
+
className={cn(
|
|
22
|
+
"flex min-w-20 shrink-0 flex-col items-center gap-1 pt-8 pb-2",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
data-slot="scaffold-sidebar"
|
|
26
|
+
{...rest}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</nav>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { motion, useReducedMotion } from "framer-motion"
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
import type { ScaffoldSubPanelProps } from "./type"
|
|
6
|
+
import { subPanelVariant } from "@/components/blocks/scaffold/utils.ts"
|
|
7
|
+
import { SCAFFOLD_EASE, SCAFFOLD_MORPH_DURATION } from "./constants"
|
|
8
|
+
|
|
9
|
+
export function ScaffoldSubPanel({
|
|
10
|
+
className,
|
|
11
|
+
children,
|
|
12
|
+
variant = 'card',
|
|
13
|
+
primary = false,
|
|
14
|
+
expanded,
|
|
15
|
+
...rest
|
|
16
|
+
}: ScaffoldSubPanelProps): React.ReactElement {
|
|
17
|
+
const reduce = useReducedMotion()
|
|
18
|
+
// The primary card holds the flexible slot unless the panel says otherwise.
|
|
19
|
+
const isExpanded = expanded ?? primary
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<motion.div
|
|
23
|
+
layout={"size"}
|
|
24
|
+
id={`scaffold-sub-panel-${variant}`}
|
|
25
|
+
// Grow is animated as a style, not a class flip — a class flip rides the
|
|
26
|
+
// layout FLIP, which scales both cards into a swap-like morph. Animating
|
|
27
|
+
// flex-grow slides only the shared boundary: the collapsed card grows up
|
|
28
|
+
// (or down) while the other is squeezed and cropped in place.
|
|
29
|
+
initial={false}
|
|
30
|
+
animate={{ flexGrow: isExpanded ? 1 : 0 }}
|
|
31
|
+
transition={
|
|
32
|
+
reduce
|
|
33
|
+
? { duration: 0 }
|
|
34
|
+
: { duration: SCAFFOLD_MORPH_DURATION, ease: SCAFFOLD_EASE }
|
|
35
|
+
}
|
|
36
|
+
className={cn(
|
|
37
|
+
"flex w-full flex-col",
|
|
38
|
+
subPanelVariant[variant],
|
|
39
|
+
// basis-14 + clip let the primary squeeze down to the strip height;
|
|
40
|
+
// the strip keeps its natural (basis-auto) height when collapsed.
|
|
41
|
+
primary ? "basis-14 min-h-14 overflow-clip" : "basis-auto min-h-14",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
data-slot={`scaffold-sub-panel-${variant}`}
|
|
45
|
+
data-expanded={isExpanded ? "" : undefined}
|
|
46
|
+
{...rest}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</motion.div>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
type ComponentProps,
|
|
3
|
+
type ReactElement,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
} from "react"
|
|
6
|
+
|
|
7
|
+
/** Inspector state shared by every scaffold part through ScaffoldContext. */
|
|
8
|
+
export interface ScaffoldContextValue {
|
|
9
|
+
/** Whether the inspector drawer is currently shown. */
|
|
10
|
+
inspectorOpen: boolean
|
|
11
|
+
/** Drawer width in px — Actions reads it to slide out of the drawer's way. */
|
|
12
|
+
inspectorWidth: number
|
|
13
|
+
setInspectorOpen: (open: boolean) => void
|
|
14
|
+
toggleInspector: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ScaffoldRootProps extends ComponentProps<"div"> {
|
|
18
|
+
/** Controlled inspector state — pair with `onInspectorOpenChange`, or
|
|
19
|
+
* spread `useScaffold().providerProps` instead of wiring by hand. */
|
|
20
|
+
inspectorOpen?: boolean
|
|
21
|
+
/** Initial inspector state when uncontrolled. Closed by default. */
|
|
22
|
+
defaultInspectorOpen?: boolean
|
|
23
|
+
onInspectorOpenChange?: (open: boolean) => void
|
|
24
|
+
/** Drawer width in px. Also drives how far Actions slides aside. */
|
|
25
|
+
inspectorWidth?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ScaffoldSidebarProps = ComponentProps<"nav">
|
|
29
|
+
|
|
30
|
+
export type ScaffoldMainProps = ComponentProps<"div">
|
|
31
|
+
|
|
32
|
+
export type ScaffoldCanvasProps = ComponentProps<"div">
|
|
33
|
+
|
|
34
|
+
/** React's drag/animation DOM handlers collide with motion's gesture props
|
|
35
|
+
* on `motion.*` elements — strip them from pass-through HTML props. */
|
|
36
|
+
type MotionSafe<T> = Omit<
|
|
37
|
+
T,
|
|
38
|
+
| "onDrag"
|
|
39
|
+
| "onDragStart"
|
|
40
|
+
| "onDragEnd"
|
|
41
|
+
| "onAnimationStart"
|
|
42
|
+
| "onAnimationEnd"
|
|
43
|
+
| "onAnimationIteration"
|
|
44
|
+
>
|
|
45
|
+
|
|
46
|
+
export interface ScaffoldActionsProps extends MotionSafe<
|
|
47
|
+
Omit<ComponentProps<"div">, "children">
|
|
48
|
+
> {
|
|
49
|
+
/** Called when the Add segment is pressed. */
|
|
50
|
+
onAdd?: () => void
|
|
51
|
+
/** Label of the Add segment. */
|
|
52
|
+
addLabel?: ReactNode
|
|
53
|
+
/** Replaces the default add icon. */
|
|
54
|
+
addIcon?: ReactNode
|
|
55
|
+
/** Number of non-visible panels tucked into the overflow dropdown —
|
|
56
|
+
* the badge on the browse segment. The segment renders when this or
|
|
57
|
+
* `onBrowse` is present; the badge only when > 0. Per the design
|
|
58
|
+
* comments the dropdown menu itself is a later iteration; for now the
|
|
59
|
+
* segment just keeps the count. */
|
|
60
|
+
overflowCount?: number
|
|
61
|
+
/** Called when the overflow (stacked-panels) segment is pressed. */
|
|
62
|
+
onBrowse?: () => void
|
|
63
|
+
/** Accessible name of the overflow segment. */
|
|
64
|
+
browseLabel?: string
|
|
65
|
+
/** Extra segments appended after the built-in ones. */
|
|
66
|
+
children?: ReactNode
|
|
67
|
+
/** Emit interaction-sound attributes (inert until an app binds cuelume). */
|
|
68
|
+
sound?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type TScaffoldPanelChild<
|
|
72
|
+
T extends ScaffoldSubPanelProps = ScaffoldSubPanelProps,
|
|
73
|
+
> = ReactElement<T, React.JSXElementConstructor<T>>
|
|
74
|
+
|
|
75
|
+
export interface ScaffoldPanelProps {
|
|
76
|
+
/** The panel's content, rendered in a card with a drop shadow. */
|
|
77
|
+
children: ReactNode
|
|
78
|
+
/** The panel's content, rendered in a card with a drop shadow. */
|
|
79
|
+
SecondaryPanel?: TScaffoldPanelChild
|
|
80
|
+
/** Renders the floating close button in the panel's top-right corner. */
|
|
81
|
+
onClose?: () => void
|
|
82
|
+
/** Renders the floating swap button over the primary/secondary gap.
|
|
83
|
+
* Only shown when `secondary` is present. Clicking it trades the two
|
|
84
|
+
* cards with a layout morph — flip the slot content here so each card
|
|
85
|
+
* carries its subject to its new home. */
|
|
86
|
+
onSwap?: () => void
|
|
87
|
+
/** Controlled swap state — while true the strip's card sits in the
|
|
88
|
+
* primary slot. Omit letting the panel toggle it on swap clicks. */
|
|
89
|
+
swapped?: boolean
|
|
90
|
+
/** Initial swap state when uncontrolled. */
|
|
91
|
+
defaultSwapped?: boolean
|
|
92
|
+
/** Fixed width in px; omitted, the panel flexes to share the canvas. */
|
|
93
|
+
width?: number
|
|
94
|
+
/** Accessible name of the panel region. */
|
|
95
|
+
name?: string
|
|
96
|
+
/** Emit interaction-sound attributes on the floating buttons. */
|
|
97
|
+
sound?: boolean
|
|
98
|
+
/** Accessible name of the close button. */
|
|
99
|
+
closeLabel?: string
|
|
100
|
+
/** Accessible name of the swap button. */
|
|
101
|
+
swapLabel?: string
|
|
102
|
+
/** Extra class names for the panel's root element. */
|
|
103
|
+
className?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type TSubPanelPosition = "bottom" | "top"
|
|
107
|
+
export type TSubPanelVariant = "card" | "subtle" | "inset"
|
|
108
|
+
|
|
109
|
+
export interface ScaffoldSubPanelProps extends MotionSafe<
|
|
110
|
+
Omit<ComponentProps<"div">, "children">
|
|
111
|
+
> {
|
|
112
|
+
children?: ReactNode
|
|
113
|
+
/** Accessible name of the sub-panel region. */
|
|
114
|
+
name?: string
|
|
115
|
+
/** Whether this is the primary or secondary sub-panel. */
|
|
116
|
+
primary?: boolean
|
|
117
|
+
/** Whether this sub-panel holds the flexible slot. Defaults to `primary` —
|
|
118
|
+
* the panel flips it when the Expand action trades the slots. */
|
|
119
|
+
expanded?: boolean
|
|
120
|
+
/** Visual style of the sub-panel. */
|
|
121
|
+
variant?: TSubPanelVariant
|
|
122
|
+
/** Fixed width in px; omitted, the sub-panel flexes to share the canvas. */
|
|
123
|
+
width?: number
|
|
124
|
+
/** Emit interaction-sound attributes on the floating buttons. */
|
|
125
|
+
sound?: boolean
|
|
126
|
+
/** Extra class names for the sub-panel's root element. */
|
|
127
|
+
className?: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ScaffoldInspectorProps extends MotionSafe<
|
|
131
|
+
Omit<ComponentProps<"aside">, "children">
|
|
132
|
+
> {
|
|
133
|
+
children?: ReactNode
|
|
134
|
+
/** Accessible name of the drawer region. */
|
|
135
|
+
name?: string
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface PanelFloatingButtonProps {
|
|
139
|
+
onClick?: () => void
|
|
140
|
+
/** Accessible name — the button is icon-only. */
|
|
141
|
+
label: string
|
|
142
|
+
sound?: boolean
|
|
143
|
+
className?: string
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface PanelMenuButtonProps extends PanelFloatingButtonProps {
|
|
147
|
+
/** Called when the Expand action is pressed — the panel trades which
|
|
148
|
+
* sub-panel holds the flexible slot. */
|
|
149
|
+
onExpand?: () => void
|
|
150
|
+
/** Whether the secondary strip currently holds the flexible slot —
|
|
151
|
+
* flips the Expand chevron between down and up. */
|
|
152
|
+
secondaryExpanded?: boolean
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface UseScaffoldOptions {
|
|
156
|
+
/** Initial inspector state. Closed by default. */
|
|
157
|
+
defaultInspectorOpen?: boolean
|
|
158
|
+
/** Fires on every inspector open/close. */
|
|
159
|
+
onInspectorChange?: (open: boolean) => void
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface ScaffoldControls {
|
|
163
|
+
inspectorOpen: boolean
|
|
164
|
+
setInspectorOpen: (open: boolean) => void
|
|
165
|
+
toggleInspector: () => void
|
|
166
|
+
/** Spread onto Scaffold.Root. */
|
|
167
|
+
providerProps: Pick<
|
|
168
|
+
ScaffoldRootProps,
|
|
169
|
+
"inspectorOpen" | "onInspectorOpenChange"
|
|
170
|
+
>
|
|
171
|
+
}
|