@exegia/corpora-ui 0.17.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.
Files changed (112) hide show
  1. package/dist-lib/components/blocks/nav/sidebar-block.d.ts +1 -1
  2. package/dist-lib/components/blocks/scaffold/__tests__/scaffold.test.d.ts +1 -0
  3. package/dist-lib/components/blocks/scaffold/constants.d.ts +20 -0
  4. package/dist-lib/components/blocks/scaffold/index.d.ts +25 -0
  5. package/dist-lib/components/blocks/scaffold/panel-close-button.d.ts +4 -0
  6. package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +7 -0
  7. package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +9 -0
  8. package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +7 -0
  9. package/dist-lib/components/blocks/scaffold/scaffold-context.d.ts +5 -0
  10. package/dist-lib/components/blocks/scaffold/scaffold-inspector.d.ts +9 -0
  11. package/dist-lib/components/blocks/scaffold/scaffold-main.d.ts +8 -0
  12. package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +10 -0
  13. package/dist-lib/components/blocks/scaffold/scaffold-root.d.ts +8 -0
  14. package/dist-lib/components/blocks/scaffold/scaffold-sidebar.d.ts +7 -0
  15. package/dist-lib/components/blocks/scaffold/scaffold-sub-panel.d.ts +3 -0
  16. package/dist-lib/components/blocks/scaffold/type.d.ts +133 -0
  17. package/dist-lib/components/blocks/scaffold/use-scaffold.d.ts +8 -0
  18. package/dist-lib/components/blocks/scaffold/utils.d.ts +15 -0
  19. package/dist-lib/components/blocks/shell/animated-panel-inset.d.ts +2 -0
  20. package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +2 -0
  21. package/dist-lib/components/blocks/shell/animated-panel-trigger.d.ts +2 -0
  22. package/dist-lib/components/blocks/shell/animated-panel.d.ts +2 -0
  23. package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +2 -0
  24. package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +2 -0
  25. package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +2 -0
  26. package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +2 -0
  27. package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +2 -0
  28. package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +2 -0
  29. package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +3 -0
  30. package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +2 -0
  31. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +3 -0
  32. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +2 -0
  33. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +2 -0
  34. package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +2 -0
  35. package/dist-lib/components/blocks/shell/index.d.ts +16 -0
  36. package/dist-lib/components/blocks/shell/type.d.ts +90 -4
  37. package/dist-lib/components/blocks/shell/utils.d.ts +37 -0
  38. package/dist-lib/components/motion/index.d.ts +1 -0
  39. package/dist-lib/components/motion/overflow-actions.d.ts +35 -0
  40. package/dist-lib/components/ui/button-group.d.ts +11 -0
  41. package/dist-lib/components/ui/glasscn/glass-button-group.d.ts +5 -0
  42. package/dist-lib/components/ui/glasscn/glass-container.d.ts +7 -0
  43. package/dist-lib/components/ui/glasscn/glass-separator.d.ts +5 -0
  44. package/dist-lib/components/ui/group.d.ts +17 -0
  45. package/dist-lib/components/ui/separator.d.ts +2 -3
  46. package/dist-lib/components/ui/sheet.d.ts +14 -0
  47. package/dist-lib/components/ui/tooltip.d.ts +6 -0
  48. package/dist-lib/index.d.ts +2 -1
  49. package/dist-lib/index.js +1592 -1084
  50. package/dist-lib/index.js.map +1 -1
  51. package/dist-lib/lib/ease.d.ts +3 -0
  52. package/dist-lib/lib/hooks/use-hover-capable.d.ts +6 -0
  53. package/package.json +2 -1
  54. package/src/components/beste/piece/browser-frame.tsx +2 -2
  55. package/src/components/blocks/nav/sidebar-block.tsx +5 -5
  56. package/src/components/blocks/nav/sidebar-nav-row.tsx +4 -5
  57. package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +203 -0
  58. package/src/components/blocks/scaffold/constants.ts +28 -0
  59. package/src/components/blocks/scaffold/index.ts +38 -0
  60. package/src/components/blocks/scaffold/panel-close-button.tsx +36 -0
  61. package/src/components/blocks/scaffold/panel-menu-button.tsx +240 -0
  62. package/src/components/blocks/scaffold/scaffold-actions.tsx +97 -0
  63. package/src/components/blocks/scaffold/scaffold-canvas.tsx +29 -0
  64. package/src/components/blocks/scaffold/scaffold-context.ts +18 -0
  65. package/src/components/blocks/scaffold/scaffold-inspector.tsx +57 -0
  66. package/src/components/blocks/scaffold/scaffold-main.tsx +28 -0
  67. package/src/components/blocks/scaffold/scaffold-panel.tsx +98 -0
  68. package/src/components/blocks/scaffold/scaffold-root.tsx +68 -0
  69. package/src/components/blocks/scaffold/scaffold-sidebar.tsx +31 -0
  70. package/src/components/blocks/scaffold/scaffold-sub-panel.tsx +51 -0
  71. package/src/components/blocks/scaffold/type.ts +171 -0
  72. package/src/components/blocks/scaffold/use-scaffold.ts +48 -0
  73. package/src/components/blocks/scaffold/utils.ts +30 -0
  74. package/src/components/blocks/shell/animated-panel-inset.tsx +23 -0
  75. package/src/components/blocks/shell/animated-panel-provider.tsx +155 -0
  76. package/src/components/blocks/shell/animated-panel-trigger.tsx +42 -0
  77. package/src/components/blocks/shell/animated-panel.tsx +135 -0
  78. package/src/components/blocks/shell/animated-sidebar-content.tsx +19 -0
  79. package/src/components/blocks/shell/animated-sidebar-footer.tsx +19 -0
  80. package/src/components/blocks/shell/animated-sidebar-group-content.tsx +16 -0
  81. package/src/components/blocks/shell/animated-sidebar-group-label.tsx +29 -0
  82. package/src/components/blocks/shell/animated-sidebar-group.tsx +16 -0
  83. package/src/components/blocks/shell/animated-sidebar-header.tsx +16 -0
  84. package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +151 -0
  85. package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +20 -0
  86. package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +85 -0
  87. package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +19 -0
  88. package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +44 -0
  89. package/src/components/blocks/shell/animated-sidebar-menu.tsx +26 -0
  90. package/src/components/blocks/shell/index.ts +18 -0
  91. package/src/components/blocks/shell/shell-layout.tsx +28 -36
  92. package/src/components/blocks/shell/type.ts +112 -6
  93. package/src/components/blocks/shell/use-shell-panels.ts +1 -1
  94. package/src/components/blocks/shell/utils.ts +142 -1
  95. package/src/components/docs/demo-controls.tsx +1 -1
  96. package/src/components/motion/index.ts +0 -0
  97. package/src/components/motion/overflow-actions.tsx +376 -0
  98. package/src/components/ui/button-group.tsx +88 -0
  99. package/src/components/ui/glasscn/glass-button-group.tsx +48 -0
  100. package/src/components/ui/glasscn/glass-container.tsx +125 -0
  101. package/src/components/ui/glasscn/glass-separator.tsx +39 -0
  102. package/src/components/ui/glasscn/liquid-glass.tsx +250 -210
  103. package/src/components/ui/group.tsx +92 -0
  104. package/src/components/ui/separator.tsx +11 -11
  105. package/src/components/ui/sheet.tsx +135 -0
  106. package/src/components/ui/tooltip.tsx +66 -0
  107. package/src/index.css +1 -1
  108. package/src/index.ts +13 -1
  109. package/src/lib/ease.ts +26 -0
  110. package/src/lib/hooks/use-hover-capable.ts +23 -0
  111. package/dist-lib/components/motion/animated-sidebar.d.ts +0 -106
  112. package/src/components/motion/animated-sidebar.tsx +0 -1171
@@ -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
+ }