@vllnt/ui 0.2.0 → 0.2.1-canary.06f0e84

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 (202) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/README.md +27 -12
  3. package/dist/components/activity-log/activity-log.js +1 -0
  4. package/dist/components/agent-activity/agent-activity.js +311 -0
  5. package/dist/components/agent-activity/index.js +18 -0
  6. package/dist/components/ai-artifact/ai-artifact.js +422 -0
  7. package/dist/components/ai-artifact/index.js +24 -0
  8. package/dist/components/ai-sidebar/ai-sidebar.js +254 -0
  9. package/dist/components/ai-sidebar/index.js +22 -0
  10. package/dist/components/alert-pulse/alert-pulse.js +93 -0
  11. package/dist/components/alert-pulse/index.js +6 -0
  12. package/dist/components/anchor-port/anchor-port.js +51 -0
  13. package/dist/components/anchor-port/index.js +4 -0
  14. package/dist/components/animated-text/animated-text.js +1 -0
  15. package/dist/components/auto-reload/auto-reload.js +367 -0
  16. package/dist/components/auto-reload/index.js +6 -0
  17. package/dist/components/banner/banner.js +155 -0
  18. package/dist/components/banner/index.js +10 -0
  19. package/dist/components/bottom-activity-strip/bottom-activity-strip.js +91 -0
  20. package/dist/components/bottom-activity-strip/index.js +6 -0
  21. package/dist/components/bottom-bar/bottom-bar.js +25 -0
  22. package/dist/components/bottom-bar/index.js +4 -0
  23. package/dist/components/canvas-shell/canvas-foundation-demo.js +183 -0
  24. package/dist/components/canvas-shell/canvas-shell-route-config.js +0 -0
  25. package/dist/components/canvas-shell/canvas-shell.js +261 -0
  26. package/dist/components/canvas-shell/index.js +4 -0
  27. package/dist/components/canvas-view/canvas-view.js +461 -0
  28. package/dist/components/canvas-view/index.js +6 -0
  29. package/dist/components/chart/area-chart.js +1 -0
  30. package/dist/components/chart/line-chart.js +1 -0
  31. package/dist/components/chat-dock-section/chat-dock-section.js +56 -0
  32. package/dist/components/chat-dock-section/index.js +6 -0
  33. package/dist/components/checklist/checklist.js +7 -0
  34. package/dist/components/checklist/index.js +3 -1
  35. package/dist/components/choropleth-map/choropleth-map.js +373 -0
  36. package/dist/components/choropleth-map/index.js +10 -0
  37. package/dist/components/chronological-timeline/chronological-timeline.js +337 -0
  38. package/dist/components/chronological-timeline/index.js +8 -0
  39. package/dist/components/civilization-card/civilization-card.js +258 -0
  40. package/dist/components/civilization-card/index.js +8 -0
  41. package/dist/components/combobox/combobox.js +44 -20
  42. package/dist/components/comment-pin/comment-pin.js +104 -0
  43. package/dist/components/comment-pin/index.js +6 -0
  44. package/dist/components/connector-edge/connector-edge.js +66 -0
  45. package/dist/components/connector-edge/index.js +6 -0
  46. package/dist/components/context-lens/context-lens.js +98 -0
  47. package/dist/components/context-lens/index.js +6 -0
  48. package/dist/components/conversation-thread/conversation-thread.js +348 -0
  49. package/dist/components/conversation-thread/index.js +20 -0
  50. package/dist/components/copy-button/copy-button.js +189 -0
  51. package/dist/components/copy-button/index.js +8 -0
  52. package/dist/components/curriculum/curriculum.js +349 -0
  53. package/dist/components/curriculum/index.js +10 -0
  54. package/dist/components/data-list/data-list.js +1 -0
  55. package/dist/components/document-sibling-nav/document-sibling-nav.js +111 -0
  56. package/dist/components/document-sibling-nav/index.js +8 -0
  57. package/dist/components/edge-label/edge-label.js +26 -0
  58. package/dist/components/edge-label/index.js +4 -0
  59. package/dist/components/empty-state/empty-state.js +93 -0
  60. package/dist/components/empty-state/index.js +8 -0
  61. package/dist/components/era-comparison/era-comparison.js +198 -0
  62. package/dist/components/era-comparison/index.js +16 -0
  63. package/dist/components/floating-toolbar/floating-toolbar.js +66 -0
  64. package/dist/components/floating-toolbar/index.js +6 -0
  65. package/dist/components/follow-mode/follow-mode.js +89 -0
  66. package/dist/components/follow-mode/index.js +6 -0
  67. package/dist/components/form/form.js +432 -0
  68. package/dist/components/form/index.js +20 -0
  69. package/dist/components/gantt-chart/gantt-chart.js +331 -0
  70. package/dist/components/gantt-chart/index.js +6 -0
  71. package/dist/components/geography-quiz-map/geography-quiz-map.js +343 -0
  72. package/dist/components/geography-quiz-map/index.js +12 -0
  73. package/dist/components/glass-panel/glass-panel.js +21 -0
  74. package/dist/components/glass-panel/index.js +4 -0
  75. package/dist/components/globe-3d/globe-3d.js +417 -0
  76. package/dist/components/globe-3d/index.js +10 -0
  77. package/dist/components/group-hull/group-hull.js +29 -0
  78. package/dist/components/group-hull/index.js +4 -0
  79. package/dist/components/handoff-beacon/handoff-beacon.js +78 -0
  80. package/dist/components/handoff-beacon/index.js +6 -0
  81. package/dist/components/heat-map-overlay/heat-map-overlay.js +215 -0
  82. package/dist/components/heat-map-overlay/index.js +6 -0
  83. package/dist/components/heat-overlay/heat-overlay.js +92 -0
  84. package/dist/components/heat-overlay/index.js +6 -0
  85. package/dist/components/historic-timeline/historic-timeline.js +342 -0
  86. package/dist/components/historic-timeline/index.js +6 -0
  87. package/dist/components/historical-figure-card/historical-figure-card.js +273 -0
  88. package/dist/components/historical-figure-card/index.js +6 -0
  89. package/dist/components/index.js +568 -1
  90. package/dist/components/infinite-plane/index.js +6 -0
  91. package/dist/components/infinite-plane/infinite-plane.js +75 -0
  92. package/dist/components/interactive-timeline/index.js +16 -0
  93. package/dist/components/interactive-timeline/interactive-timeline.js +708 -0
  94. package/dist/components/jarvis-dock/index.js +6 -0
  95. package/dist/components/jarvis-dock/jarvis-dock.js +98 -0
  96. package/dist/components/kbd/index.js +5 -0
  97. package/dist/components/kbd/kbd.js +117 -0
  98. package/dist/components/knowledge-check/index.js +6 -0
  99. package/dist/components/knowledge-check/knowledge-check.js +448 -0
  100. package/dist/components/left-rail/index.js +4 -0
  101. package/dist/components/left-rail/left-rail.js +25 -0
  102. package/dist/components/live-cursor/index.js +6 -0
  103. package/dist/components/live-cursor/live-cursor.js +62 -0
  104. package/dist/components/map-2d/index.js +20 -0
  105. package/dist/components/map-2d/map-2d.js +455 -0
  106. package/dist/components/map-timeline/index.js +16 -0
  107. package/dist/components/map-timeline/map-timeline.js +506 -0
  108. package/dist/components/metric-cluster/index.js +6 -0
  109. package/dist/components/metric-cluster/metric-cluster.js +96 -0
  110. package/dist/components/mini-map-panel/index.js +6 -0
  111. package/dist/components/mini-map-panel/mini-map-panel.js +74 -0
  112. package/dist/components/model-comparison/index.js +12 -0
  113. package/dist/components/model-comparison/model-comparison.js +211 -0
  114. package/dist/components/multi-select/index.js +6 -0
  115. package/dist/components/multi-select/multi-select.js +258 -0
  116. package/dist/components/multi-select-lasso/index.js +6 -0
  117. package/dist/components/multi-select-lasso/multi-select-lasso.js +76 -0
  118. package/dist/components/newsletter-signup/index.js +8 -0
  119. package/dist/components/newsletter-signup/newsletter-signup.js +269 -0
  120. package/dist/components/object-card/index.js +6 -0
  121. package/dist/components/object-card/object-card.js +126 -0
  122. package/dist/components/object-handle/index.js +4 -0
  123. package/dist/components/object-handle/object-handle.js +38 -0
  124. package/dist/components/object-inspector/index.js +6 -0
  125. package/dist/components/object-inspector/object-inspector.js +136 -0
  126. package/dist/components/overview-board/index.js +8 -0
  127. package/dist/components/overview-board/overview-board.js +127 -0
  128. package/dist/components/parallel-timeline/index.js +6 -0
  129. package/dist/components/parallel-timeline/parallel-timeline.js +251 -0
  130. package/dist/components/playback-ghost/index.js +6 -0
  131. package/dist/components/playback-ghost/playback-ghost.js +83 -0
  132. package/dist/components/policy-delivery-panel/index.js +6 -0
  133. package/dist/components/policy-delivery-panel/policy-delivery-panel.js +99 -0
  134. package/dist/components/presence-stack/index.js +6 -0
  135. package/dist/components/presence-stack/presence-stack.js +108 -0
  136. package/dist/components/presence-sync-indicator/index.js +6 -0
  137. package/dist/components/presence-sync-indicator/presence-sync-indicator.js +73 -0
  138. package/dist/components/pricing-table/index.js +8 -0
  139. package/dist/components/pricing-table/pricing-table.js +247 -0
  140. package/dist/components/primary-source-viewer/index.js +26 -0
  141. package/dist/components/primary-source-viewer/primary-source-viewer.js +439 -0
  142. package/dist/components/progress-tracker/index.js +20 -0
  143. package/dist/components/progress-tracker/progress-tracker.js +527 -0
  144. package/dist/components/prompt-templates/index.js +6 -0
  145. package/dist/components/prompt-templates/prompt-templates.js +403 -0
  146. package/dist/components/property-section/index.js +6 -0
  147. package/dist/components/property-section/property-section.js +101 -0
  148. package/dist/components/relationship-inspector/index.js +6 -0
  149. package/dist/components/relationship-inspector/relationship-inspector.js +102 -0
  150. package/dist/components/right-dock/index.js +4 -0
  151. package/dist/components/right-dock/right-dock.js +28 -0
  152. package/dist/components/route-map/index.js +6 -0
  153. package/dist/components/route-map/route-map.js +339 -0
  154. package/dist/components/routing-assignment-panel/index.js +6 -0
  155. package/dist/components/routing-assignment-panel/routing-assignment-panel.js +122 -0
  156. package/dist/components/run-timeline/index.js +6 -0
  157. package/dist/components/run-timeline/run-timeline.js +221 -0
  158. package/dist/components/runtime-overview-panel/index.js +6 -0
  159. package/dist/components/runtime-overview-panel/runtime-overview-panel.js +89 -0
  160. package/dist/components/segmented-control/index.js +12 -0
  161. package/dist/components/segmented-control/segmented-control.js +61 -0
  162. package/dist/components/selection-halo/index.js +6 -0
  163. package/dist/components/selection-halo/selection-halo.js +72 -0
  164. package/dist/components/selection-presence/index.js +6 -0
  165. package/dist/components/selection-presence/selection-presence.js +50 -0
  166. package/dist/components/snap-guides/index.js +6 -0
  167. package/dist/components/snap-guides/snap-guides.js +45 -0
  168. package/dist/components/spinner/unicode-spinner.js +1 -0
  169. package/dist/components/state-badge-overlay/index.js +6 -0
  170. package/dist/components/state-badge-overlay/state-badge-overlay.js +90 -0
  171. package/dist/components/sticky-metric/index.js +6 -0
  172. package/dist/components/sticky-metric/sticky-metric.js +83 -0
  173. package/dist/components/story-map/index.js +8 -0
  174. package/dist/components/story-map/story-map.js +414 -0
  175. package/dist/components/tags-input/index.js +4 -0
  176. package/dist/components/tags-input/tags-input.js +178 -0
  177. package/dist/components/thread-bubble/index.js +6 -0
  178. package/dist/components/thread-bubble/thread-bubble.js +85 -0
  179. package/dist/components/threshold-ring/index.js +6 -0
  180. package/dist/components/threshold-ring/threshold-ring.js +160 -0
  181. package/dist/components/timeline/index.js +12 -0
  182. package/dist/components/timeline/timeline.js +239 -0
  183. package/dist/components/timeline-scrubber/index.js +6 -0
  184. package/dist/components/timeline-scrubber/timeline-scrubber.js +179 -0
  185. package/dist/components/top-bar/index.js +4 -0
  186. package/dist/components/top-bar/top-bar.js +31 -0
  187. package/dist/components/transaction-list/index.js +14 -0
  188. package/dist/components/transaction-list/transaction-list.js +226 -0
  189. package/dist/components/tree-view/index.js +6 -0
  190. package/dist/components/tree-view/tree-view.js +298 -0
  191. package/dist/components/usage-breakdown/usage-breakdown.js +1 -0
  192. package/dist/components/viewport-bookmarks/index.js +6 -0
  193. package/dist/components/viewport-bookmarks/viewport-bookmarks.js +116 -0
  194. package/dist/components/workspace-switcher/index.js +6 -0
  195. package/dist/components/workspace-switcher/workspace-switcher.js +61 -0
  196. package/dist/components/world-breadcrumbs/index.js +6 -0
  197. package/dist/components/world-breadcrumbs/world-breadcrumbs.js +114 -0
  198. package/dist/components/zoom-hud/index.js +4 -0
  199. package/dist/components/zoom-hud/zoom-hud.js +61 -0
  200. package/dist/index.d.ts +7906 -225
  201. package/dist/index.js +3 -1
  202. package/package.json +9 -5
@@ -0,0 +1,98 @@
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 TONE_CLASS = {
8
+ danger: "text-red-600 dark:text-red-400",
9
+ neutral: "text-foreground",
10
+ primary: "text-blue-600 dark:text-blue-400",
11
+ success: "text-emerald-600 dark:text-emerald-400"
12
+ };
13
+ const DEFAULT_LABELS = {
14
+ paletteTrigger: "Open command palette",
15
+ region: "Jarvis dock"
16
+ };
17
+ const ActionButton = (props) => {
18
+ const { action } = props;
19
+ const tone = action.tone ?? "neutral";
20
+ const handleClick = () => {
21
+ action.onActivate();
22
+ };
23
+ return /* @__PURE__ */ jsxs(
24
+ "button",
25
+ {
26
+ className: "group relative flex h-12 w-12 flex-col items-center justify-center gap-0.5 rounded-md border border-transparent text-[10px] uppercase tracking-wide text-muted-foreground transition-colors hover:border-border hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
27
+ "data-jarvis-action": action.id,
28
+ "data-jarvis-tone": tone,
29
+ onClick: handleClick,
30
+ type: "button",
31
+ children: [
32
+ /* @__PURE__ */ jsx(
33
+ "span",
34
+ {
35
+ "aria-hidden": "true",
36
+ className: cn(
37
+ "text-base leading-none transition-transform group-hover:scale-110",
38
+ TONE_CLASS[tone]
39
+ ),
40
+ children: action.glyph
41
+ }
42
+ ),
43
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: action.label }),
44
+ action.badge ? /* @__PURE__ */ jsx(
45
+ "span",
46
+ {
47
+ className: "absolute right-1 top-1 inline-flex min-h-[14px] min-w-[14px] items-center justify-center rounded-full bg-foreground px-1 text-[9px] font-medium text-background",
48
+ "data-jarvis-badge": true,
49
+ children: action.badge
50
+ }
51
+ ) : null
52
+ ]
53
+ }
54
+ );
55
+ };
56
+ const JarvisDock = forwardRef(
57
+ (props, ref) => {
58
+ const { actions, className, labels, onOpenPalette, ...rest } = props;
59
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
60
+ const handlePalette = () => {
61
+ onOpenPalette?.();
62
+ };
63
+ return /* @__PURE__ */ jsxs(
64
+ "nav",
65
+ {
66
+ "aria-label": resolvedLabels.region,
67
+ className: cn(
68
+ "inline-flex items-center gap-1 rounded-2xl border bg-background/90 p-1.5 shadow-md backdrop-blur",
69
+ className
70
+ ),
71
+ "data-jarvis-dock": true,
72
+ ref,
73
+ ...rest,
74
+ children: [
75
+ actions.map((action) => /* @__PURE__ */ jsx(ActionButton, { action }, action.id)),
76
+ onOpenPalette ? /* @__PURE__ */ jsxs(Fragment, { children: [
77
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "mx-1 h-8 w-px bg-border" }),
78
+ /* @__PURE__ */ jsx(
79
+ "button",
80
+ {
81
+ "aria-label": resolvedLabels.paletteTrigger,
82
+ className: "flex h-12 w-12 items-center justify-center rounded-md border border-transparent text-base text-muted-foreground transition-colors hover:border-border hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
83
+ "data-jarvis-palette-trigger": true,
84
+ onClick: handlePalette,
85
+ type: "button",
86
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2318" })
87
+ }
88
+ )
89
+ ] }) : null
90
+ ]
91
+ }
92
+ );
93
+ }
94
+ );
95
+ JarvisDock.displayName = "JarvisDock";
96
+ export {
97
+ JarvisDock
98
+ };
@@ -0,0 +1,5 @@
1
+ import { Kbd, kbdVariants } from "./kbd";
2
+ export {
3
+ Kbd,
4
+ kbdVariants
5
+ };
@@ -0,0 +1,117 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef,
5
+ useSyncExternalStore
6
+ } from "react";
7
+ import { cva } from "class-variance-authority";
8
+ import { cn } from "../../lib/utils";
9
+ const kbdVariants = cva(
10
+ "inline-flex select-none items-center justify-center rounded border border-border bg-muted font-mono font-medium text-foreground shadow-[0_1px_0_0_hsl(var(--border))] [&>svg]:h-3 [&>svg]:w-3",
11
+ {
12
+ defaultVariants: {
13
+ size: "md"
14
+ },
15
+ variants: {
16
+ size: {
17
+ lg: "h-7 min-w-[1.75rem] px-2 text-sm",
18
+ md: "h-5 min-w-[1.25rem] px-1.5 text-xs",
19
+ sm: "h-4 min-w-[1rem] px-1 text-[10px]"
20
+ }
21
+ }
22
+ }
23
+ );
24
+ const MAC_PLATFORM_PATTERN = /mac|iphone|ipad|ipod/i;
25
+ const MODIFIER_LABELS = {
26
+ alt: { mac: "\u2325", other: "Alt" },
27
+ ctrl: { mac: "\u2303", other: "Ctrl" },
28
+ meta: { mac: "\u2318", other: "Win" },
29
+ mod: { mac: "\u2318", other: "Ctrl" },
30
+ shift: { mac: "\u21E7", other: "Shift" }
31
+ };
32
+ const SPECIAL_KEY_LABELS = {
33
+ arrowdown: "\u2193",
34
+ arrowleft: "\u2190",
35
+ arrowright: "\u2192",
36
+ arrowup: "\u2191",
37
+ backspace: "\u232B",
38
+ delete: "\u2326",
39
+ enter: "\u21B5",
40
+ escape: "Esc",
41
+ return: "\u21B5",
42
+ space: "Space",
43
+ tab: "\u21E5"
44
+ };
45
+ const SHORTCUT_SEPARATOR = /\s*\+\s*/;
46
+ function isMacPlatform() {
47
+ if (typeof navigator === "undefined") return false;
48
+ return MAC_PLATFORM_PATTERN.test(navigator.userAgent);
49
+ }
50
+ function noopUnsubscribe() {
51
+ return;
52
+ }
53
+ function subscribeNoop() {
54
+ return noopUnsubscribe;
55
+ }
56
+ function getServerSnapshot() {
57
+ return false;
58
+ }
59
+ function useIsMac() {
60
+ return useSyncExternalStore(subscribeNoop, isMacPlatform, getServerSnapshot);
61
+ }
62
+ function isModifier(value) {
63
+ return Object.hasOwn(MODIFIER_LABELS, value);
64
+ }
65
+ function formatToken(token, mac) {
66
+ const lowered = token.toLowerCase();
67
+ if (isModifier(lowered)) {
68
+ const labels = MODIFIER_LABELS[lowered];
69
+ return mac ? labels.mac : labels.other;
70
+ }
71
+ const special = SPECIAL_KEY_LABELS[lowered];
72
+ if (special !== void 0) return special;
73
+ return token.length === 1 ? token.toUpperCase() : token;
74
+ }
75
+ const Kbd = forwardRef(
76
+ ({ children, className, shortcut, size, ...rest }, ref) => {
77
+ const isMac = useIsMac();
78
+ if (children !== void 0) {
79
+ return /* @__PURE__ */ jsx(
80
+ "kbd",
81
+ {
82
+ className: cn(kbdVariants({ size }), className),
83
+ ref,
84
+ ...rest,
85
+ children
86
+ }
87
+ );
88
+ }
89
+ if (shortcut) {
90
+ const tokens = shortcut.split(SHORTCUT_SEPARATOR).filter(Boolean);
91
+ const ariaLabel = tokens.map((token) => formatToken(token, isMac)).join(" + ");
92
+ return /* @__PURE__ */ jsx("span", { "aria-label": ariaLabel, className: "inline-flex items-center gap-1", children: tokens.map((token, index) => /* @__PURE__ */ jsx(
93
+ "kbd",
94
+ {
95
+ className: cn(kbdVariants({ size }), className),
96
+ ref: index === 0 ? ref : void 0,
97
+ ...index === 0 ? rest : {},
98
+ children: formatToken(token, isMac)
99
+ },
100
+ `${token}-${index.toString()}`
101
+ )) });
102
+ }
103
+ return /* @__PURE__ */ jsx(
104
+ "kbd",
105
+ {
106
+ className: cn(kbdVariants({ size }), className),
107
+ ref,
108
+ ...rest
109
+ }
110
+ );
111
+ }
112
+ );
113
+ Kbd.displayName = "Kbd";
114
+ export {
115
+ Kbd,
116
+ kbdVariants
117
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ KnowledgeCheck
3
+ } from "./knowledge-check";
4
+ export {
5
+ KnowledgeCheck
6
+ };
@@ -0,0 +1,448 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import {
4
+ forwardRef,
5
+ useCallback,
6
+ useId,
7
+ useMemo,
8
+ useState
9
+ } from "react";
10
+ import { CheckCircle2, RotateCcw, XCircle } from "lucide-react";
11
+ import { cn } from "../../lib/utils";
12
+ import { Button } from "../button/button";
13
+ import { Input } from "../input/input";
14
+ const DEFAULT_LABELS = {
15
+ back: "Back",
16
+ check: "Check",
17
+ correct: "Correct",
18
+ falseOption: "False",
19
+ incorrect: "Try again",
20
+ next: "Next",
21
+ outOf: "of",
22
+ retry: "Retry",
23
+ scored: "You scored",
24
+ trueOption: "True"
25
+ };
26
+ function compareFillBlank(question, response) {
27
+ const normalize = (value) => question.caseSensitive ? value.trim() : value.trim().toLowerCase();
28
+ return normalize(response) === normalize(question.answer);
29
+ }
30
+ function isMultipleChoiceCorrect(question, value) {
31
+ return question.options.some(
32
+ (option) => option.value === value && option.correct === true
33
+ );
34
+ }
35
+ function evaluateAnswer(question, response) {
36
+ switch (question.type) {
37
+ case "fill-blank":
38
+ return typeof response === "string" && compareFillBlank(question, response);
39
+ case "multiple-choice":
40
+ return typeof response === "string" && isMultipleChoiceCorrect(question, response);
41
+ case "true-false":
42
+ return typeof response === "boolean" && response === question.answer;
43
+ }
44
+ }
45
+ function useKnowledgeCheckController(options) {
46
+ const { onAnswer, onComplete, questions } = options;
47
+ const [index, setIndex] = useState(0);
48
+ const [responses, setResponses] = useState({});
49
+ const [answers, setAnswers] = useState({});
50
+ const [completed, setCompleted] = useState(false);
51
+ const setResponse = useCallback(
52
+ (questionId, value) => {
53
+ setResponses((current2) => ({ ...current2, [questionId]: value }));
54
+ },
55
+ []
56
+ );
57
+ const handleSubmit = useCallback(() => {
58
+ const question = questions[index];
59
+ if (!question) return;
60
+ const response = responses[question.id];
61
+ if (response === void 0) return;
62
+ const answer = {
63
+ correct: evaluateAnswer(question, response),
64
+ questionId: question.id,
65
+ response
66
+ };
67
+ setAnswers((current2) => ({ ...current2, [question.id]: answer }));
68
+ onAnswer?.(answer);
69
+ }, [index, onAnswer, questions, responses]);
70
+ const handleNext = useCallback(() => {
71
+ if (index >= questions.length - 1) {
72
+ const finalScore = computeScore(questions, answers);
73
+ setCompleted(true);
74
+ onComplete?.(finalScore);
75
+ return;
76
+ }
77
+ setIndex((value) => value + 1);
78
+ }, [answers, index, onComplete, questions]);
79
+ const handlePrevious = useCallback(() => {
80
+ setIndex((value) => Math.max(0, value - 1));
81
+ }, []);
82
+ const handleReset = useCallback(() => {
83
+ setIndex(0);
84
+ setResponses({});
85
+ setAnswers({});
86
+ setCompleted(false);
87
+ }, []);
88
+ const score = useMemo(
89
+ () => completed ? computeScore(questions, answers) : void 0,
90
+ [answers, completed, questions]
91
+ );
92
+ const current = questions[index] ?? questions[0];
93
+ if (!current) {
94
+ throw new Error("KnowledgeCheck requires at least one question");
95
+ }
96
+ return {
97
+ answers,
98
+ current,
99
+ handleNext,
100
+ handlePrevious,
101
+ handleReset,
102
+ handleSubmit,
103
+ index,
104
+ isComplete: completed,
105
+ isFirst: index === 0,
106
+ isLast: index === questions.length - 1,
107
+ responses,
108
+ score,
109
+ setResponse
110
+ };
111
+ }
112
+ function computeScore(questions, answers) {
113
+ const correct = Object.values(answers).filter(
114
+ (entry) => entry.correct
115
+ ).length;
116
+ return { answers, correct, total: questions.length };
117
+ }
118
+ function Feedback({
119
+ answer,
120
+ correctLabel,
121
+ explanation,
122
+ incorrectLabel
123
+ }) {
124
+ if (!answer) return null;
125
+ return /* @__PURE__ */ jsxs(
126
+ "div",
127
+ {
128
+ "aria-live": "polite",
129
+ className: cn(
130
+ "flex items-start gap-2 rounded-md border p-3 text-sm",
131
+ answer.correct ? "border-emerald-500/40 bg-emerald-500/10 text-emerald-900 dark:text-emerald-200" : "border-destructive/40 bg-destructive/10 text-destructive"
132
+ ),
133
+ role: "status",
134
+ children: [
135
+ answer.correct ? /* @__PURE__ */ jsx(CheckCircle2, { "aria-hidden": "true", className: "mt-0.5 h-4 w-4 shrink-0" }) : /* @__PURE__ */ jsx(XCircle, { "aria-hidden": "true", className: "mt-0.5 h-4 w-4 shrink-0" }),
136
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
137
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: answer.correct ? correctLabel : incorrectLabel }),
138
+ explanation ? /* @__PURE__ */ jsx("span", { className: "text-xs opacity-80", children: explanation }) : null
139
+ ] })
140
+ ]
141
+ }
142
+ );
143
+ }
144
+ function MultipleChoiceOption({
145
+ groupName,
146
+ onChange,
147
+ option,
148
+ value
149
+ }) {
150
+ const id = `${groupName}-${option.value}`;
151
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm hover:bg-accent", children: [
152
+ /* @__PURE__ */ jsx(
153
+ "input",
154
+ {
155
+ checked: value === option.value,
156
+ className: "h-4 w-4",
157
+ id,
158
+ name: groupName,
159
+ onChange,
160
+ type: "radio",
161
+ value: option.value
162
+ }
163
+ ),
164
+ /* @__PURE__ */ jsx("label", { className: "flex-1 cursor-pointer", htmlFor: id, children: option.label })
165
+ ] });
166
+ }
167
+ function MultipleChoiceField({
168
+ groupName,
169
+ onChange,
170
+ options,
171
+ value
172
+ }) {
173
+ const handleSelect = useCallback(
174
+ (event) => {
175
+ onChange(event.target.value);
176
+ },
177
+ [onChange]
178
+ );
179
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", role: "radiogroup", children: options.map((option) => /* @__PURE__ */ jsx(
180
+ MultipleChoiceOption,
181
+ {
182
+ groupName,
183
+ onChange: handleSelect,
184
+ option,
185
+ value
186
+ },
187
+ option.value
188
+ )) });
189
+ }
190
+ function TrueFalseField({
191
+ falseLabel,
192
+ groupName,
193
+ onChange,
194
+ trueLabel,
195
+ value
196
+ }) {
197
+ const handleSelectTrue = useCallback(() => {
198
+ onChange(true);
199
+ }, [onChange]);
200
+ const handleSelectFalse = useCallback(() => {
201
+ onChange(false);
202
+ }, [onChange]);
203
+ return /* @__PURE__ */ jsxs(
204
+ "div",
205
+ {
206
+ "aria-label": groupName,
207
+ className: "flex flex-wrap gap-2",
208
+ role: "radiogroup",
209
+ children: [
210
+ /* @__PURE__ */ jsx(
211
+ Button,
212
+ {
213
+ "aria-pressed": value === true,
214
+ onClick: handleSelectTrue,
215
+ size: "sm",
216
+ type: "button",
217
+ variant: value === true ? "default" : "outline",
218
+ children: trueLabel
219
+ }
220
+ ),
221
+ /* @__PURE__ */ jsx(
222
+ Button,
223
+ {
224
+ "aria-pressed": value === false,
225
+ onClick: handleSelectFalse,
226
+ size: "sm",
227
+ type: "button",
228
+ variant: value === false ? "default" : "outline",
229
+ children: falseLabel
230
+ }
231
+ )
232
+ ]
233
+ }
234
+ );
235
+ }
236
+ function FillBlankField({
237
+ inputId,
238
+ onChange,
239
+ value
240
+ }) {
241
+ const handleChange = useCallback(
242
+ (event) => {
243
+ onChange(event.target.value);
244
+ },
245
+ [onChange]
246
+ );
247
+ return /* @__PURE__ */ jsx(Input, { id: inputId, onChange: handleChange, value });
248
+ }
249
+ function QuestionField({
250
+ groupName,
251
+ inputId,
252
+ labels,
253
+ onResponse,
254
+ question,
255
+ response
256
+ }) {
257
+ switch (question.type) {
258
+ case "fill-blank":
259
+ return /* @__PURE__ */ jsx(
260
+ FillBlankField,
261
+ {
262
+ inputId,
263
+ onChange: onResponse,
264
+ value: typeof response === "string" ? response : ""
265
+ }
266
+ );
267
+ case "multiple-choice":
268
+ return /* @__PURE__ */ jsx(
269
+ MultipleChoiceField,
270
+ {
271
+ groupName,
272
+ onChange: onResponse,
273
+ options: question.options,
274
+ value: typeof response === "string" ? response : void 0
275
+ }
276
+ );
277
+ case "true-false":
278
+ return /* @__PURE__ */ jsx(
279
+ TrueFalseField,
280
+ {
281
+ falseLabel: labels.falseOption,
282
+ groupName,
283
+ onChange: onResponse,
284
+ trueLabel: labels.trueOption,
285
+ value: typeof response === "boolean" ? response : void 0
286
+ }
287
+ );
288
+ }
289
+ }
290
+ function ScoreSummary({
291
+ labels,
292
+ onRetry,
293
+ score
294
+ }) {
295
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 rounded-lg border border-border bg-muted/20 p-6 text-center", children: [
296
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: labels.scored }),
297
+ /* @__PURE__ */ jsxs("p", { className: "text-3xl font-bold tracking-tight text-foreground", children: [
298
+ score.correct,
299
+ " ",
300
+ labels.outOf,
301
+ " ",
302
+ score.total
303
+ ] }),
304
+ /* @__PURE__ */ jsxs(Button, { onClick: onRetry, size: "sm", type: "button", variant: "outline", children: [
305
+ /* @__PURE__ */ jsx(RotateCcw, { "aria-hidden": "true", className: "mr-2 h-4 w-4" }),
306
+ labels.retry
307
+ ] })
308
+ ] });
309
+ }
310
+ function QuestionPane({
311
+ controller,
312
+ groupName,
313
+ inputId,
314
+ labels,
315
+ questions
316
+ }) {
317
+ const question = controller.current;
318
+ const response = controller.responses[question.id];
319
+ const answer = controller.answers[question.id];
320
+ const handleResponse = useCallback(
321
+ (value) => {
322
+ controller.setResponse(question.id, value);
323
+ },
324
+ [controller, question.id]
325
+ );
326
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
327
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: `${(controller.index + 1).toString()} ${labels.outOf} ${questions.length.toString()}` }),
328
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: question.question }),
329
+ /* @__PURE__ */ jsx(
330
+ QuestionField,
331
+ {
332
+ groupName,
333
+ inputId,
334
+ labels,
335
+ onResponse: handleResponse,
336
+ question,
337
+ response
338
+ }
339
+ ),
340
+ /* @__PURE__ */ jsx(
341
+ Feedback,
342
+ {
343
+ answer,
344
+ correctLabel: labels.correct,
345
+ explanation: question.explanation,
346
+ incorrectLabel: labels.incorrect
347
+ }
348
+ ),
349
+ /* @__PURE__ */ jsx(
350
+ PaneActions,
351
+ {
352
+ answered: answer !== void 0,
353
+ controller,
354
+ labels,
355
+ responseSet: response !== void 0
356
+ }
357
+ )
358
+ ] });
359
+ }
360
+ function PaneActions({
361
+ answered,
362
+ controller,
363
+ labels,
364
+ responseSet
365
+ }) {
366
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 pt-1", children: [
367
+ /* @__PURE__ */ jsx(
368
+ Button,
369
+ {
370
+ disabled: controller.isFirst,
371
+ onClick: controller.handlePrevious,
372
+ size: "sm",
373
+ type: "button",
374
+ variant: "ghost",
375
+ children: labels.back
376
+ }
377
+ ),
378
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: answered ? /* @__PURE__ */ jsx(Button, { onClick: controller.handleNext, size: "sm", type: "button", children: controller.isLast ? labels.scored : labels.next }) : /* @__PURE__ */ jsx(
379
+ Button,
380
+ {
381
+ disabled: !responseSet,
382
+ onClick: controller.handleSubmit,
383
+ size: "sm",
384
+ type: "button",
385
+ children: labels.check
386
+ }
387
+ ) })
388
+ ] });
389
+ }
390
+ const KnowledgeCheck = forwardRef(
391
+ (props, ref) => {
392
+ const {
393
+ className,
394
+ labels,
395
+ onAnswer,
396
+ onComplete,
397
+ questions,
398
+ title,
399
+ ...rest
400
+ } = props;
401
+ const resolvedLabels = useMemo(
402
+ () => ({ ...DEFAULT_LABELS, ...labels }),
403
+ [labels]
404
+ );
405
+ const groupName = useId();
406
+ const inputId = useId();
407
+ const controller = useKnowledgeCheckController({
408
+ onAnswer,
409
+ onComplete,
410
+ questions
411
+ });
412
+ return /* @__PURE__ */ jsxs(
413
+ "section",
414
+ {
415
+ className: cn(
416
+ "flex flex-col gap-4 rounded-2xl border bg-background p-4",
417
+ className
418
+ ),
419
+ ref,
420
+ ...rest,
421
+ children: [
422
+ title ? /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold tracking-tight text-foreground", children: title }) : null,
423
+ controller.isComplete && controller.score ? /* @__PURE__ */ jsx(
424
+ ScoreSummary,
425
+ {
426
+ labels: resolvedLabels,
427
+ onRetry: controller.handleReset,
428
+ score: controller.score
429
+ }
430
+ ) : /* @__PURE__ */ jsx(
431
+ QuestionPane,
432
+ {
433
+ controller,
434
+ groupName,
435
+ inputId,
436
+ labels: resolvedLabels,
437
+ questions
438
+ }
439
+ )
440
+ ]
441
+ }
442
+ );
443
+ }
444
+ );
445
+ KnowledgeCheck.displayName = "KnowledgeCheck";
446
+ export {
447
+ KnowledgeCheck
448
+ };
@@ -0,0 +1,4 @@
1
+ import { LeftRail } from "./left-rail";
2
+ export {
3
+ LeftRail
4
+ };
@@ -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 LeftRail = forwardRef(
5
+ ({ children, className, footer, title, ...props }, ref) => /* @__PURE__ */ jsxs(
6
+ "aside",
7
+ {
8
+ className: cn(
9
+ "flex h-full w-[4.5rem] shrink-0 flex-col items-center gap-3 border-r border-border bg-background px-2 py-3",
10
+ className
11
+ ),
12
+ ref,
13
+ ...props,
14
+ children: [
15
+ title ? /* @__PURE__ */ jsx("div", { className: "flex min-h-9 items-center text-[11px] font-medium uppercase tracking-[0.24em] text-muted-foreground", children: title }) : null,
16
+ /* @__PURE__ */ jsx("div", { className: "flex w-full flex-1 flex-col items-center gap-2", children }),
17
+ footer ? /* @__PURE__ */ jsx("div", { className: "flex w-full flex-col items-center gap-2", children: footer }) : null
18
+ ]
19
+ }
20
+ )
21
+ );
22
+ LeftRail.displayName = "LeftRail";
23
+ export {
24
+ LeftRail
25
+ };