@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,73 @@
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 STATE_LABEL = {
8
+ error: "Sync error",
9
+ live: "Live",
10
+ offline: "Offline",
11
+ reconnecting: "Reconnecting",
12
+ syncing: "Syncing"
13
+ };
14
+ const STATE_DOT = {
15
+ error: "bg-red-500",
16
+ live: "bg-emerald-500",
17
+ offline: "bg-muted-foreground",
18
+ reconnecting: "bg-amber-500 animate-pulse",
19
+ syncing: "bg-blue-500 animate-pulse"
20
+ };
21
+ const STATE_TEXT = {
22
+ error: "text-red-700 dark:text-red-300",
23
+ live: "text-emerald-700 dark:text-emerald-300",
24
+ offline: "text-muted-foreground",
25
+ reconnecting: "text-amber-700 dark:text-amber-300",
26
+ syncing: "text-blue-700 dark:text-blue-300"
27
+ };
28
+ const DEFAULT_LABELS = {
29
+ region: "Presence sync"
30
+ };
31
+ const PresenceSyncIndicator = forwardRef((props, ref) => {
32
+ const { className, label, labels, state, status, ...rest } = props;
33
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
34
+ const text = label ?? STATE_LABEL[state];
35
+ return /* @__PURE__ */ jsxs(
36
+ "div",
37
+ {
38
+ "aria-label": `${resolvedLabels.region}: ${STATE_LABEL[state]}`,
39
+ className: cn(
40
+ "inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-2 py-1 text-[11px] shadow-sm",
41
+ className
42
+ ),
43
+ "data-presence-state": state,
44
+ "data-presence-sync": true,
45
+ ref,
46
+ role: "status",
47
+ ...rest,
48
+ children: [
49
+ /* @__PURE__ */ jsx(
50
+ "span",
51
+ {
52
+ "aria-hidden": "true",
53
+ className: cn("h-1.5 w-1.5 rounded-full", STATE_DOT[state]),
54
+ "data-presence-sync-dot": true
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsx("span", { className: cn("font-medium", STATE_TEXT[state]), children: text }),
58
+ status ? /* @__PURE__ */ jsx(
59
+ "span",
60
+ {
61
+ className: "text-[10px] text-muted-foreground",
62
+ "data-presence-sync-status": true,
63
+ children: status
64
+ }
65
+ ) : null
66
+ ]
67
+ }
68
+ );
69
+ });
70
+ PresenceSyncIndicator.displayName = "PresenceSyncIndicator";
71
+ export {
72
+ PresenceSyncIndicator
73
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ ProgressTracker,
3
+ ProgressTrackerBadge,
4
+ ProgressTrackerModule,
5
+ ProgressTrackerModules,
6
+ ProgressTrackerOverview,
7
+ ProgressTrackerStat,
8
+ ProgressTrackerStats,
9
+ useProgressTrackerContext
10
+ } from "./progress-tracker";
11
+ export {
12
+ ProgressTracker,
13
+ ProgressTrackerBadge,
14
+ ProgressTrackerModule,
15
+ ProgressTrackerModules,
16
+ ProgressTrackerOverview,
17
+ ProgressTrackerStat,
18
+ ProgressTrackerStats,
19
+ useProgressTrackerContext
20
+ };
@@ -0,0 +1,527 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../lib/utils";
5
+ import { Badge } from "../badge";
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle
12
+ } from "../card";
13
+ import { CHECKLIST_PROGRESS_EVENT } from "../checklist";
14
+ import { ProgressBar } from "../progress-bar";
15
+ const ProgressTrackerContext = React.createContext(null);
16
+ function clampPercentage(value) {
17
+ if (Number.isNaN(value)) return 0;
18
+ if (value <= 1) return Math.round(Math.max(0, value) * 100);
19
+ return Math.round(Math.min(Math.max(0, value), 100));
20
+ }
21
+ const STATUS_LABELS = {
22
+ available: "Available",
23
+ completed: "Completed",
24
+ "in-progress": "In progress",
25
+ locked: "Locked"
26
+ };
27
+ const STATUS_CLASSES = {
28
+ available: "border-secondary bg-secondary text-secondary-foreground",
29
+ completed: "border-primary/20 bg-primary text-primary-foreground",
30
+ "in-progress": "border-primary/20 bg-primary/10 text-primary",
31
+ locked: "border-border bg-muted text-muted-foreground"
32
+ };
33
+ function getStatusLabel(status) {
34
+ return STATUS_LABELS[status];
35
+ }
36
+ function getStatusClasses(status) {
37
+ return STATUS_CLASSES[status];
38
+ }
39
+ function readPersistedChecklistItems(persistKey) {
40
+ if (!persistKey || typeof window === "undefined") return [];
41
+ try {
42
+ const saved = localStorage.getItem(`checklist:${persistKey}`);
43
+ if (!saved) return [];
44
+ const parsed = JSON.parse(saved);
45
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
46
+ } catch {
47
+ return [];
48
+ }
49
+ }
50
+ function areStringArraysEqual(left, right) {
51
+ if (left.length !== right.length) return false;
52
+ return left.every((value, index) => value === right[index]);
53
+ }
54
+ function getChecklistPersistKey(event) {
55
+ if (!(event instanceof CustomEvent)) return null;
56
+ const detail = event.detail;
57
+ if (typeof detail !== "object" || detail === null) return null;
58
+ if (!("persistKey" in detail)) return null;
59
+ const { persistKey } = detail;
60
+ return typeof persistKey === "string" ? persistKey : null;
61
+ }
62
+ function getResolvedLessonProgress(module) {
63
+ if (!module.persistKey || !module.checklistItems?.length) {
64
+ return {
65
+ completedLessons: module.completedLessons ?? 0,
66
+ totalLessons: module.lessons
67
+ };
68
+ }
69
+ const validIds = new Set(module.checklistItems.map((item) => item.id));
70
+ const persistedIds = readPersistedChecklistItems(module.persistKey);
71
+ const completedLessons = persistedIds.filter((id) => validIds.has(id)).length;
72
+ return {
73
+ completedLessons,
74
+ totalLessons: module.checklistItems.length
75
+ };
76
+ }
77
+ function useChecklistProgress(checklistItems = [], persistKey) {
78
+ const total = checklistItems.length;
79
+ const [persistedIds, setPersistedIds] = React.useState(
80
+ () => readPersistedChecklistItems(persistKey)
81
+ );
82
+ const setPersistedIdsIfChanged = React.useCallback((nextIds) => {
83
+ setPersistedIds(
84
+ (currentIds) => areStringArraysEqual(currentIds, nextIds) ? currentIds : nextIds
85
+ );
86
+ }, []);
87
+ React.useEffect(() => {
88
+ setPersistedIdsIfChanged(readPersistedChecklistItems(persistKey));
89
+ }, [persistKey, setPersistedIdsIfChanged]);
90
+ React.useEffect(() => {
91
+ if (!persistKey || typeof window === "undefined") return;
92
+ const sync = (event) => {
93
+ const eventPersistKey = getChecklistPersistKey(event);
94
+ if (eventPersistKey && eventPersistKey !== persistKey) return;
95
+ setPersistedIdsIfChanged(readPersistedChecklistItems(persistKey));
96
+ };
97
+ const syncEventListener = (event) => {
98
+ sync(event);
99
+ };
100
+ window.addEventListener("storage", sync);
101
+ window.addEventListener("focus", sync);
102
+ window.addEventListener(CHECKLIST_PROGRESS_EVENT, syncEventListener);
103
+ return () => {
104
+ window.removeEventListener("storage", sync);
105
+ window.removeEventListener("focus", sync);
106
+ window.removeEventListener(CHECKLIST_PROGRESS_EVENT, syncEventListener);
107
+ };
108
+ }, [persistKey, setPersistedIdsIfChanged]);
109
+ if (!persistKey || total === 0) return null;
110
+ const validIds = new Set(checklistItems.map((item) => item.id));
111
+ const completedCount = persistedIds.filter((id) => validIds.has(id)).length;
112
+ return {
113
+ completedCount,
114
+ progress: total > 0 ? Math.round(completedCount / total * 100) : 0,
115
+ total
116
+ };
117
+ }
118
+ function useProgressTrackerContext() {
119
+ const context = React.useContext(ProgressTrackerContext);
120
+ if (!context) {
121
+ throw new Error(
122
+ "ProgressTracker compound components must be used within <ProgressTracker />."
123
+ );
124
+ }
125
+ return context;
126
+ }
127
+ function ProgressTrackerRoot({
128
+ children,
129
+ className,
130
+ modules = [],
131
+ overallProgress,
132
+ streak = 0,
133
+ title = "Learning progress",
134
+ ...props
135
+ }) {
136
+ const contextValue = React.useMemo(
137
+ () => ({
138
+ modules,
139
+ overallProgress: clampPercentage(overallProgress),
140
+ streak,
141
+ title
142
+ }),
143
+ [modules, overallProgress, streak, title]
144
+ );
145
+ return /* @__PURE__ */ jsx(ProgressTrackerContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
146
+ "section",
147
+ {
148
+ "aria-label": title,
149
+ className: cn("grid gap-6", className),
150
+ ...props,
151
+ children
152
+ }
153
+ ) });
154
+ }
155
+ function ProgressTrackerOverview({
156
+ className,
157
+ description = "Track completion across modules, lessons, and exercises.",
158
+ label = "Overall progress",
159
+ ...props
160
+ }) {
161
+ const { modules, overallProgress, streak, title } = useProgressTrackerContext();
162
+ const trackedPersistKeys = React.useMemo(
163
+ () => modules.map((module) => module.persistKey).filter(Boolean),
164
+ [modules]
165
+ );
166
+ const [, forceChecklistRefresh] = React.useState(0);
167
+ React.useEffect(() => {
168
+ if (trackedPersistKeys.length === 0 || typeof window === "undefined") {
169
+ return;
170
+ }
171
+ const trackedKeys = new Set(trackedPersistKeys);
172
+ const sync = (event) => {
173
+ const eventPersistKey = getChecklistPersistKey(event);
174
+ if (eventPersistKey && !trackedKeys.has(eventPersistKey)) return;
175
+ forceChecklistRefresh((version) => version + 1);
176
+ };
177
+ const syncEventListener = (event) => {
178
+ sync(event);
179
+ };
180
+ window.addEventListener("storage", sync);
181
+ window.addEventListener("focus", sync);
182
+ window.addEventListener(CHECKLIST_PROGRESS_EVENT, syncEventListener);
183
+ return () => {
184
+ window.removeEventListener("storage", sync);
185
+ window.removeEventListener("focus", sync);
186
+ window.removeEventListener(CHECKLIST_PROGRESS_EVENT, syncEventListener);
187
+ };
188
+ }, [trackedPersistKeys]);
189
+ const radius = 54;
190
+ const circumference = 2 * Math.PI * radius;
191
+ const offset = circumference - overallProgress / 100 * circumference;
192
+ const completedModules = modules.filter(
193
+ (module) => module.status === "completed"
194
+ ).length;
195
+ const lessonTotals = modules.reduce(
196
+ (totals, module) => {
197
+ const resolvedProgress = getResolvedLessonProgress(module);
198
+ return {
199
+ completedLessons: totals.completedLessons + resolvedProgress.completedLessons,
200
+ totalLessons: totals.totalLessons + resolvedProgress.totalLessons
201
+ };
202
+ },
203
+ { completedLessons: 0, totalLessons: 0 }
204
+ );
205
+ const totalLessons = lessonTotals.totalLessons;
206
+ const completedLessons = lessonTotals.completedLessons;
207
+ const totalExercises = modules.reduce(
208
+ (sum, module) => sum + (module.exercises ?? 0),
209
+ 0
210
+ );
211
+ const completedExercises = modules.reduce(
212
+ (sum, module) => sum + (module.completedExercises ?? 0),
213
+ 0
214
+ );
215
+ return /* @__PURE__ */ jsxs(Card, { className: cn("overflow-hidden", className), ...props, children: [
216
+ /* @__PURE__ */ jsxs(CardHeader, { className: "gap-4 sm:flex-row sm:items-start sm:justify-between", children: [
217
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
218
+ /* @__PURE__ */ jsx(Badge, { className: "w-fit", variant: "secondary", children: label }),
219
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
220
+ /* @__PURE__ */ jsx(CardTitle, { children: title }),
221
+ /* @__PURE__ */ jsx(CardDescription, { children: description })
222
+ ] })
223
+ ] }),
224
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-full border bg-background px-3 py-1 text-sm text-muted-foreground whitespace-nowrap", children: [
225
+ /* @__PURE__ */ jsx(
226
+ "span",
227
+ {
228
+ "aria-hidden": "true",
229
+ className: "inline-flex h-2.5 w-2.5 rounded-full bg-primary"
230
+ }
231
+ ),
232
+ /* @__PURE__ */ jsxs("span", { children: [
233
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: streak }),
234
+ " day",
235
+ streak === 1 ? "" : "s",
236
+ " streak"
237
+ ] })
238
+ ] })
239
+ ] }),
240
+ /* @__PURE__ */ jsxs(CardContent, { className: "grid gap-6 lg:grid-cols-[auto,1fr] lg:items-center", children: [
241
+ /* @__PURE__ */ jsxs("div", { className: "mx-auto flex flex-col items-center gap-3 text-center", children: [
242
+ /* @__PURE__ */ jsxs("div", { className: "relative flex h-36 w-36 items-center justify-center", children: [
243
+ /* @__PURE__ */ jsxs("svg", { className: "h-36 w-36 -rotate-90", viewBox: "0 0 120 120", children: [
244
+ /* @__PURE__ */ jsx(
245
+ "circle",
246
+ {
247
+ className: "stroke-muted",
248
+ cx: "60",
249
+ cy: "60",
250
+ fill: "none",
251
+ r: radius,
252
+ strokeWidth: "10"
253
+ }
254
+ ),
255
+ /* @__PURE__ */ jsx(
256
+ "circle",
257
+ {
258
+ "aria-label": label,
259
+ "aria-valuemax": 100,
260
+ "aria-valuemin": 0,
261
+ "aria-valuenow": overallProgress,
262
+ className: "stroke-primary transition-all duration-500",
263
+ cx: "60",
264
+ cy: "60",
265
+ fill: "none",
266
+ r: radius,
267
+ role: "progressbar",
268
+ strokeDasharray: circumference,
269
+ strokeDashoffset: offset,
270
+ strokeLinecap: "round",
271
+ strokeWidth: "10"
272
+ }
273
+ )
274
+ ] }),
275
+ /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: [
276
+ /* @__PURE__ */ jsxs("span", { className: "text-3xl font-semibold text-foreground", children: [
277
+ overallProgress,
278
+ "%"
279
+ ] }),
280
+ /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-[0.2em] text-muted-foreground", children: "Complete" })
281
+ ] })
282
+ ] }),
283
+ /* @__PURE__ */ jsxs("p", { className: "max-w-52 text-sm text-muted-foreground", children: [
284
+ completedModules,
285
+ " of ",
286
+ modules.length,
287
+ " modules completed."
288
+ ] })
289
+ ] }),
290
+ /* @__PURE__ */ jsxs("dl", { className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-4", children: [
291
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-muted/30 p-4", children: [
292
+ /* @__PURE__ */ jsx("dt", { className: "text-sm text-muted-foreground", children: "Modules" }),
293
+ /* @__PURE__ */ jsxs("dd", { className: "mt-2 text-2xl font-semibold text-foreground", children: [
294
+ completedModules,
295
+ "/",
296
+ modules.length
297
+ ] })
298
+ ] }),
299
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-muted/30 p-4", children: [
300
+ /* @__PURE__ */ jsx("dt", { className: "text-sm text-muted-foreground", children: "Lessons" }),
301
+ /* @__PURE__ */ jsxs("dd", { className: "mt-2 text-2xl font-semibold text-foreground", children: [
302
+ completedLessons,
303
+ "/",
304
+ totalLessons
305
+ ] })
306
+ ] }),
307
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-muted/30 p-4", children: [
308
+ /* @__PURE__ */ jsx("dt", { className: "text-sm text-muted-foreground", children: "Exercises" }),
309
+ /* @__PURE__ */ jsxs("dd", { className: "mt-2 text-2xl font-semibold text-foreground", children: [
310
+ completedExercises,
311
+ "/",
312
+ totalExercises
313
+ ] })
314
+ ] }),
315
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-muted/30 p-4", children: [
316
+ /* @__PURE__ */ jsx("dt", { className: "text-sm text-muted-foreground", children: "Momentum" }),
317
+ /* @__PURE__ */ jsxs("dd", { className: "mt-2 text-2xl font-semibold text-foreground", children: [
318
+ streak,
319
+ " day",
320
+ streak === 1 ? "" : "s"
321
+ ] })
322
+ ] })
323
+ ] })
324
+ ] })
325
+ ] });
326
+ }
327
+ function ProgressTrackerModules({
328
+ children,
329
+ className,
330
+ ...props
331
+ }) {
332
+ return /* @__PURE__ */ jsx(
333
+ "div",
334
+ {
335
+ className: cn("grid gap-4 md:grid-cols-2 xl:grid-cols-3", className),
336
+ ...props,
337
+ children
338
+ }
339
+ );
340
+ }
341
+ function ProgressTrackerModule({
342
+ badge,
343
+ checklistItems,
344
+ className,
345
+ completedExercises,
346
+ completedLessons = 0,
347
+ currentLesson,
348
+ description,
349
+ exercises,
350
+ href,
351
+ id,
352
+ lessons,
353
+ persistKey,
354
+ progress,
355
+ skills = [],
356
+ status,
357
+ timeSpent,
358
+ title,
359
+ ...props
360
+ }) {
361
+ const checklistProgress = useChecklistProgress(checklistItems, persistKey);
362
+ const resolvedLessons = checklistProgress?.completedCount ?? completedLessons;
363
+ const resolvedLessonTotal = checklistProgress?.total || lessons;
364
+ const progressPercent = checklistProgress?.progress ?? clampPercentage(progress);
365
+ const progressValue = Math.min(resolvedLessons, resolvedLessonTotal);
366
+ const safeExerciseTotal = exercises ?? 0;
367
+ const safeExerciseComplete = Math.min(
368
+ completedExercises ?? 0,
369
+ safeExerciseTotal
370
+ );
371
+ const card = /* @__PURE__ */ jsxs(
372
+ Card,
373
+ {
374
+ "aria-disabled": status === "locked",
375
+ className: cn(
376
+ "h-full",
377
+ status === "locked" && "opacity-80",
378
+ href && "transition-shadow hover:shadow-md focus-within:shadow-md",
379
+ className
380
+ ),
381
+ id,
382
+ ...props,
383
+ children: [
384
+ /* @__PURE__ */ jsxs(CardHeader, { className: "gap-4", children: [
385
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
386
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
387
+ /* @__PURE__ */ jsx(CardTitle, { className: "text-xl", children: title }),
388
+ description ? /* @__PURE__ */ jsx(CardDescription, { children: description }) : null
389
+ ] }),
390
+ /* @__PURE__ */ jsx(
391
+ Badge,
392
+ {
393
+ className: cn("whitespace-nowrap", getStatusClasses(status)),
394
+ variant: "outline",
395
+ children: getStatusLabel(status)
396
+ }
397
+ )
398
+ ] }),
399
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
400
+ badge ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: badge }) : null,
401
+ currentLesson ? /* @__PURE__ */ jsxs(Badge, { variant: "outline", children: [
402
+ "Current: ",
403
+ currentLesson
404
+ ] }) : null,
405
+ timeSpent ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: timeSpent }) : null
406
+ ] })
407
+ ] }),
408
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
409
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
410
+ /* @__PURE__ */ jsx(
411
+ "div",
412
+ {
413
+ "aria-label": `${title} progress`,
414
+ "aria-valuemax": 100,
415
+ "aria-valuemin": 0,
416
+ "aria-valuenow": progressPercent,
417
+ role: "progressbar",
418
+ children: /* @__PURE__ */ jsx(
419
+ ProgressBar,
420
+ {
421
+ completedLabel: "lessons",
422
+ currentLabel: `${progressPercent}% complete`,
423
+ isComplete: status === "completed",
424
+ max: resolvedLessonTotal,
425
+ showLabels: true,
426
+ value: progressValue
427
+ }
428
+ )
429
+ }
430
+ ),
431
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2 text-sm text-muted-foreground sm:grid-cols-2", children: [
432
+ /* @__PURE__ */ jsxs("span", { children: [
433
+ "Lessons:",
434
+ " ",
435
+ /* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground", children: [
436
+ resolvedLessons,
437
+ "/",
438
+ resolvedLessonTotal
439
+ ] })
440
+ ] }),
441
+ /* @__PURE__ */ jsxs("span", { children: [
442
+ "Exercises:",
443
+ " ",
444
+ /* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground", children: [
445
+ safeExerciseComplete,
446
+ "/",
447
+ safeExerciseTotal
448
+ ] })
449
+ ] })
450
+ ] })
451
+ ] }),
452
+ skills.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: skills.map((skill) => /* @__PURE__ */ jsx(ProgressTrackerBadge, { children: skill }, skill)) }) : null
453
+ ] })
454
+ ]
455
+ }
456
+ );
457
+ if (!href || status === "locked") return card;
458
+ return /* @__PURE__ */ jsx(
459
+ "a",
460
+ {
461
+ className: "block rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
462
+ href,
463
+ children: card
464
+ }
465
+ );
466
+ }
467
+ function ProgressTrackerStats({
468
+ children,
469
+ className,
470
+ ...props
471
+ }) {
472
+ return /* @__PURE__ */ jsx(
473
+ "div",
474
+ {
475
+ className: cn("grid gap-4 sm:grid-cols-2 xl:grid-cols-4", className),
476
+ ...props,
477
+ children
478
+ }
479
+ );
480
+ }
481
+ function ProgressTrackerStat({
482
+ className,
483
+ label,
484
+ value,
485
+ ...props
486
+ }) {
487
+ return /* @__PURE__ */ jsx(Card, { className: cn("h-full", className), ...props, children: /* @__PURE__ */ jsxs(CardContent, { className: "flex h-full flex-col justify-center gap-2 p-6", children: [
488
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: label }),
489
+ /* @__PURE__ */ jsx("span", { className: "text-2xl font-semibold text-foreground", children: value })
490
+ ] }) });
491
+ }
492
+ function ProgressTrackerBadge({
493
+ children,
494
+ className,
495
+ ...props
496
+ }) {
497
+ return /* @__PURE__ */ jsx(
498
+ Badge,
499
+ {
500
+ className: cn(
501
+ "px-3 py-1 text-[11px] uppercase tracking-wide whitespace-nowrap",
502
+ className
503
+ ),
504
+ variant: "secondary",
505
+ ...props,
506
+ children
507
+ }
508
+ );
509
+ }
510
+ const ProgressTracker = Object.assign(ProgressTrackerRoot, {
511
+ Badge: ProgressTrackerBadge,
512
+ Module: ProgressTrackerModule,
513
+ Modules: ProgressTrackerModules,
514
+ Overview: ProgressTrackerOverview,
515
+ Stat: ProgressTrackerStat,
516
+ Stats: ProgressTrackerStats
517
+ });
518
+ export {
519
+ ProgressTracker,
520
+ ProgressTrackerBadge,
521
+ ProgressTrackerModule,
522
+ ProgressTrackerModules,
523
+ ProgressTrackerOverview,
524
+ ProgressTrackerStat,
525
+ ProgressTrackerStats,
526
+ useProgressTrackerContext
527
+ };
@@ -0,0 +1,4 @@
1
+ import { RightDock } from "./right-dock";
2
+ export {
3
+ RightDock
4
+ };
@@ -0,0 +1,28 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "../../lib/utils";
4
+ const RightDock = forwardRef(
5
+ ({ children, className, footer, header, title, ...props }, ref) => /* @__PURE__ */ jsxs(
6
+ "aside",
7
+ {
8
+ className: cn(
9
+ "flex h-full min-w-[18rem] max-w-[24rem] shrink-0 flex-col border-l border-border bg-background",
10
+ className
11
+ ),
12
+ ref,
13
+ ...props,
14
+ children: [
15
+ header || title ? /* @__PURE__ */ jsxs("div", { className: "border-b border-border/60 px-4 py-3", children: [
16
+ title ? /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-foreground", children: title }) : null,
17
+ header ? /* @__PURE__ */ jsx("div", { className: "mt-2", children: header }) : null
18
+ ] }) : null,
19
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto p-4", children }),
20
+ footer ? /* @__PURE__ */ jsx("div", { className: "border-t border-border/60 p-4", children: footer }) : null
21
+ ]
22
+ }
23
+ )
24
+ );
25
+ RightDock.displayName = "RightDock";
26
+ export {
27
+ RightDock
28
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ RunTimeline
3
+ } from "./run-timeline";
4
+ export {
5
+ RunTimeline
6
+ };