@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,4 @@
1
+ import { CanvasShell } from "./canvas-shell";
2
+ export {
3
+ CanvasShell
4
+ };
@@ -0,0 +1,461 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef,
5
+ useCallback,
6
+ useEffect,
7
+ useId,
8
+ useImperativeHandle,
9
+ useRef,
10
+ useState
11
+ } from "react";
12
+ import { cn } from "../../lib/utils";
13
+ const DEFAULT_VIEWPORT = { x: 0, y: 0, zoom: 1 };
14
+ const INTERACTIVE_ELEMENT_SELECTOR = [
15
+ "a[href]",
16
+ "button",
17
+ 'input:not([type="hidden"])',
18
+ "select",
19
+ "textarea",
20
+ "summary",
21
+ '[contenteditable=""]',
22
+ '[contenteditable="true"]',
23
+ '[role="button"]',
24
+ '[role="checkbox"]',
25
+ '[role="link"]',
26
+ '[role="menuitem"]',
27
+ '[role="option"]',
28
+ '[role="radio"]',
29
+ '[role="slider"]',
30
+ '[role="spinbutton"]',
31
+ '[role="switch"]',
32
+ '[role="tab"]',
33
+ '[role="textbox"]'
34
+ ].join(", ");
35
+ function clampZoom(value, minZoom, maxZoom) {
36
+ return Math.min(maxZoom, Math.max(minZoom, Number(value.toFixed(2))));
37
+ }
38
+ function isHtmlElement(target) {
39
+ return target instanceof HTMLElement;
40
+ }
41
+ function isInteractiveDescendant(element, container) {
42
+ const interactiveAncestor = element.closest(INTERACTIVE_ELEMENT_SELECTOR);
43
+ return interactiveAncestor !== null && container.contains(interactiveAncestor);
44
+ }
45
+ function supportsScrollableOverflow(value) {
46
+ return value === "auto" || value === "overlay" || value === "scroll";
47
+ }
48
+ function hasScrollableAxis(element, axis) {
49
+ const style = window.getComputedStyle(element);
50
+ if (axis === "x") {
51
+ return supportsScrollableOverflow(style.overflowX) && element.scrollWidth > element.clientWidth;
52
+ }
53
+ return supportsScrollableOverflow(style.overflowY) && element.scrollHeight > element.clientHeight;
54
+ }
55
+ function hasScrollableAncestor(element, container, delta) {
56
+ if (!container.contains(element) || element === container) {
57
+ return false;
58
+ }
59
+ if (delta.x !== 0 && hasScrollableAxis(element, "x") || delta.y !== 0 && hasScrollableAxis(element, "y")) {
60
+ return true;
61
+ }
62
+ return element.parentElement === null ? false : hasScrollableAncestor(element.parentElement, container, delta);
63
+ }
64
+ function shouldHandleCanvasKeyboardEvent(event) {
65
+ if (isHtmlElement(event.target) && event.target !== event.currentTarget && isInteractiveDescendant(event.target, event.currentTarget)) {
66
+ return false;
67
+ }
68
+ return true;
69
+ }
70
+ function shouldHandleCanvasWheelEvent(event) {
71
+ if (isHtmlElement(event.target) && hasScrollableAncestor(event.target, event.currentTarget, {
72
+ x: event.deltaX,
73
+ y: event.deltaY
74
+ })) {
75
+ return false;
76
+ }
77
+ return true;
78
+ }
79
+ function isPanGesture(event, isSpacePressed) {
80
+ return event.button === 1 || event.button === 0 && isSpacePressed;
81
+ }
82
+ function createViewportKeyHandler({
83
+ nudgeViewport,
84
+ resetViewport,
85
+ setViewport,
86
+ viewportRef,
87
+ zoomStep
88
+ }) {
89
+ return (event) => {
90
+ if (event.key === "+" || event.key === "=") {
91
+ event.preventDefault();
92
+ setViewport({
93
+ ...viewportRef.current,
94
+ zoom: viewportRef.current.zoom + zoomStep
95
+ });
96
+ return;
97
+ }
98
+ if (event.key === "-") {
99
+ event.preventDefault();
100
+ setViewport({
101
+ ...viewportRef.current,
102
+ zoom: viewportRef.current.zoom - zoomStep
103
+ });
104
+ return;
105
+ }
106
+ if (event.key === "0") {
107
+ event.preventDefault();
108
+ resetViewport();
109
+ return;
110
+ }
111
+ if (event.key === "ArrowLeft") {
112
+ event.preventDefault();
113
+ nudgeViewport(40, 0);
114
+ return;
115
+ }
116
+ if (event.key === "ArrowRight") {
117
+ event.preventDefault();
118
+ nudgeViewport(-40, 0);
119
+ return;
120
+ }
121
+ if (event.key === "ArrowUp") {
122
+ event.preventDefault();
123
+ nudgeViewport(0, 40);
124
+ return;
125
+ }
126
+ if (event.key === "ArrowDown") {
127
+ event.preventDefault();
128
+ nudgeViewport(0, -40);
129
+ }
130
+ };
131
+ }
132
+ function useViewportState({
133
+ defaultViewport,
134
+ maxZoom,
135
+ minZoom,
136
+ onViewportChange
137
+ }) {
138
+ const defaultViewportRef = useRef(defaultViewport);
139
+ const viewportRef = useRef(defaultViewport);
140
+ const [viewport, setViewport] = useState(defaultViewport);
141
+ useEffect(() => {
142
+ defaultViewportRef.current = defaultViewport;
143
+ }, [defaultViewport]);
144
+ const applyViewport = useCallback(
145
+ (nextViewport) => {
146
+ const resolvedViewport = {
147
+ x: Math.round(nextViewport.x),
148
+ y: Math.round(nextViewport.y),
149
+ zoom: clampZoom(nextViewport.zoom, minZoom, maxZoom)
150
+ };
151
+ viewportRef.current = resolvedViewport;
152
+ setViewport(resolvedViewport);
153
+ onViewportChange?.(resolvedViewport);
154
+ },
155
+ [maxZoom, minZoom, onViewportChange]
156
+ );
157
+ const resetViewport = useCallback(() => {
158
+ applyViewport(defaultViewportRef.current);
159
+ }, [applyViewport]);
160
+ const nudgeViewport = useCallback(
161
+ (deltaX, deltaY) => {
162
+ const currentViewport = viewportRef.current;
163
+ applyViewport({
164
+ x: currentViewport.x + deltaX,
165
+ y: currentViewport.y + deltaY,
166
+ zoom: currentViewport.zoom
167
+ });
168
+ },
169
+ [applyViewport]
170
+ );
171
+ return {
172
+ nudgeViewport,
173
+ resetViewport,
174
+ setViewport: applyViewport,
175
+ viewport,
176
+ viewportRef
177
+ };
178
+ }
179
+ function useCanvasKeyboardInteractions({
180
+ nudgeViewport,
181
+ resetViewport,
182
+ setViewport,
183
+ viewportRef,
184
+ zoomStep
185
+ }) {
186
+ const [isSpacePressed, setIsSpacePressed] = useState(false);
187
+ const handleWheel = useCallback(
188
+ (event) => {
189
+ if (event.ctrlKey || event.metaKey) {
190
+ event.preventDefault();
191
+ setViewport({
192
+ ...viewportRef.current,
193
+ zoom: viewportRef.current.zoom + (event.deltaY > 0 ? -zoomStep : zoomStep)
194
+ });
195
+ return;
196
+ }
197
+ if (!shouldHandleCanvasWheelEvent(event)) {
198
+ return;
199
+ }
200
+ event.preventDefault();
201
+ nudgeViewport(-event.deltaX, -event.deltaY);
202
+ },
203
+ [nudgeViewport, setViewport, viewportRef, zoomStep]
204
+ );
205
+ const handleKeyDown = useCallback(
206
+ (event) => {
207
+ if (!shouldHandleCanvasKeyboardEvent(event)) {
208
+ return;
209
+ }
210
+ if (event.key === " ") {
211
+ event.preventDefault();
212
+ setIsSpacePressed(true);
213
+ return;
214
+ }
215
+ createViewportKeyHandler({
216
+ nudgeViewport,
217
+ resetViewport,
218
+ setViewport,
219
+ viewportRef,
220
+ zoomStep
221
+ })(event);
222
+ },
223
+ [nudgeViewport, resetViewport, setViewport, viewportRef, zoomStep]
224
+ );
225
+ const handleKeyUp = useCallback(
226
+ (event) => {
227
+ if (!shouldHandleCanvasKeyboardEvent(event)) {
228
+ return;
229
+ }
230
+ if (event.key === " ") {
231
+ setIsSpacePressed(false);
232
+ }
233
+ },
234
+ []
235
+ );
236
+ return { handleKeyDown, handleKeyUp, handleWheel, isSpacePressed };
237
+ }
238
+ function endCanvasDrag(event, dragOriginRef, setIsDragging) {
239
+ dragOriginRef.current = null;
240
+ setIsDragging(false);
241
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
242
+ event.currentTarget.releasePointerCapture(event.pointerId);
243
+ }
244
+ }
245
+ function useCanvasPointerInteractions({
246
+ isSpacePressed,
247
+ setViewport,
248
+ viewportRef
249
+ }) {
250
+ const dragOriginRef = useRef(null);
251
+ const [isDragging, setIsDragging] = useState(false);
252
+ const handlePointerDown = useCallback(
253
+ (event) => {
254
+ if (!isPanGesture(event, isSpacePressed)) {
255
+ return;
256
+ }
257
+ if (event.button === 1) {
258
+ event.preventDefault();
259
+ }
260
+ dragOriginRef.current = {
261
+ pointerX: event.clientX,
262
+ pointerY: event.clientY,
263
+ viewport: viewportRef.current
264
+ };
265
+ event.currentTarget.setPointerCapture(event.pointerId);
266
+ setIsDragging(true);
267
+ },
268
+ [isSpacePressed, viewportRef]
269
+ );
270
+ const handlePointerMove = useCallback(
271
+ (event) => {
272
+ const dragOrigin = dragOriginRef.current;
273
+ if (!dragOrigin) {
274
+ return;
275
+ }
276
+ setViewport({
277
+ x: dragOrigin.viewport.x + (event.clientX - dragOrigin.pointerX),
278
+ y: dragOrigin.viewport.y + (event.clientY - dragOrigin.pointerY),
279
+ zoom: dragOrigin.viewport.zoom
280
+ });
281
+ },
282
+ [setViewport]
283
+ );
284
+ const handlePointerCancel = useCallback(
285
+ (event) => {
286
+ endCanvasDrag(event, dragOriginRef, setIsDragging);
287
+ },
288
+ []
289
+ );
290
+ const handlePointerUp = useCallback(
291
+ (event) => {
292
+ endCanvasDrag(event, dragOriginRef, setIsDragging);
293
+ },
294
+ []
295
+ );
296
+ return {
297
+ handlePointerCancel,
298
+ handlePointerDown,
299
+ handlePointerMove,
300
+ handlePointerUp,
301
+ isDragging
302
+ };
303
+ }
304
+ function usePreventBodySelection(disabled) {
305
+ useEffect(() => {
306
+ if (typeof document === "undefined") {
307
+ return;
308
+ }
309
+ const { body } = document;
310
+ const previousUserSelect = body.style.userSelect;
311
+ if (disabled) {
312
+ body.style.userSelect = "none";
313
+ }
314
+ return () => {
315
+ body.style.userSelect = previousUserSelect;
316
+ };
317
+ }, [disabled]);
318
+ }
319
+ function useCanvasViewHandle(ref, viewportState) {
320
+ useImperativeHandle(
321
+ ref,
322
+ () => ({
323
+ resetViewport: viewportState.resetViewport,
324
+ setViewport: viewportState.setViewport
325
+ }),
326
+ [viewportState.resetViewport, viewportState.setViewport]
327
+ );
328
+ }
329
+ function CanvasInteractionLayer({
330
+ children,
331
+ instructionsId,
332
+ isDragging,
333
+ isSpacePressed,
334
+ onKeyDown,
335
+ onKeyUp,
336
+ onPointerCancel,
337
+ onPointerDown,
338
+ onPointerMove,
339
+ onPointerUp,
340
+ onWheel,
341
+ viewport
342
+ }) {
343
+ return /* @__PURE__ */ jsxs(
344
+ "div",
345
+ {
346
+ "aria-describedby": instructionsId,
347
+ "aria-label": "Canvas workspace",
348
+ "aria-roledescription": "canvas",
349
+ className: cn(
350
+ "relative h-full w-full select-none touch-none outline-none",
351
+ isDragging || isSpacePressed ? "cursor-grab active:cursor-grabbing" : "cursor-default"
352
+ ),
353
+ "data-viewport": JSON.stringify(viewport),
354
+ onKeyDown,
355
+ onKeyUp,
356
+ onPointerCancel,
357
+ onPointerDown,
358
+ onPointerMove,
359
+ onPointerUp,
360
+ onWheel,
361
+ role: "button",
362
+ tabIndex: 0,
363
+ children: [
364
+ /* @__PURE__ */ jsx("div", { className: "sr-only", id: instructionsId, children: "Hold space and drag or use the middle mouse button to pan. Use plus, minus, or control wheel to zoom. Press zero to reset the viewport." }),
365
+ children
366
+ ]
367
+ }
368
+ );
369
+ }
370
+ function CanvasContentLayer({
371
+ children,
372
+ overlay,
373
+ viewport
374
+ }) {
375
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
376
+ /* @__PURE__ */ jsx(
377
+ "div",
378
+ {
379
+ className: "absolute inset-0 origin-top-left transition-transform duration-150 ease-out",
380
+ style: {
381
+ transform: `translate3d(${viewport.x}px, ${viewport.y}px, 0) scale(${viewport.zoom})`
382
+ },
383
+ children
384
+ }
385
+ ),
386
+ overlay ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-20", children: overlay }) : null
387
+ ] });
388
+ }
389
+ const CanvasView = forwardRef(
390
+ ({
391
+ children,
392
+ className,
393
+ defaultViewport = DEFAULT_VIEWPORT,
394
+ maxZoom = 2,
395
+ minZoom = 0.5,
396
+ onViewportChange,
397
+ overlay,
398
+ zoomStep = 0.1,
399
+ ...props
400
+ }, ref) => {
401
+ const instructionsId = useId();
402
+ const viewportState = useViewportState({
403
+ defaultViewport,
404
+ maxZoom,
405
+ minZoom,
406
+ onViewportChange
407
+ });
408
+ const keyboard = useCanvasKeyboardInteractions({
409
+ nudgeViewport: viewportState.nudgeViewport,
410
+ resetViewport: viewportState.resetViewport,
411
+ setViewport: viewportState.setViewport,
412
+ viewportRef: viewportState.viewportRef,
413
+ zoomStep
414
+ });
415
+ const pointer = useCanvasPointerInteractions({
416
+ isSpacePressed: keyboard.isSpacePressed,
417
+ setViewport: viewportState.setViewport,
418
+ viewportRef: viewportState.viewportRef
419
+ });
420
+ usePreventBodySelection(pointer.isDragging);
421
+ useCanvasViewHandle(ref, viewportState);
422
+ return /* @__PURE__ */ jsx(
423
+ "div",
424
+ {
425
+ className: cn(
426
+ "relative h-full min-h-[32rem] overflow-hidden rounded-sm border border-border bg-background",
427
+ className
428
+ ),
429
+ ...props,
430
+ children: /* @__PURE__ */ jsx(
431
+ CanvasInteractionLayer,
432
+ {
433
+ instructionsId,
434
+ isDragging: pointer.isDragging,
435
+ isSpacePressed: keyboard.isSpacePressed,
436
+ onKeyDown: keyboard.handleKeyDown,
437
+ onKeyUp: keyboard.handleKeyUp,
438
+ onPointerCancel: pointer.handlePointerCancel,
439
+ onPointerDown: pointer.handlePointerDown,
440
+ onPointerMove: pointer.handlePointerMove,
441
+ onPointerUp: pointer.handlePointerUp,
442
+ onWheel: keyboard.handleWheel,
443
+ viewport: viewportState.viewport,
444
+ children: /* @__PURE__ */ jsx(
445
+ CanvasContentLayer,
446
+ {
447
+ overlay,
448
+ viewport: viewportState.viewport,
449
+ children
450
+ }
451
+ )
452
+ }
453
+ )
454
+ }
455
+ );
456
+ }
457
+ );
458
+ CanvasView.displayName = "CanvasView";
459
+ export {
460
+ CanvasView
461
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ CanvasView
3
+ } from "./canvas-view";
4
+ export {
5
+ CanvasView
6
+ };
@@ -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";
@@ -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,56 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { ArrowUpRight, MessageSquareText } from "lucide-react";
4
+ import { cn } from "../../lib/utils";
5
+ import { Button } from "../button";
6
+ const ChatDockSection = forwardRef(
7
+ ({
8
+ className,
9
+ composerPlaceholder = "Ask about runs, errors, or pending work\u2026",
10
+ contextLabel,
11
+ messages,
12
+ title = "Assistant",
13
+ ...props
14
+ }, ref) => /* @__PURE__ */ jsxs(
15
+ "section",
16
+ {
17
+ className: cn(
18
+ "flex flex-col gap-4 rounded-2xl border border-border/70 bg-background/75 p-4 shadow-[0_10px_35px_hsl(var(--foreground)/0.06)] backdrop-blur-xl",
19
+ className
20
+ ),
21
+ ref,
22
+ ...props,
23
+ children: [
24
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
25
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
26
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
27
+ /* @__PURE__ */ jsx(MessageSquareText, { className: "size-4 text-primary" }),
28
+ title
29
+ ] }),
30
+ contextLabel ? /* @__PURE__ */ jsx("div", { className: "text-xs uppercase tracking-[0.24em] text-muted-foreground", children: contextLabel }) : null
31
+ ] }),
32
+ /* @__PURE__ */ jsxs(Button, { size: "sm", type: "button", variant: "ghost", children: [
33
+ "Open thread",
34
+ /* @__PURE__ */ jsx(ArrowUpRight, { className: "size-4" })
35
+ ] })
36
+ ] }),
37
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: messages.map((message) => /* @__PURE__ */ jsxs(
38
+ "div",
39
+ {
40
+ className: "rounded-xl border border-border/60 bg-background/85 px-3 py-2",
41
+ children: [
42
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground", children: message.speaker }),
43
+ /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm leading-6 text-foreground", children: message.body })
44
+ ]
45
+ },
46
+ message.id
47
+ )) }),
48
+ /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-dashed border-border/80 bg-background/70 px-3 py-3 text-sm text-muted-foreground", children: composerPlaceholder })
49
+ ]
50
+ }
51
+ )
52
+ );
53
+ ChatDockSection.displayName = "ChatDockSection";
54
+ export {
55
+ ChatDockSection
56
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ ChatDockSection
3
+ } from "./chat-dock-section";
4
+ export {
5
+ ChatDockSection
6
+ };
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { cn } from "../../lib/utils";
5
+ const CHECKLIST_PROGRESS_EVENT = "vllnt:checklist-progress-change";
5
6
  function ChecklistItemRow({
6
7
  isChecked,
7
8
  item,
@@ -133,6 +134,11 @@ function Checklist({
133
134
  `checklist:${persistKey}`,
134
135
  JSON.stringify([...newChecked])
135
136
  );
137
+ window.dispatchEvent(
138
+ new CustomEvent(CHECKLIST_PROGRESS_EVENT, {
139
+ detail: { persistKey }
140
+ })
141
+ );
136
142
  } catch {
137
143
  }
138
144
  }
@@ -177,5 +183,6 @@ function Checklist({
177
183
  ] });
178
184
  }
179
185
  export {
186
+ CHECKLIST_PROGRESS_EVENT,
180
187
  Checklist
181
188
  };
@@ -1,6 +1,8 @@
1
1
  import {
2
- Checklist
2
+ Checklist,
3
+ CHECKLIST_PROGRESS_EVENT
3
4
  } from "./checklist";
4
5
  export {
6
+ CHECKLIST_PROGRESS_EVENT,
5
7
  Checklist
6
8
  };
@@ -0,0 +1,104 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef
5
+ } from "react";
6
+ import { cn } from "../../lib/utils";
7
+ const DEFAULT_LABELS = {
8
+ region: "Comment",
9
+ unreadSuffix: "unread"
10
+ };
11
+ const STATE_FILL = {
12
+ open: "bg-foreground text-background",
13
+ resolved: "bg-muted text-muted-foreground"
14
+ };
15
+ const PinBody = (props) => {
16
+ const showBadge = typeof props.unread === "number" && props.unread > 0;
17
+ const useAccent = props.accent && props.state === "open";
18
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
19
+ /* @__PURE__ */ jsx(
20
+ "span",
21
+ {
22
+ "aria-hidden": "true",
23
+ className: cn(
24
+ "flex h-7 w-7 items-center justify-center rounded-full border border-background text-[11px] font-semibold shadow-sm",
25
+ STATE_FILL[props.state]
26
+ ),
27
+ "data-comment-pin-body": true,
28
+ style: useAccent ? { backgroundColor: props.accent, color: "white" } : void 0,
29
+ children: props.authorInitial ?? "\u2022"
30
+ }
31
+ ),
32
+ showBadge ? /* @__PURE__ */ jsx(
33
+ "span",
34
+ {
35
+ "aria-hidden": "true",
36
+ className: "absolute -right-1 -top-1 inline-flex min-h-[14px] min-w-[14px] items-center justify-center rounded-full bg-red-500 px-1 text-[9px] font-medium text-white",
37
+ "data-comment-pin-unread": true,
38
+ children: props.unread
39
+ }
40
+ ) : null
41
+ ] });
42
+ };
43
+ const CommentPin = forwardRef(
44
+ (props, ref) => {
45
+ const {
46
+ authorInitial,
47
+ className,
48
+ color,
49
+ labels,
50
+ onActivate,
51
+ state = "open",
52
+ unread,
53
+ x,
54
+ y,
55
+ ...rest
56
+ } = props;
57
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
58
+ const showBadge = typeof unread === "number" && unread > 0;
59
+ const ariaLabel = showBadge ? `${resolvedLabels.region}, ${unread} ${resolvedLabels.unreadSuffix}` : resolvedLabels.region;
60
+ const handleClick = () => {
61
+ onActivate?.();
62
+ };
63
+ const body = /* @__PURE__ */ jsx(
64
+ PinBody,
65
+ {
66
+ accent: color,
67
+ authorInitial,
68
+ state,
69
+ unread
70
+ }
71
+ );
72
+ return /* @__PURE__ */ jsx(
73
+ "div",
74
+ {
75
+ "aria-label": ariaLabel,
76
+ className: cn(
77
+ "absolute z-30 inline-flex -translate-x-1/2 -translate-y-1/2",
78
+ className
79
+ ),
80
+ "data-comment-pin": true,
81
+ "data-comment-pin-state": state,
82
+ ref,
83
+ role: "img",
84
+ style: { left: x, top: y },
85
+ ...rest,
86
+ children: onActivate ? /* @__PURE__ */ jsx(
87
+ "button",
88
+ {
89
+ "aria-label": ariaLabel,
90
+ className: "relative inline-flex rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
91
+ "data-comment-pin-trigger": true,
92
+ onClick: handleClick,
93
+ type: "button",
94
+ children: body
95
+ }
96
+ ) : /* @__PURE__ */ jsx("span", { className: "relative inline-flex", children: body })
97
+ }
98
+ );
99
+ }
100
+ );
101
+ CommentPin.displayName = "CommentPin";
102
+ export {
103
+ CommentPin
104
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ CommentPin
3
+ } from "./comment-pin";
4
+ export {
5
+ CommentPin
6
+ };