@vllnt/ui 0.2.0 → 0.2.1-canary.9c473e0

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 (58) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/README.md +27 -12
  3. package/dist/components/activity-log/activity-log.js +1 -0
  4. package/dist/components/anchor-port/anchor-port.js +51 -0
  5. package/dist/components/anchor-port/index.js +4 -0
  6. package/dist/components/animated-text/animated-text.js +1 -0
  7. package/dist/components/bottom-bar/bottom-bar.js +25 -0
  8. package/dist/components/bottom-bar/index.js +4 -0
  9. package/dist/components/canvas-shell/canvas-foundation-demo.js +183 -0
  10. package/dist/components/canvas-shell/canvas-shell-route-config.js +0 -0
  11. package/dist/components/canvas-shell/canvas-shell.js +261 -0
  12. package/dist/components/canvas-shell/index.js +4 -0
  13. package/dist/components/canvas-view/canvas-view.js +461 -0
  14. package/dist/components/canvas-view/index.js +6 -0
  15. package/dist/components/chart/area-chart.js +1 -0
  16. package/dist/components/chart/line-chart.js +1 -0
  17. package/dist/components/chat-dock-section/chat-dock-section.js +56 -0
  18. package/dist/components/chat-dock-section/index.js +6 -0
  19. package/dist/components/connector-edge/connector-edge.js +66 -0
  20. package/dist/components/connector-edge/index.js +6 -0
  21. package/dist/components/data-list/data-list.js +1 -0
  22. package/dist/components/edge-label/edge-label.js +26 -0
  23. package/dist/components/edge-label/index.js +4 -0
  24. package/dist/components/form/form.js +263 -0
  25. package/dist/components/form/index.js +16 -0
  26. package/dist/components/glass-panel/glass-panel.js +21 -0
  27. package/dist/components/glass-panel/index.js +4 -0
  28. package/dist/components/group-hull/group-hull.js +29 -0
  29. package/dist/components/group-hull/index.js +4 -0
  30. package/dist/components/index.js +88 -0
  31. package/dist/components/left-rail/index.js +4 -0
  32. package/dist/components/left-rail/left-rail.js +25 -0
  33. package/dist/components/mini-map-panel/index.js +6 -0
  34. package/dist/components/mini-map-panel/mini-map-panel.js +74 -0
  35. package/dist/components/multi-select/index.js +6 -0
  36. package/dist/components/multi-select/multi-select.js +258 -0
  37. package/dist/components/object-card/index.js +6 -0
  38. package/dist/components/object-card/object-card.js +126 -0
  39. package/dist/components/object-handle/index.js +4 -0
  40. package/dist/components/object-handle/object-handle.js +38 -0
  41. package/dist/components/overview-board/index.js +8 -0
  42. package/dist/components/overview-board/overview-board.js +127 -0
  43. package/dist/components/right-dock/index.js +4 -0
  44. package/dist/components/right-dock/right-dock.js +28 -0
  45. package/dist/components/segmented-control/index.js +12 -0
  46. package/dist/components/segmented-control/segmented-control.js +61 -0
  47. package/dist/components/spinner/unicode-spinner.js +1 -0
  48. package/dist/components/tags-input/index.js +4 -0
  49. package/dist/components/tags-input/tags-input.js +178 -0
  50. package/dist/components/top-bar/index.js +4 -0
  51. package/dist/components/top-bar/top-bar.js +31 -0
  52. package/dist/components/usage-breakdown/usage-breakdown.js +1 -0
  53. package/dist/components/workspace-switcher/index.js +6 -0
  54. package/dist/components/workspace-switcher/workspace-switcher.js +61 -0
  55. package/dist/components/zoom-hud/index.js +4 -0
  56. package/dist/components/zoom-hud/zoom-hud.js +61 -0
  57. package/dist/index.d.ts +455 -5
  58. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,11 +4,21 @@ All notable changes to `@vllnt/ui` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.2.1] - 2026-04-21
8
+
9
+ ### Fixed
10
+
11
+ - **Public API:** `ProgressCard` now re-exported from `@vllnt/ui` — it shipped in `0.2.0`'s tarball but was missing from the barrel, so `import { ProgressCard } from "@vllnt/ui"` resolved to `undefined`.
12
+
13
+ ### Docs
14
+
15
+ - Correct casing for the AI family (`AIChatInput`, `AIMessageBubble`, `AISourceCitation`, `AIStreamingText`, `AIToolCallDisplay`) and `SocialFAB` across README, package README, CHANGELOG, and `llms-full.txt`.
16
+
7
17
  ## [0.2.0] - 2026-04-21
8
18
 
9
19
  ### Added
10
20
 
11
- - **AI family** — `AiChatInput`, `AiMessageBubble`, `AiSourceCitation`, `AiStreamingText`, `AiToolCallDisplay`, `ThinkingBlock`, `ModelSelector`.
21
+ - **AI family** — `AIChatInput`, `AIMessageBubble`, `AISourceCitation`, `AIStreamingText`, `AIToolCallDisplay`, `ThinkingBlock`, `ModelSelector`.
12
22
  - **Financial family** — `CandlestickChart`, `MarketTreemap`, `OrderBook`, `TickerTape`, `SparklineGrid`, `WalletCard`, `Watchlist`.
13
23
  - **Ops / Status family** — `StatusBoard`, `StatusIndicator`, `LiveFeed`, `WorldClockBar`, `SeverityBadge`, `RoleBadge`, `ScopeSelector`.
14
24
  - **Billing & Plans family** — `SubscriptionCard`, `PlanBadge`, `CreditBadge`, `UsageBreakdown`.
@@ -96,6 +106,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
96
106
 
97
107
  - Initial public publish to the public npm registry.
98
108
 
109
+ [0.2.1]: https://github.com/vllnt/ui/releases/tag/v0.2.1
99
110
  [0.2.0]: https://github.com/vllnt/ui/releases/tag/v0.2.0
100
111
  [0.1.11]: https://github.com/vllnt/ui/releases/tag/v0.1.11
101
112
  [0.1.10]: https://github.com/vllnt/ui/releases/tag/v0.1.10
package/README.md CHANGED
@@ -218,7 +218,7 @@ import {
218
218
  | `Alert` | `{ Alert, AlertTitle, AlertDescription, alertVariants }` | Static alert banner |
219
219
  | `Badge` | `{ Badge, badgeVariants }` | Inline status label. Variants: default, secondary, destructive, outline |
220
220
  | `Skeleton` | `{ Skeleton }` | Loading placeholder |
221
- | `Spinner` | `{ Spinner }` | Loading spinner |
221
+ | `Spinner` | `{ Spinner, UnicodeSpinner }` | Styled spinner + ASCII-art `UnicodeSpinner` for terminals / code blocks |
222
222
 
223
223
  ### Navigation
224
224
 
@@ -242,7 +242,7 @@ import {
242
242
  | `BarChart` | `{ BarChart }` | Chart component |
243
243
  | `LineChart` | `{ LineChart }` | Chart component |
244
244
  | `CodeBlock` | `{ CodeBlock }` | Syntax-highlighted code via `react-syntax-highlighter` |
245
- | `FlowDiagram` | `{ FlowDiagram }` | Flow diagram via `@xyflow/react` |
245
+ | `FlowDiagram` | `{ FlowDiagram, FlowControls, FlowFullscreen, FlowErrorBoundary, useFlowDiagram }` | Flow diagram via `@xyflow/react`. Extras: custom controls, fullscreen wrapper, error boundary, and the `useFlowDiagram` hook for imperative access. |
246
246
  | `TableOfContents` | `{ TableOfContents }` | Page table of contents |
247
247
 
248
248
  ### App Components
@@ -276,7 +276,10 @@ import {
276
276
  | `Flashcard` | `{ Flashcard }` | Flip-card for spaced repetition |
277
277
  | `Stepper` | `{ Stepper }` | Linear progress stepper |
278
278
  | `Tour` | `{ Tour }` | Guided product tour |
279
- | `Annotation` | `{ Annotation }` | Inline annotation / highlight |
279
+ | `Annotation` | `{ Annotation, Highlight }` | Inline annotation block + inline `Highlight` span for in-prose emphasis |
280
+ | `LearningObjectives` | `{ LearningObjectives, Prerequisites, Summary }` | Objectives list plus companion `Prerequisites` and `Summary` blocks |
281
+ | `KeyConcept` | `{ KeyConcept, Glossary }` | Concept callout + `Glossary` term list |
282
+ | `Comparison` | `{ Comparison, BeforeAfter }` | Side-by-side compare block + `BeforeAfter` slider |
280
283
  | `CompletionDialog` | `{ CompletionDialog }` | End-of-flow celebration dialog |
281
284
  | `TruncatedText` | `{ TruncatedText }` | Expand-on-overflow text block |
282
285
  | `TableOfContentsPanel` | `{ TableOfContentsPanel }` | Sidebar TOC panel |
@@ -286,11 +289,11 @@ import {
286
289
 
287
290
  | Component | Import | Notes |
288
291
  |-----------|--------|-------|
289
- | `AiChatInput` | `{ AiChatInput }` | Chat composer with submit + attachments |
290
- | `AiMessageBubble` | `{ AiMessageBubble }` | User/assistant message bubble |
291
- | `AiStreamingText` | `{ AiStreamingText }` | Token-stream renderer |
292
- | `AiToolCallDisplay` | `{ AiToolCallDisplay }` | Tool-use invocation render |
293
- | `AiSourceCitation` | `{ AiSourceCitation }` | Inline citation reference |
292
+ | `AIChatInput` | `{ AIChatInput }` | Chat composer with submit + attachments |
293
+ | `AIMessageBubble` | `{ AIMessageBubble }` | User/assistant message bubble |
294
+ | `AIStreamingText` | `{ AIStreamingText }` | Token-stream renderer |
295
+ | `AIToolCallDisplay` | `{ AIToolCallDisplay }` | Tool-use invocation render |
296
+ | `AISourceCitation` | `{ AISourceCitation }` | Inline citation reference |
294
297
  | `ModelSelector` | `{ ModelSelector }` | Model picker (provider-agnostic) |
295
298
 
296
299
  ### Financial
@@ -334,7 +337,7 @@ import {
334
337
  | `BorderBeam` | `{ BorderBeam }` | Animated gradient border |
335
338
  | `Marquee` | `{ Marquee }` | Infinite scroll marquee |
336
339
  | `NumberTicker` | `{ NumberTicker }` | Animated number counter |
337
- | `Spinner` | `{ Spinner }` | Rich loading-spinner library |
340
+ | `Spinner` | `{ Spinner, UnicodeSpinner }` | Styled spinner + ASCII-art `UnicodeSpinner` for terminals / code blocks |
338
341
 
339
342
  ### Form Additions
340
343
 
@@ -371,7 +374,7 @@ import {
371
374
  | `CountdownTimer` | `{ CountdownTimer }` | Event countdown |
372
375
  | `AvatarGroup` | `{ AvatarGroup }` | Stacked avatar group |
373
376
  | `FloatingActionButton` | `{ FloatingActionButton }` | Anchored FAB |
374
- | `SocialFab` | `{ SocialFab }` | Social-links FAB |
377
+ | `SocialFAB` | `{ SocialFab }` | Social-links FAB |
375
378
  | `KeyboardShortcutsHelp` | `{ KeyboardShortcutsHelp }` | Cmd-? shortcut dialog |
376
379
  | `ShareDialog` | `{ ShareDialog }` | Shareable-link dialog |
377
380
  | `HorizontalScrollRow` | `{ HorizontalScrollRow }` | Scroll-snap row |
@@ -386,10 +389,22 @@ import { cn } from "@vllnt/ui";
386
389
  <div className={cn("p-4 bg-primary", isActive && "bg-accent", className)} />
387
390
  ```
388
391
 
392
+ ### Hooks
393
+
394
+ | Hook | Purpose |
395
+ |------|---------|
396
+ | `useDebounce(value, delayMs)` | Returns the value after `delayMs` of idle — ideal for search inputs. |
397
+ | `useHorizontalScroll(ref)` | Drives the behavior used by `HorizontalScrollRow` — wheel → horizontal scroll, scroll-snap helpers. |
398
+ | `useMobile(breakpoint?)` | Boolean for "viewport is below breakpoint" (default `768`). SSR-safe. |
399
+ | `useSidebar()` | Reads `SidebarProvider` state — `{ open, setOpen, toggle }`. Throws outside a provider. |
400
+ | `useFlowDiagram(options)` | Imperative controller for `FlowDiagram` — fit view, zoom, export to PNG. |
401
+ | `useSocialFab()` | Drives the open/close state of `SocialFab`. |
402
+
389
403
  ```tsx
390
- import { useDebounce } from "@vllnt/ui";
404
+ import { useDebounce, useMobile } from "@vllnt/ui";
391
405
 
392
- const debouncedValue = useDebounce(searchTerm, 300);
406
+ const query = useDebounce(input, 300);
407
+ const isMobile = useMobile();
393
408
  ```
394
409
 
395
410
  ## Types
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
3
  import { forwardRef, useMemo, useState } from "react";
3
4
  import { ArrowRight, ChevronLeft, ChevronRight } from "lucide-react";
@@ -0,0 +1,51 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../../lib/utils";
4
+ const toneClasses = {
5
+ bidirectional: "border-violet-500/40 bg-violet-500/15 text-violet-700 dark:text-violet-300",
6
+ input: "border-sky-500/40 bg-sky-500/15 text-sky-700 dark:text-sky-300",
7
+ output: "border-emerald-500/40 bg-emerald-500/15 text-emerald-700 dark:text-emerald-300"
8
+ };
9
+ const stateClasses = {
10
+ active: "scale-100 opacity-100",
11
+ blocked: "opacity-60 saturate-50",
12
+ idle: "opacity-80"
13
+ };
14
+ const sideClasses = {
15
+ bottom: "self-end",
16
+ left: "self-start",
17
+ right: "self-end",
18
+ top: "self-start"
19
+ };
20
+ const AnchorPort = forwardRef(
21
+ ({
22
+ className,
23
+ side = "right",
24
+ state = "idle",
25
+ tone = "bidirectional",
26
+ ...props
27
+ }, ref) => /* @__PURE__ */ jsx(
28
+ "span",
29
+ {
30
+ "aria-label": props["aria-label"] ?? `${tone} ${side} port ${state}`,
31
+ className: cn(
32
+ "inline-flex size-7 items-center justify-center rounded-full border shadow-sm",
33
+ toneClasses[tone],
34
+ stateClasses[state],
35
+ sideClasses[side],
36
+ className
37
+ ),
38
+ "data-side": side,
39
+ "data-state": state,
40
+ "data-tone": tone,
41
+ ref,
42
+ role: "img",
43
+ ...props,
44
+ children: /* @__PURE__ */ jsx("span", { className: "size-2.5 rounded-full bg-current" })
45
+ }
46
+ )
47
+ );
48
+ AnchorPort.displayName = "AnchorPort";
49
+ export {
50
+ AnchorPort
51
+ };
@@ -0,0 +1,4 @@
1
+ import { AnchorPort } from "./anchor-port";
2
+ export {
3
+ AnchorPort
4
+ };
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
3
  import * as React from "react";
3
4
  import { cn } from "../../lib/utils";
@@ -0,0 +1,25 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../../lib/utils";
4
+ const BottomBar = forwardRef(
5
+ ({ center, className, leading, trailing, ...props }, ref) => /* @__PURE__ */ jsxs(
6
+ "div",
7
+ {
8
+ className: cn(
9
+ "flex min-h-14 items-center gap-3 px-4 py-3 text-sm",
10
+ className
11
+ ),
12
+ ref,
13
+ ...props,
14
+ children: [
15
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: leading }),
16
+ center ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center justify-center gap-2", children: center }) : null,
17
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 items-center justify-end gap-2", children: trailing })
18
+ ]
19
+ }
20
+ )
21
+ );
22
+ BottomBar.displayName = "BottomBar";
23
+ export {
24
+ BottomBar
25
+ };
@@ -0,0 +1,4 @@
1
+ import { BottomBar } from "./bottom-bar";
2
+ export {
3
+ BottomBar
4
+ };
@@ -0,0 +1,183 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Activity, Bot, Compass, Layers3, Sparkles } from "lucide-react";
3
+ import { BottomBar } from "../bottom-bar";
4
+ import { Button } from "../button";
5
+ import { CanvasView } from "../canvas-view";
6
+ import { ChatDockSection } from "../chat-dock-section";
7
+ import { GlassPanel } from "../glass-panel";
8
+ import { LeftRail } from "../left-rail";
9
+ import { RightDock } from "../right-dock";
10
+ import { TopBar } from "../top-bar";
11
+ import { WorkspaceSwitcher } from "../workspace-switcher";
12
+ import { CanvasShell } from "./canvas-shell";
13
+ function DemoLeftBar() {
14
+ return /* @__PURE__ */ jsx(GlassPanel, { className: "overflow-hidden", children: /* @__PURE__ */ jsxs(
15
+ LeftRail,
16
+ {
17
+ className: "w-[5rem] border-0 bg-transparent px-3 py-4",
18
+ footer: /* @__PURE__ */ jsx(Button, { "aria-label": "Runs", size: "icon", type: "button", variant: "ghost", children: /* @__PURE__ */ jsx(Activity, { className: "size-4" }) }),
19
+ title: "Mode",
20
+ children: [
21
+ /* @__PURE__ */ jsx(
22
+ Button,
23
+ {
24
+ "aria-label": "Overview",
25
+ size: "icon",
26
+ type: "button",
27
+ variant: "secondary",
28
+ children: /* @__PURE__ */ jsx(Compass, { className: "size-4" })
29
+ }
30
+ ),
31
+ /* @__PURE__ */ jsx(Button, { "aria-label": "Objects", size: "icon", type: "button", variant: "ghost", children: /* @__PURE__ */ jsx(Layers3, { className: "size-4" }) }),
32
+ /* @__PURE__ */ jsx(Button, { "aria-label": "Agents", size: "icon", type: "button", variant: "ghost", children: /* @__PURE__ */ jsx(Bot, { className: "size-4" }) })
33
+ ]
34
+ }
35
+ ) });
36
+ }
37
+ function DemoTopBar() {
38
+ return /* @__PURE__ */ jsx(GlassPanel, { children: /* @__PURE__ */ jsx(
39
+ TopBar,
40
+ {
41
+ className: "border-0 bg-transparent px-5 font-sans",
42
+ leading: /* @__PURE__ */ jsx("div", { className: "flex size-9 items-center justify-center rounded-xl bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(Sparkles, { className: "size-4" }) }),
43
+ subtitle: "Calm operating surface",
44
+ title: "Operator workspace",
45
+ trailing: /* @__PURE__ */ jsx(Button, { size: "sm", type: "button", variant: "outline", children: "Open command" }),
46
+ children: /* @__PURE__ */ jsx(
47
+ WorkspaceSwitcher,
48
+ {
49
+ className: "bg-background/60",
50
+ defaultValue: "orchestrate",
51
+ workspaces: [
52
+ { id: "orchestrate", label: "Orchestrate" },
53
+ { id: "objects", label: "Objects" },
54
+ { id: "signals", label: "Signals" }
55
+ ]
56
+ }
57
+ )
58
+ }
59
+ ) });
60
+ }
61
+ function DemoRightBar() {
62
+ return /* @__PURE__ */ jsx(GlassPanel, { className: "h-full overflow-hidden", children: /* @__PURE__ */ jsx(
63
+ RightDock,
64
+ {
65
+ className: "h-full min-w-[22rem] max-w-[22rem] border-0 bg-transparent",
66
+ footer: /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Chat stays contextual and secondary to the center workspace." }),
67
+ header: /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Context shifts by route while the shell stays stable." }),
68
+ title: "Context",
69
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
70
+ /* @__PURE__ */ jsx(
71
+ ChatDockSection,
72
+ {
73
+ contextLabel: "Today \xB7 overview",
74
+ messages: [
75
+ {
76
+ body: "Three failed runs came in overnight. Start with the invoice retry and the security digest.",
77
+ id: "assistant",
78
+ speaker: "Assistant"
79
+ },
80
+ {
81
+ body: "Queue the approvals first, then review the stale automations after lunch.",
82
+ id: "operator",
83
+ speaker: "Operator"
84
+ }
85
+ ]
86
+ }
87
+ ),
88
+ /* @__PURE__ */ jsxs("div", { className: "rounded-2xl border border-border/70 bg-background/75 p-4 shadow-[0_10px_35px_hsl(var(--foreground)/0.06)] backdrop-blur-xl", children: [
89
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.24em] text-muted-foreground", children: "Selected context" }),
90
+ /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm font-medium text-foreground", children: "Inbox triage" }),
91
+ /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm leading-6 text-muted-foreground", children: "Landing route keeps the assistant close to the operational queue instead of taking over the center canvas." })
92
+ ] })
93
+ ] })
94
+ }
95
+ ) });
96
+ }
97
+ function DemoBottomBar() {
98
+ return /* @__PURE__ */ jsx(GlassPanel, { children: /* @__PURE__ */ jsx(
99
+ BottomBar,
100
+ {
101
+ center: /* @__PURE__ */ jsxs(Fragment, { children: [
102
+ /* @__PURE__ */ jsx("div", { className: "rounded-full border border-border/70 px-3 py-1 text-xs text-muted-foreground", children: "3 errors" }),
103
+ /* @__PURE__ */ jsx("div", { className: "rounded-full border border-border/70 px-3 py-1 text-xs text-muted-foreground", children: "7 awaiting action" })
104
+ ] }),
105
+ leading: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
106
+ /* @__PURE__ */ jsx(Activity, { className: "size-4" }),
107
+ "System healthy enough to proceed"
108
+ ] }),
109
+ trailing: /* @__PURE__ */ jsx(Button, { size: "sm", type: "button", variant: "ghost", children: "Open inbox" })
110
+ }
111
+ ) });
112
+ }
113
+ function DemoCanvasObjects() {
114
+ return /* @__PURE__ */ jsxs("div", { className: "relative h-[1200px] w-[1600px] overflow-hidden rounded-[28px] border border-border/12 bg-[radial-gradient(circle_at_top,hsl(var(--primary)/0.07),transparent_38%),linear-gradient(180deg,hsl(var(--background)/0.18),hsl(var(--background)/0.04))]", children: [
115
+ /* @__PURE__ */ jsxs("div", { className: "absolute left-[14%] top-[22%] w-72 rounded-[2rem] border border-border/18 bg-background/18 p-5 shadow-[0_22px_80px_hsl(var(--foreground)/0.05)] backdrop-blur-xl", children: [
116
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
117
+ /* @__PURE__ */ jsx("div", { className: "flex size-11 items-center justify-center rounded-2xl bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(Layers3, { className: "size-5" }) }),
118
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-primary/50" })
119
+ ] }),
120
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 space-y-3", children: [
121
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded-full bg-foreground/10" }),
122
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
123
+ /* @__PURE__ */ jsx("div", { className: "h-20 rounded-[1.5rem] border border-border/18 bg-background/35" }),
124
+ /* @__PURE__ */ jsx("div", { className: "h-20 rounded-[1.5rem] border border-border/18 bg-background/28" })
125
+ ] }),
126
+ /* @__PURE__ */ jsx("div", { className: "h-10 rounded-[1.25rem] border border-border/18 bg-background/30" })
127
+ ] })
128
+ ] }),
129
+ /* @__PURE__ */ jsxs("div", { className: "absolute right-[15%] top-[18%] w-56 rounded-[1.75rem] border border-border/18 bg-background/14 p-4 shadow-[0_18px_60px_hsl(var(--foreground)/0.04)] backdrop-blur-xl", children: [
130
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
131
+ /* @__PURE__ */ jsx("div", { className: "flex size-10 items-center justify-center rounded-2xl bg-background/60 text-foreground/70", children: /* @__PURE__ */ jsx(Activity, { className: "size-4" }) }),
132
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
133
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-foreground/10" }),
134
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-foreground/10" }),
135
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-primary/45" })
136
+ ] })
137
+ ] }),
138
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-3", children: [
139
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded-full bg-foreground/10" }),
140
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
141
+ /* @__PURE__ */ jsx("div", { className: "h-11 rounded-[1rem] border border-border/18 bg-background/32" }),
142
+ /* @__PURE__ */ jsx("div", { className: "h-11 rounded-[1rem] border border-border/18 bg-background/24" }),
143
+ /* @__PURE__ */ jsx("div", { className: "h-11 rounded-[1rem] border border-border/18 bg-background/18" })
144
+ ] })
145
+ ] })
146
+ ] }),
147
+ /* @__PURE__ */ jsxs("div", { className: "absolute bottom-[18%] left-[22%] w-64 rounded-[1.75rem] border border-border/18 bg-background/12 p-4 shadow-[0_18px_60px_hsl(var(--foreground)/0.04)] backdrop-blur-xl", children: [
148
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
149
+ /* @__PURE__ */ jsx("div", { className: "flex size-10 items-center justify-center rounded-2xl bg-background/55 text-foreground/70", children: /* @__PURE__ */ jsx(Sparkles, { className: "size-4" }) }),
150
+ /* @__PURE__ */ jsx("div", { className: "h-7 w-16 rounded-full border border-border/18 bg-background/38" })
151
+ ] }),
152
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 grid grid-cols-3 gap-2", children: [
153
+ /* @__PURE__ */ jsx("div", { className: "h-16 rounded-[1rem] border border-border/18 bg-background/26" }),
154
+ /* @__PURE__ */ jsx("div", { className: "h-16 rounded-[1rem] border border-border/18 bg-background/20" }),
155
+ /* @__PURE__ */ jsx("div", { className: "h-16 rounded-[1rem] border border-border/18 bg-background/14" })
156
+ ] })
157
+ ] })
158
+ ] });
159
+ }
160
+ function CanvasFoundationDemo() {
161
+ return /* @__PURE__ */ jsx(
162
+ CanvasShell,
163
+ {
164
+ bottomBar: /* @__PURE__ */ jsx(DemoBottomBar, {}),
165
+ className: "h-[100dvh] min-h-[720px]",
166
+ contentPadding: { bottom: 120, left: 112, right: 392, top: 112 },
167
+ leftBar: /* @__PURE__ */ jsx(DemoLeftBar, {}),
168
+ rightBar: /* @__PURE__ */ jsx(DemoRightBar, {}),
169
+ topBar: /* @__PURE__ */ jsx(DemoTopBar, {}),
170
+ children: /* @__PURE__ */ jsx(
171
+ CanvasView,
172
+ {
173
+ className: "h-full rounded-none border-0 bg-background/25",
174
+ defaultViewport: { x: 0, y: 0, zoom: 1 },
175
+ children: /* @__PURE__ */ jsx(DemoCanvasObjects, {})
176
+ }
177
+ )
178
+ }
179
+ );
180
+ }
181
+ export {
182
+ CanvasFoundationDemo
183
+ };
@@ -0,0 +1,261 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../../lib/utils";
4
+ function toInsetValue(value) {
5
+ if (typeof value === "number") {
6
+ return `${value}px`;
7
+ }
8
+ return value;
9
+ }
10
+ const FLOATING_BOTTOM_BAR_FOOTPRINT = "3.5rem";
11
+ const FLOATING_LEFT_BAR_FOOTPRINT = "4.5rem";
12
+ const FLOATING_RIGHT_BAR_FOOTPRINT = "18rem";
13
+ const FLOATING_TOP_BAR_FOOTPRINT = "3.5rem";
14
+ function getReservedInset(inset, footprint, override) {
15
+ return toInsetValue(override) ?? `calc(${inset} + ${footprint})`;
16
+ }
17
+ function getSafeAreaInsets({
18
+ chromeInset,
19
+ contentPadding,
20
+ hasBottomBar,
21
+ hasLeftBar,
22
+ hasRightBar,
23
+ hasTopBar
24
+ }) {
25
+ const inset = toInsetValue(chromeInset) ?? "16px";
26
+ return {
27
+ bottom: hasBottomBar ? getReservedInset(
28
+ inset,
29
+ FLOATING_BOTTOM_BAR_FOOTPRINT,
30
+ contentPadding?.bottom
31
+ ) : toInsetValue(contentPadding?.bottom) ?? inset,
32
+ left: hasLeftBar ? getReservedInset(
33
+ inset,
34
+ FLOATING_LEFT_BAR_FOOTPRINT,
35
+ contentPadding?.left
36
+ ) : toInsetValue(contentPadding?.left) ?? inset,
37
+ right: hasRightBar ? getReservedInset(
38
+ inset,
39
+ FLOATING_RIGHT_BAR_FOOTPRINT,
40
+ contentPadding?.right
41
+ ) : toInsetValue(contentPadding?.right) ?? inset,
42
+ top: hasTopBar ? getReservedInset(inset, FLOATING_TOP_BAR_FOOTPRINT, contentPadding?.top) : toInsetValue(contentPadding?.top) ?? inset
43
+ };
44
+ }
45
+ function getSafeAreaStyle(insets) {
46
+ return {
47
+ "--canvas-shell-safe-bottom": insets.bottom,
48
+ "--canvas-shell-safe-left": insets.left,
49
+ "--canvas-shell-safe-right": insets.right,
50
+ "--canvas-shell-safe-top": insets.top
51
+ };
52
+ }
53
+ const hasChromeContent = Boolean;
54
+ function CanvasShellChromeBefore({
55
+ inset,
56
+ leftBar,
57
+ topBar
58
+ }) {
59
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
60
+ hasChromeContent(topBar) ? /* @__PURE__ */ jsx(
61
+ "div",
62
+ {
63
+ className: "pointer-events-none absolute inset-x-0 z-30",
64
+ style: { top: inset },
65
+ children: /* @__PURE__ */ jsx(
66
+ "div",
67
+ {
68
+ className: "pointer-events-auto mx-auto w-full max-w-[960px]",
69
+ style: { paddingLeft: inset, paddingRight: inset },
70
+ children: topBar
71
+ }
72
+ )
73
+ }
74
+ ) : null,
75
+ hasChromeContent(leftBar) ? /* @__PURE__ */ jsx(
76
+ "div",
77
+ {
78
+ className: "pointer-events-none absolute left-0 z-30 flex",
79
+ style: {
80
+ bottom: "var(--canvas-shell-safe-bottom)",
81
+ left: inset,
82
+ top: "var(--canvas-shell-safe-top)"
83
+ },
84
+ children: /* @__PURE__ */ jsx("div", { className: "pointer-events-auto flex", children: leftBar })
85
+ }
86
+ ) : null
87
+ ] });
88
+ }
89
+ function CanvasShellChromeAfter({
90
+ bottomBar,
91
+ inset,
92
+ rightBar
93
+ }) {
94
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
95
+ hasChromeContent(rightBar) ? /* @__PURE__ */ jsx(
96
+ "div",
97
+ {
98
+ className: "pointer-events-none absolute right-0 z-30 flex",
99
+ style: {
100
+ bottom: "var(--canvas-shell-safe-bottom)",
101
+ right: inset,
102
+ top: "var(--canvas-shell-safe-top)"
103
+ },
104
+ children: /* @__PURE__ */ jsx("div", { className: "pointer-events-auto flex", children: rightBar })
105
+ }
106
+ ) : null,
107
+ hasChromeContent(bottomBar) ? /* @__PURE__ */ jsx(
108
+ "div",
109
+ {
110
+ className: "pointer-events-none absolute inset-x-0 z-30",
111
+ style: { bottom: inset },
112
+ children: /* @__PURE__ */ jsx(
113
+ "div",
114
+ {
115
+ className: "pointer-events-auto mx-auto w-full max-w-[960px]",
116
+ style: { paddingLeft: inset, paddingRight: inset },
117
+ children: bottomBar
118
+ }
119
+ )
120
+ }
121
+ ) : null
122
+ ] });
123
+ }
124
+ function renderLegacyCanvasShell({
125
+ bottomBar: _bottomBar,
126
+ bottomSlot,
127
+ children,
128
+ chromeInset: _chromeInset = 16,
129
+ className,
130
+ contentPadding: _contentPadding,
131
+ leftBar: _leftBar,
132
+ leftRail,
133
+ rightBar: _rightBar,
134
+ rightDock,
135
+ style,
136
+ topBar,
137
+ ...props
138
+ }, ref) {
139
+ return /* @__PURE__ */ jsxs(
140
+ "section",
141
+ {
142
+ className: cn(
143
+ "flex min-h-[720px] w-full flex-col overflow-hidden rounded-md border border-border bg-background",
144
+ className
145
+ ),
146
+ ref,
147
+ style,
148
+ ...props,
149
+ children: [
150
+ topBar,
151
+ /* @__PURE__ */ jsxs("div", { className: "grid min-h-0 flex-1 grid-cols-[auto_minmax(0,1fr)_auto] overflow-hidden bg-background", children: [
152
+ leftRail ?? /* @__PURE__ */ jsx("div", {}),
153
+ /* @__PURE__ */ jsx("div", { className: "relative min-h-0 min-w-0 overflow-hidden", children }),
154
+ rightDock ?? /* @__PURE__ */ jsx("div", {})
155
+ ] }),
156
+ bottomSlot ? /* @__PURE__ */ jsx("div", { className: "border-t border-border bg-background px-4 py-2", children: bottomSlot }) : null
157
+ ]
158
+ }
159
+ );
160
+ }
161
+ function renderFloatingContent(children, contentStyle) {
162
+ return /* @__PURE__ */ jsx(
163
+ "div",
164
+ {
165
+ className: "relative z-0 h-full w-full min-h-0 min-w-0",
166
+ "data-slot": "canvas-shell-content",
167
+ style: contentStyle,
168
+ children: /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-0 min-w-0 overflow-hidden", children })
169
+ }
170
+ );
171
+ }
172
+ function renderFloatingCanvasShell({
173
+ bottomBar,
174
+ bottomSlot,
175
+ children,
176
+ chromeInset = 16,
177
+ className,
178
+ contentPadding,
179
+ leftBar,
180
+ leftRail,
181
+ rightBar,
182
+ rightDock,
183
+ style,
184
+ topBar,
185
+ ...props
186
+ }, ref) {
187
+ const inset = toInsetValue(chromeInset) ?? "16px";
188
+ const resolvedBottomBar = bottomBar ?? bottomSlot;
189
+ const resolvedLeftBar = leftBar ?? leftRail;
190
+ const resolvedRightBar = rightBar ?? rightDock;
191
+ const hasTopBar = hasChromeContent(topBar);
192
+ const hasLeftBar = hasChromeContent(resolvedLeftBar);
193
+ const hasRightBar = hasChromeContent(resolvedRightBar);
194
+ const hasBottomBar = hasChromeContent(resolvedBottomBar);
195
+ const safeAreaInsets = getSafeAreaInsets({
196
+ chromeInset,
197
+ contentPadding,
198
+ hasBottomBar,
199
+ hasLeftBar,
200
+ hasRightBar,
201
+ hasTopBar
202
+ });
203
+ const mergedStyle = {
204
+ ...getSafeAreaStyle(safeAreaInsets),
205
+ ...style
206
+ };
207
+ const contentStyle = {
208
+ paddingBottom: "var(--canvas-shell-safe-bottom)",
209
+ paddingLeft: "var(--canvas-shell-safe-left)",
210
+ paddingRight: "var(--canvas-shell-safe-right)",
211
+ paddingTop: "var(--canvas-shell-safe-top)"
212
+ };
213
+ return /* @__PURE__ */ jsxs(
214
+ "section",
215
+ {
216
+ className: cn(
217
+ "relative isolate flex min-h-[720px] w-full overflow-hidden bg-[radial-gradient(circle_at_top,hsl(var(--background)/0.94),hsl(var(--muted)/0.6))]",
218
+ className
219
+ ),
220
+ ref,
221
+ style: mergedStyle,
222
+ ...props,
223
+ children: [
224
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-[linear-gradient(180deg,hsl(var(--background)/0.94),hsl(var(--background)/0.8))]" }),
225
+ /* @__PURE__ */ jsx(
226
+ CanvasShellChromeBefore,
227
+ {
228
+ inset,
229
+ leftBar: hasLeftBar ? resolvedLeftBar : void 0,
230
+ topBar: hasTopBar ? topBar : void 0
231
+ }
232
+ ),
233
+ renderFloatingContent(children, contentStyle),
234
+ /* @__PURE__ */ jsx(
235
+ CanvasShellChromeAfter,
236
+ {
237
+ bottomBar: hasBottomBar ? resolvedBottomBar : void 0,
238
+ inset,
239
+ rightBar: hasRightBar ? resolvedRightBar : void 0
240
+ }
241
+ )
242
+ ]
243
+ }
244
+ );
245
+ }
246
+ const CanvasShell = forwardRef((props, ref) => {
247
+ const { bottomBar, chromeInset, contentPadding, leftBar, rightBar } = props;
248
+ const hasExplicitChromeInset = Object.prototype.hasOwnProperty.call(
249
+ props,
250
+ "chromeInset"
251
+ );
252
+ const usesFloatingChrome = hasChromeContent(bottomBar) || hasChromeContent(leftBar) || hasChromeContent(rightBar) || contentPadding !== void 0 || hasExplicitChromeInset && chromeInset !== void 0;
253
+ if (!usesFloatingChrome) {
254
+ return renderLegacyCanvasShell(props, ref);
255
+ }
256
+ return renderFloatingCanvasShell(props, ref);
257
+ });
258
+ CanvasShell.displayName = "CanvasShell";
259
+ export {
260
+ CanvasShell
261
+ };