@vllnt/ui 0.2.0 → 0.2.1-canary.73a93ee

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 (86) 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/checklist/checklist.js +7 -0
  20. package/dist/components/checklist/index.js +3 -1
  21. package/dist/components/comment-pin/comment-pin.js +104 -0
  22. package/dist/components/comment-pin/index.js +6 -0
  23. package/dist/components/connector-edge/connector-edge.js +66 -0
  24. package/dist/components/connector-edge/index.js +6 -0
  25. package/dist/components/conversation-thread/conversation-thread.js +348 -0
  26. package/dist/components/conversation-thread/index.js +20 -0
  27. package/dist/components/curriculum/curriculum.js +349 -0
  28. package/dist/components/curriculum/index.js +10 -0
  29. package/dist/components/data-list/data-list.js +1 -0
  30. package/dist/components/edge-label/edge-label.js +26 -0
  31. package/dist/components/edge-label/index.js +4 -0
  32. package/dist/components/form/form.js +432 -0
  33. package/dist/components/form/index.js +20 -0
  34. package/dist/components/glass-panel/glass-panel.js +21 -0
  35. package/dist/components/glass-panel/index.js +4 -0
  36. package/dist/components/group-hull/group-hull.js +29 -0
  37. package/dist/components/group-hull/index.js +4 -0
  38. package/dist/components/index.js +176 -0
  39. package/dist/components/infinite-plane/index.js +6 -0
  40. package/dist/components/infinite-plane/infinite-plane.js +75 -0
  41. package/dist/components/left-rail/index.js +4 -0
  42. package/dist/components/left-rail/left-rail.js +25 -0
  43. package/dist/components/live-cursor/index.js +6 -0
  44. package/dist/components/live-cursor/live-cursor.js +62 -0
  45. package/dist/components/mini-map-panel/index.js +6 -0
  46. package/dist/components/mini-map-panel/mini-map-panel.js +74 -0
  47. package/dist/components/multi-select/index.js +6 -0
  48. package/dist/components/multi-select/multi-select.js +258 -0
  49. package/dist/components/object-card/index.js +6 -0
  50. package/dist/components/object-card/object-card.js +126 -0
  51. package/dist/components/object-handle/index.js +4 -0
  52. package/dist/components/object-handle/object-handle.js +38 -0
  53. package/dist/components/overview-board/index.js +8 -0
  54. package/dist/components/overview-board/overview-board.js +127 -0
  55. package/dist/components/presence-stack/index.js +6 -0
  56. package/dist/components/presence-stack/presence-stack.js +108 -0
  57. package/dist/components/presence-sync-indicator/index.js +6 -0
  58. package/dist/components/presence-sync-indicator/presence-sync-indicator.js +73 -0
  59. package/dist/components/progress-tracker/index.js +20 -0
  60. package/dist/components/progress-tracker/progress-tracker.js +527 -0
  61. package/dist/components/right-dock/index.js +4 -0
  62. package/dist/components/right-dock/right-dock.js +28 -0
  63. package/dist/components/run-timeline/index.js +6 -0
  64. package/dist/components/run-timeline/run-timeline.js +221 -0
  65. package/dist/components/segmented-control/index.js +12 -0
  66. package/dist/components/segmented-control/segmented-control.js +61 -0
  67. package/dist/components/selection-presence/index.js +6 -0
  68. package/dist/components/selection-presence/selection-presence.js +50 -0
  69. package/dist/components/spinner/unicode-spinner.js +1 -0
  70. package/dist/components/tags-input/index.js +4 -0
  71. package/dist/components/tags-input/tags-input.js +178 -0
  72. package/dist/components/thread-bubble/index.js +6 -0
  73. package/dist/components/thread-bubble/thread-bubble.js +85 -0
  74. package/dist/components/top-bar/index.js +4 -0
  75. package/dist/components/top-bar/top-bar.js +31 -0
  76. package/dist/components/usage-breakdown/usage-breakdown.js +1 -0
  77. package/dist/components/viewport-bookmarks/index.js +6 -0
  78. package/dist/components/viewport-bookmarks/viewport-bookmarks.js +116 -0
  79. package/dist/components/workspace-switcher/index.js +6 -0
  80. package/dist/components/workspace-switcher/workspace-switcher.js +61 -0
  81. package/dist/components/world-breadcrumbs/index.js +6 -0
  82. package/dist/components/world-breadcrumbs/world-breadcrumbs.js +114 -0
  83. package/dist/components/zoom-hud/index.js +4 -0
  84. package/dist/components/zoom-hud/zoom-hud.js +61 -0
  85. package/dist/index.d.ts +1468 -6
  86. package/package.json +7 -3
@@ -0,0 +1,258 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Check, ChevronDown } from "lucide-react";
5
+ import { cn } from "../../lib/utils";
6
+ import { Badge } from "../badge";
7
+ import { Button } from "../button";
8
+ import {
9
+ Command,
10
+ CommandEmpty,
11
+ CommandGroup,
12
+ CommandInput,
13
+ CommandItem,
14
+ CommandList
15
+ } from "../command";
16
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover";
17
+ function getUniqueValues(values) {
18
+ return values.filter((value, index) => values.indexOf(value) === index);
19
+ }
20
+ function shouldOpenFromKey(key) {
21
+ return key === " " || key === "ArrowDown" || key === "Enter";
22
+ }
23
+ function TriggerContent({ placeholder, selectedOptions }) {
24
+ if (selectedOptions.length === 0) {
25
+ return /* @__PURE__ */ jsx("span", { children: placeholder });
26
+ }
27
+ return /* @__PURE__ */ jsx(Fragment, { children: selectedOptions.map((option) => /* @__PURE__ */ jsx(Badge, { className: "max-w-full", variant: "secondary", children: /* @__PURE__ */ jsx("span", { className: "truncate", children: option.label }) }, option.value)) });
28
+ }
29
+ function OptionList({
30
+ disabled,
31
+ emptyText,
32
+ onSelect,
33
+ options,
34
+ searchable,
35
+ searchPlaceholder,
36
+ selectedValues
37
+ }) {
38
+ return /* @__PURE__ */ jsxs(Command, { children: [
39
+ searchable ? /* @__PURE__ */ jsx(CommandInput, { placeholder: searchPlaceholder }) : null,
40
+ /* @__PURE__ */ jsxs(CommandList, { "aria-multiselectable": "true", children: [
41
+ /* @__PURE__ */ jsx(CommandEmpty, { children: emptyText }),
42
+ /* @__PURE__ */ jsx(CommandGroup, { children: /* @__PURE__ */ jsx("div", { children: options.map((option) => {
43
+ const isSelected = selectedValues.includes(option.value);
44
+ return /* @__PURE__ */ jsxs(
45
+ CommandItem,
46
+ {
47
+ "aria-disabled": option.disabled || void 0,
48
+ "aria-selected": isSelected,
49
+ className: "gap-2",
50
+ disabled: disabled || option.disabled,
51
+ onSelect: () => {
52
+ onSelect(option.value);
53
+ },
54
+ role: "option",
55
+ value: option.label,
56
+ children: [
57
+ /* @__PURE__ */ jsx(
58
+ "span",
59
+ {
60
+ className: cn(
61
+ "flex h-4 w-4 items-center justify-center rounded-sm border border-input bg-background text-primary transition-opacity",
62
+ isSelected ? "opacity-100" : "opacity-50"
63
+ ),
64
+ children: isSelected ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : null
65
+ }
66
+ ),
67
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: option.label })
68
+ ]
69
+ },
70
+ option.value
71
+ );
72
+ }) }) })
73
+ ] })
74
+ ] });
75
+ }
76
+ function MultiSelectContent({
77
+ contentId,
78
+ disabled,
79
+ emptyText,
80
+ onSelect,
81
+ options,
82
+ searchable,
83
+ searchPlaceholder,
84
+ selectedValues
85
+ }) {
86
+ return /* @__PURE__ */ jsx(
87
+ PopoverContent,
88
+ {
89
+ align: "start",
90
+ className: "w-[var(--radix-popover-trigger-width)] p-0",
91
+ id: contentId,
92
+ children: /* @__PURE__ */ jsx(
93
+ OptionList,
94
+ {
95
+ disabled,
96
+ emptyText,
97
+ onSelect,
98
+ options,
99
+ searchable,
100
+ searchPlaceholder,
101
+ selectedValues
102
+ }
103
+ )
104
+ }
105
+ );
106
+ }
107
+ function useMultiSelectState({
108
+ defaultValue,
109
+ onOpenChange,
110
+ onValueChange,
111
+ value
112
+ }) {
113
+ const [open, setOpen] = React.useState(false);
114
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(
115
+ () => getUniqueValues(defaultValue)
116
+ );
117
+ const isControlled = value !== void 0;
118
+ const selectedValues = React.useMemo(
119
+ () => getUniqueValues(value ?? uncontrolledValue),
120
+ [uncontrolledValue, value]
121
+ );
122
+ const setSelectedValues = React.useCallback(
123
+ (nextValue) => {
124
+ const uniqueValues = getUniqueValues(nextValue);
125
+ if (!isControlled) {
126
+ setUncontrolledValue(uniqueValues);
127
+ }
128
+ onValueChange?.(uniqueValues);
129
+ },
130
+ [isControlled, onValueChange]
131
+ );
132
+ const handleOpenChange = React.useCallback(
133
+ (nextOpen) => {
134
+ setOpen(nextOpen);
135
+ onOpenChange?.(nextOpen);
136
+ },
137
+ [onOpenChange]
138
+ );
139
+ return {
140
+ handleOpenChange,
141
+ open,
142
+ selectedValues,
143
+ setSelectedValues
144
+ };
145
+ }
146
+ const MultiSelectTrigger = React.forwardRef(
147
+ ({
148
+ className,
149
+ contentId,
150
+ disabled = false,
151
+ onKeyDown,
152
+ open,
153
+ placeholder = "Select options",
154
+ selectedOptions,
155
+ ...props
156
+ }, ref) => /* @__PURE__ */ jsxs(
157
+ Button,
158
+ {
159
+ "aria-controls": contentId,
160
+ "aria-expanded": open,
161
+ "aria-haspopup": "listbox",
162
+ className: cn(
163
+ "min-h-10 w-full justify-between px-3 py-2 text-sm font-normal",
164
+ selectedOptions.length === 0 && "text-muted-foreground",
165
+ className
166
+ ),
167
+ disabled,
168
+ onKeyDown,
169
+ ref,
170
+ role: "combobox",
171
+ type: "button",
172
+ variant: "outline",
173
+ ...props,
174
+ children: [
175
+ /* @__PURE__ */ jsx("span", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-1 text-left", children: /* @__PURE__ */ jsx(
176
+ TriggerContent,
177
+ {
178
+ placeholder,
179
+ selectedOptions
180
+ }
181
+ ) }),
182
+ /* @__PURE__ */ jsx(ChevronDown, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })
183
+ ]
184
+ }
185
+ )
186
+ );
187
+ MultiSelectTrigger.displayName = "MultiSelectTrigger";
188
+ const MultiSelect = React.forwardRef(
189
+ ({
190
+ defaultValue = [],
191
+ emptyText = "No options found.",
192
+ onKeyDown,
193
+ onOpenChange,
194
+ onValueChange,
195
+ options,
196
+ searchable = false,
197
+ searchPlaceholder = "Search options...",
198
+ value,
199
+ ...props
200
+ }, ref) => {
201
+ const contentId = React.useId();
202
+ const { handleOpenChange, open, selectedValues, setSelectedValues } = useMultiSelectState({ defaultValue, onOpenChange, onValueChange, value });
203
+ const selectedOptions = React.useMemo(
204
+ () => options.filter((option) => selectedValues.includes(option.value)),
205
+ [options, selectedValues]
206
+ );
207
+ const handleSelect = React.useCallback(
208
+ (nextValue) => {
209
+ const nextSelectedValues = selectedValues.includes(nextValue) ? selectedValues.filter((valueItem) => valueItem !== nextValue) : [...selectedValues, nextValue];
210
+ setSelectedValues(nextSelectedValues);
211
+ },
212
+ [selectedValues, setSelectedValues]
213
+ );
214
+ const handleTriggerKeyDown = React.useCallback(
215
+ (event) => {
216
+ onKeyDown?.(event);
217
+ if (event.defaultPrevented || props.disabled) {
218
+ return;
219
+ }
220
+ if (shouldOpenFromKey(event.key)) {
221
+ event.preventDefault();
222
+ handleOpenChange(true);
223
+ }
224
+ },
225
+ [handleOpenChange, onKeyDown, props.disabled]
226
+ );
227
+ return /* @__PURE__ */ jsxs(Popover, { onOpenChange: handleOpenChange, open, children: [
228
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
229
+ MultiSelectTrigger,
230
+ {
231
+ ...props,
232
+ contentId,
233
+ onKeyDown: handleTriggerKeyDown,
234
+ open,
235
+ ref,
236
+ selectedOptions
237
+ }
238
+ ) }),
239
+ /* @__PURE__ */ jsx(
240
+ MultiSelectContent,
241
+ {
242
+ contentId,
243
+ disabled: props.disabled || false,
244
+ emptyText,
245
+ onSelect: handleSelect,
246
+ options,
247
+ searchable,
248
+ searchPlaceholder,
249
+ selectedValues
250
+ }
251
+ )
252
+ ] });
253
+ }
254
+ );
255
+ MultiSelect.displayName = "MultiSelect";
256
+ export {
257
+ MultiSelect
258
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ ObjectCard
3
+ } from "./object-card";
4
+ export {
5
+ ObjectCard
6
+ };
@@ -0,0 +1,126 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../../lib/utils";
4
+ import { Badge } from "../badge";
5
+ import { Button } from "../button";
6
+ const stateClasses = {
7
+ blocked: "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300",
8
+ complete: "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
9
+ idle: "border-border/70 bg-muted/60 text-muted-foreground",
10
+ running: "border-sky-500/30 bg-sky-500/10 text-sky-700 dark:text-sky-300"
11
+ };
12
+ function ObjectCardHeader({
13
+ kind,
14
+ ports,
15
+ state,
16
+ summary,
17
+ title
18
+ }) {
19
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
20
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
21
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
22
+ /* @__PURE__ */ jsx(
23
+ Badge,
24
+ {
25
+ className: "rounded-full border-border/60 bg-background/70 px-2.5 py-1 text-[11px] uppercase tracking-[0.2em] text-muted-foreground",
26
+ variant: "outline",
27
+ children: kind
28
+ }
29
+ ),
30
+ /* @__PURE__ */ jsx(
31
+ "span",
32
+ {
33
+ className: cn(
34
+ "inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-medium capitalize",
35
+ stateClasses[state]
36
+ ),
37
+ children: state
38
+ }
39
+ )
40
+ ] }),
41
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
42
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold tracking-tight text-foreground", children: title }),
43
+ summary ? /* @__PURE__ */ jsx("p", { className: "max-w-[32ch] text-sm leading-6 text-muted-foreground", children: summary }) : null
44
+ ] })
45
+ ] }),
46
+ ports ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-start", children: ports }) : null
47
+ ] });
48
+ }
49
+ function ObjectCardMetrics({ metrics }) {
50
+ if (!metrics?.length) {
51
+ return null;
52
+ }
53
+ return /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-2 gap-3 rounded-2xl border border-border/60 bg-background/75 p-3", children: metrics.map((metric) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
54
+ /* @__PURE__ */ jsx("dt", { className: "text-[11px] uppercase tracking-[0.18em] text-muted-foreground", children: metric.label }),
55
+ /* @__PURE__ */ jsx("dd", { className: "text-sm font-medium text-foreground", children: metric.value })
56
+ ] }, metric.label)) });
57
+ }
58
+ function ObjectCardActions({ actions }) {
59
+ if (!actions?.length) {
60
+ return null;
61
+ }
62
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: actions.map((action) => {
63
+ const handleActionClick = () => {
64
+ action.onClick?.();
65
+ };
66
+ return /* @__PURE__ */ jsx(
67
+ Button,
68
+ {
69
+ className: "rounded-full",
70
+ onClick: handleActionClick,
71
+ size: "sm",
72
+ type: "button",
73
+ variant: "outline",
74
+ children: action.label
75
+ },
76
+ action.label
77
+ );
78
+ }) });
79
+ }
80
+ const ObjectCard = forwardRef(
81
+ ({
82
+ actions,
83
+ children,
84
+ className,
85
+ footer,
86
+ kind = "Object",
87
+ metrics = [],
88
+ ports,
89
+ state = "idle",
90
+ summary,
91
+ title,
92
+ ...props
93
+ }, ref) => /* @__PURE__ */ jsxs(
94
+ "article",
95
+ {
96
+ className: cn(
97
+ "group relative flex min-w-[320px] max-w-[420px] flex-col gap-4 rounded-[1.5rem] border border-border/70 bg-[linear-gradient(180deg,hsl(var(--background)),hsl(var(--muted)/0.22))] p-5 shadow-[0_24px_80px_hsl(var(--foreground)/0.08)] transition-transform duration-200 hover:-translate-y-0.5",
98
+ className
99
+ ),
100
+ "data-state": state,
101
+ ref,
102
+ ...props,
103
+ children: [
104
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-5 top-0 h-px bg-[linear-gradient(90deg,transparent,hsl(var(--foreground)/0.22),transparent)]" }),
105
+ /* @__PURE__ */ jsx(
106
+ ObjectCardHeader,
107
+ {
108
+ kind,
109
+ ports,
110
+ state,
111
+ summary,
112
+ title
113
+ }
114
+ ),
115
+ /* @__PURE__ */ jsx(ObjectCardMetrics, { metrics }),
116
+ children ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children }) : null,
117
+ /* @__PURE__ */ jsx(ObjectCardActions, { actions }),
118
+ footer ? /* @__PURE__ */ jsx("div", { className: "border-t border-border/60 pt-3 text-sm text-muted-foreground", children: footer }) : null
119
+ ]
120
+ }
121
+ )
122
+ );
123
+ ObjectCard.displayName = "ObjectCard";
124
+ export {
125
+ ObjectCard
126
+ };
@@ -0,0 +1,4 @@
1
+ import { ObjectHandle } from "./object-handle";
2
+ export {
3
+ ObjectHandle
4
+ };
@@ -0,0 +1,38 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../../lib/utils";
4
+ const ObjectHandle = forwardRef(
5
+ ({ className, hint, label = "Move", ...props }, ref) => /* @__PURE__ */ jsxs(
6
+ "button",
7
+ {
8
+ className: cn(
9
+ "inline-flex items-center gap-2 rounded-full border border-border/60 bg-background/85 px-3 py-1.5 text-xs font-medium text-muted-foreground shadow-sm transition-colors hover:border-border hover:text-foreground",
10
+ className
11
+ ),
12
+ ref,
13
+ type: "button",
14
+ ...props,
15
+ children: [
16
+ /* @__PURE__ */ jsxs(
17
+ "span",
18
+ {
19
+ "aria-hidden": "true",
20
+ className: "grid grid-cols-2 gap-0.5 text-[8px] leading-none",
21
+ children: [
22
+ /* @__PURE__ */ jsx("span", { children: "\u2022" }),
23
+ /* @__PURE__ */ jsx("span", { children: "\u2022" }),
24
+ /* @__PURE__ */ jsx("span", { children: "\u2022" }),
25
+ /* @__PURE__ */ jsx("span", { children: "\u2022" })
26
+ ]
27
+ }
28
+ ),
29
+ /* @__PURE__ */ jsx("span", { children: label }),
30
+ hint ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/80", children: hint }) : null
31
+ ]
32
+ }
33
+ )
34
+ );
35
+ ObjectHandle.displayName = "ObjectHandle";
36
+ export {
37
+ ObjectHandle
38
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ OverviewBoard,
3
+ OverviewCard
4
+ } from "./overview-board";
5
+ export {
6
+ OverviewBoard,
7
+ OverviewCard
8
+ };
@@ -0,0 +1,127 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { AlertCircle, ArrowRight, Inbox, ListTodo, Siren } from "lucide-react";
4
+ import { cn } from "../../lib/utils";
5
+ import { Button } from "../button";
6
+ const toneClassNames = {
7
+ danger: "border-red-500/30 bg-red-500/8",
8
+ default: "border-border/70 bg-background/80",
9
+ warning: "border-amber-500/30 bg-amber-500/8"
10
+ };
11
+ const toneAccentClassNames = {
12
+ danger: "text-red-600 dark:text-red-300",
13
+ default: "text-primary",
14
+ warning: "text-amber-600 dark:text-amber-300"
15
+ };
16
+ const OverviewCard = forwardRef(
17
+ ({
18
+ className,
19
+ ctaLabel,
20
+ description,
21
+ handleCtaClick,
22
+ heading,
23
+ icon,
24
+ metric,
25
+ tone = "default",
26
+ ...props
27
+ }, ref) => /* @__PURE__ */ jsxs(
28
+ "section",
29
+ {
30
+ className: cn(
31
+ "flex min-h-[172px] flex-col gap-4 rounded-2xl border p-5 shadow-[0_8px_30px_hsl(var(--foreground)/0.06)] backdrop-blur-xl",
32
+ toneClassNames[tone],
33
+ className
34
+ ),
35
+ ref,
36
+ ...props,
37
+ children: [
38
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
39
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
40
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.24em] text-muted-foreground", children: heading }),
41
+ /* @__PURE__ */ jsx("div", { className: "text-3xl font-semibold tracking-tight text-foreground", children: metric })
42
+ ] }),
43
+ /* @__PURE__ */ jsx(
44
+ "div",
45
+ {
46
+ className: cn(
47
+ "flex size-10 items-center justify-center rounded-xl bg-background/80",
48
+ toneAccentClassNames[tone]
49
+ ),
50
+ children: icon
51
+ }
52
+ )
53
+ ] }),
54
+ /* @__PURE__ */ jsx("p", { className: "text-sm leading-6 text-muted-foreground", children: description }),
55
+ ctaLabel ? /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
56
+ Button,
57
+ {
58
+ onClick: handleCtaClick,
59
+ size: "sm",
60
+ type: "button",
61
+ variant: "ghost",
62
+ children: [
63
+ ctaLabel,
64
+ /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
65
+ ]
66
+ }
67
+ ) }) : null
68
+ ]
69
+ }
70
+ )
71
+ );
72
+ OverviewCard.displayName = "OverviewCard";
73
+ function getDefaultIcon(heading) {
74
+ if (typeof heading !== "string") {
75
+ return /* @__PURE__ */ jsx(Inbox, { className: "size-5" });
76
+ }
77
+ if (heading.toLowerCase().includes("error")) {
78
+ return /* @__PURE__ */ jsx(AlertCircle, { className: "size-5" });
79
+ }
80
+ if (heading.toLowerCase().includes("action")) {
81
+ return /* @__PURE__ */ jsx(ListTodo, { className: "size-5" });
82
+ }
83
+ if (heading.toLowerCase().includes("run")) {
84
+ return /* @__PURE__ */ jsx(Siren, { className: "size-5" });
85
+ }
86
+ return /* @__PURE__ */ jsx(Inbox, { className: "size-5" });
87
+ }
88
+ const OverviewBoard = forwardRef(
89
+ ({ className, eyebrow, heading, items, subtitle, ...props }, ref) => /* @__PURE__ */ jsxs(
90
+ "section",
91
+ {
92
+ className: cn("mx-auto flex w-full max-w-6xl flex-col gap-6", className),
93
+ ref,
94
+ ...props,
95
+ children: [
96
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
97
+ eyebrow ? /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.28em] text-muted-foreground", children: eyebrow }) : null,
98
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
99
+ /* @__PURE__ */ jsx("h2", { className: "text-3xl font-semibold tracking-tight text-foreground", children: heading }),
100
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "max-w-3xl text-sm leading-6 text-muted-foreground", children: subtitle }) : null
101
+ ] })
102
+ ] }),
103
+ /* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2 xl:grid-cols-3", children: items.map((item) => {
104
+ const handleCtaClick = item.handleCtaClick;
105
+ return /* @__PURE__ */ jsx(
106
+ OverviewCard,
107
+ {
108
+ ctaLabel: item.ctaLabel,
109
+ description: item.description,
110
+ handleCtaClick,
111
+ heading: item.heading,
112
+ icon: item.icon ?? getDefaultIcon(item.heading),
113
+ metric: item.metric,
114
+ tone: item.tone
115
+ },
116
+ item.id
117
+ );
118
+ }) })
119
+ ]
120
+ }
121
+ )
122
+ );
123
+ OverviewBoard.displayName = "OverviewBoard";
124
+ export {
125
+ OverviewBoard,
126
+ OverviewCard
127
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ PresenceStack
3
+ } from "./presence-stack";
4
+ export {
5
+ PresenceStack
6
+ };
@@ -0,0 +1,108 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef
5
+ } from "react";
6
+ import { cn } from "../../lib/utils";
7
+ const STATUS_DOT = {
8
+ active: "bg-emerald-500",
9
+ away: "bg-amber-500",
10
+ idle: "bg-muted-foreground",
11
+ offline: "bg-muted-foreground/40"
12
+ };
13
+ const DEFAULT_LABELS = {
14
+ overflowSuffix: "more",
15
+ region: "Live presence"
16
+ };
17
+ const Avatar = (props) => {
18
+ const { user } = props;
19
+ const status = user.status ?? "active";
20
+ return /* @__PURE__ */ jsxs(
21
+ "span",
22
+ {
23
+ className: "relative -ml-2 inline-flex h-7 w-7 items-center justify-center rounded-full border-2 border-background text-[11px] font-semibold text-white shadow-sm first:ml-0",
24
+ "data-presence-stack-status": status,
25
+ "data-presence-stack-user": user.id,
26
+ style: { backgroundColor: user.color ?? "var(--foreground)" },
27
+ title: user.name,
28
+ children: [
29
+ user.initial,
30
+ /* @__PURE__ */ jsx(
31
+ "span",
32
+ {
33
+ "aria-hidden": "true",
34
+ className: cn(
35
+ "absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full border border-background",
36
+ STATUS_DOT[status]
37
+ ),
38
+ "data-presence-stack-dot": true
39
+ }
40
+ )
41
+ ]
42
+ }
43
+ );
44
+ };
45
+ const PresenceStack = forwardRef(
46
+ (props, ref) => {
47
+ const {
48
+ className,
49
+ labels,
50
+ max = 5,
51
+ onOverflowActivate,
52
+ users,
53
+ ...rest
54
+ } = props;
55
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
56
+ const visible = max >= users.length ? users : users.slice(0, max);
57
+ const hidden = users.length - visible.length;
58
+ const handleOverflow = () => {
59
+ onOverflowActivate?.();
60
+ };
61
+ return /* @__PURE__ */ jsxs(
62
+ "div",
63
+ {
64
+ "aria-label": resolvedLabels.region,
65
+ className: cn("inline-flex items-center pl-2", className),
66
+ "data-presence-stack": true,
67
+ ref,
68
+ role: "group",
69
+ ...rest,
70
+ children: [
71
+ visible.map((user) => /* @__PURE__ */ jsx(Avatar, { user }, user.id)),
72
+ hidden > 0 ? renderOverflow({
73
+ count: hidden,
74
+ handleClick: handleOverflow,
75
+ handlerProvided: Boolean(onOverflowActivate),
76
+ labels: resolvedLabels
77
+ }) : null
78
+ ]
79
+ }
80
+ );
81
+ }
82
+ );
83
+ PresenceStack.displayName = "PresenceStack";
84
+ const renderOverflow = (input) => {
85
+ const text = `+${input.count}`;
86
+ const aria = `${input.count} ${input.labels.overflowSuffix}`;
87
+ const className = "relative -ml-2 inline-flex h-7 min-w-7 items-center justify-center rounded-full border-2 border-background bg-muted px-1.5 text-[10px] font-semibold text-muted-foreground shadow-sm";
88
+ if (input.handlerProvided) {
89
+ return /* @__PURE__ */ jsx(
90
+ "button",
91
+ {
92
+ "aria-label": aria,
93
+ className: cn(
94
+ className,
95
+ "transition-colors hover:bg-muted/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
96
+ ),
97
+ "data-presence-stack-overflow": true,
98
+ onClick: input.handleClick,
99
+ type: "button",
100
+ children: text
101
+ }
102
+ );
103
+ }
104
+ return /* @__PURE__ */ jsx("span", { "aria-label": aria, className, "data-presence-stack-overflow": true, children: text });
105
+ };
106
+ export {
107
+ PresenceStack
108
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ PresenceSyncIndicator
3
+ } from "./presence-sync-indicator";
4
+ export {
5
+ PresenceSyncIndicator
6
+ };