@vllnt/ui 0.2.1-canary.0aaaad2 → 0.2.1-canary.25c4600

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 (56) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/components/activity-heatmap/activity-heatmap.js +2 -1
  3. package/dist/components/ai-artifact/ai-artifact.js +8 -8
  4. package/dist/components/ai-sidebar/ai-sidebar.js +4 -4
  5. package/dist/components/blog-card/blog-card.js +3 -1
  6. package/dist/components/bottom-activity-strip/bottom-activity-strip.js +2 -2
  7. package/dist/components/candlestick-chart/candlestick-chart.js +2 -1
  8. package/dist/components/checklist/checklist.js +4 -1
  9. package/dist/components/chronological-timeline/chronological-timeline.js +2 -2
  10. package/dist/components/code-playground/code-playground.js +2 -1
  11. package/dist/components/comment-pin/comment-pin.js +2 -2
  12. package/dist/components/comparison/comparison.js +2 -1
  13. package/dist/components/completion-dialog/completion-dialog.js +4 -1
  14. package/dist/components/content-intro/content-intro.js +5 -3
  15. package/dist/components/exercise/exercise.js +10 -2
  16. package/dist/components/faq/faq.js +6 -2
  17. package/dist/components/floating-toolbar/floating-toolbar.js +2 -2
  18. package/dist/components/horizontal-scroll-row/horizontal-scroll-row.js +2 -1
  19. package/dist/components/interactive-timeline/interactive-timeline.js +2 -2
  20. package/dist/components/jarvis-dock/jarvis-dock.js +2 -2
  21. package/dist/components/key-concept/key-concept.js +2 -1
  22. package/dist/components/keyboard-shortcuts-help/keyboard-shortcuts-help.js +2 -1
  23. package/dist/components/knowledge-check/knowledge-check.js +2 -2
  24. package/dist/components/learning-objectives/learning-objectives.js +2 -1
  25. package/dist/components/map-timeline/map-timeline.js +4 -4
  26. package/dist/components/market-treemap/market-treemap.js +2 -2
  27. package/dist/components/mdx-content/mdx-content.js +3 -3
  28. package/dist/components/navbar-saas/navbar-saas.js +1 -0
  29. package/dist/components/order-book/order-book.js +2 -2
  30. package/dist/components/policy-delivery-panel/policy-delivery-panel.js +2 -2
  31. package/dist/components/profile-section/profile-section.js +2 -1
  32. package/dist/components/prompt-templates/prompt-templates.js +2 -2
  33. package/dist/components/quiz/quiz.js +3 -2
  34. package/dist/components/relationship-inspector/relationship-inspector.js +2 -2
  35. package/dist/components/routing-assignment-panel/routing-assignment-panel.js +2 -2
  36. package/dist/components/run-timeline/run-timeline.js +2 -2
  37. package/dist/components/share-dialog/share-dialog.js +2 -2
  38. package/dist/components/share-section/share-section.js +2 -1
  39. package/dist/components/sidebar/sidebar.js +18 -17
  40. package/dist/components/slideshow/slideshow.js +2 -1
  41. package/dist/components/social-fab/social-fab.js +6 -6
  42. package/dist/components/status-board/status-board.js +2 -1
  43. package/dist/components/step-by-step/step-by-step.js +2 -1
  44. package/dist/components/table-of-contents/table-of-contents.js +5 -2
  45. package/dist/components/table-of-contents-panel/table-of-contents-panel.js +3 -1
  46. package/dist/components/timeline-scrubber/timeline-scrubber.js +2 -2
  47. package/dist/components/tutorial-complete/tutorial-complete.js +6 -4
  48. package/dist/components/tutorial-filters/tutorial-filters.js +1 -1
  49. package/dist/components/tutorial-intro-content/tutorial-intro-content.js +4 -4
  50. package/dist/components/tutorial-mdx/tutorial-mdx.js +4 -4
  51. package/dist/components/viewport-bookmarks/viewport-bookmarks.js +2 -2
  52. package/dist/components/watchlist/watchlist.js +2 -1
  53. package/dist/components/world-breadcrumbs/world-breadcrumbs.js +2 -2
  54. package/dist/components/world-clock-bar/world-clock-bar.js +2 -1
  55. package/dist/index.d.ts +95 -24
  56. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -29,6 +29,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
29
29
  - **Time navigation** — `TimelineScrubber`, `PlaybackGhost`, `BottomActivityStrip`, `RunTimeline`.
30
30
  - Total component count: **225** (up from 140).
31
31
 
32
+ - **A11y heading-level override** — every title-bearing component (`ProfileSection`, `FAQ`, `Slideshow`, `WorldClockBar`, `TableOfContentsPanel`, `TableOfContents`, `KeyboardShortcutsHelp`, `Watchlist`, `OrderBook`, `HorizontalScrollRow`, `MarketTreemap`, `ActivityHeatmap`, `Glossary`, `StatusBoard`, `CodePlayground`, `Comparison`, `Quiz`, `Exercise`, `ShareSection`, `CompletionDialog`, `Checklist`, `LearningObjectives`, `CandlestickChart`, `StepByStep`) accepts an `as` prop (`"h1"`–`"h6"`); multi-title components (`ContentIntro`, `TutorialComplete`) expose `titleAs` + `tocLabelAs` / `sectionLabelAs`. Defaults preserve existing heading tags. `HeadingTag` is re-exported from `@vllnt/ui`.
33
+
32
34
  ### Changed
33
35
 
34
36
  - Registry installs use a **hybrid CLI strategy** — leaf component source is inlined, sibling registry items resolve via `@vllnt/ui` to keep installs minimal and dedupe shared primitives.
@@ -145,6 +145,7 @@ function HeatmapGrid({
145
145
  }
146
146
  const ActivityHeatmap = React.forwardRef(
147
147
  ({
148
+ as: Heading = "h2",
148
149
  className,
149
150
  data,
150
151
  description,
@@ -157,7 +158,7 @@ const ActivityHeatmap = React.forwardRef(
157
158
  const gridData = getGridData(data, normalizedEndDate, weeks);
158
159
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), ref, ...props, children: [
159
160
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
160
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold tracking-tight", children: title }),
161
+ /* @__PURE__ */ jsx(Heading, { className: "text-lg font-semibold tracking-tight", children: title }),
161
162
  description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }) : null
162
163
  ] }),
163
164
  /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg border bg-card p-4 shadow-sm", children: /* @__PURE__ */ jsx(HeatmapGrid, { gridData, weeks }) })
@@ -259,7 +259,7 @@ const AIArtifactToolbar = forwardRef(({ className, ...rest }, ref) => /* @__PURE
259
259
  AIArtifactToolbar.displayName = "AIArtifactToolbar";
260
260
  const AIArtifactCopyButton = forwardRef(({ className, onClick, ...rest }, ref) => {
261
261
  const { copied, copy, labels } = useAIArtifact();
262
- const handleClick = useCallback(
262
+ const handleCopyArtifact = useCallback(
263
263
  (event) => {
264
264
  onClick?.(event);
265
265
  if (event.defaultPrevented) return;
@@ -272,7 +272,7 @@ const AIArtifactCopyButton = forwardRef(({ className, onClick, ...rest }, ref) =
272
272
  {
273
273
  "aria-label": copied ? labels.copied : labels.copy,
274
274
  className: cn("size-8", className),
275
- onClick: handleClick,
275
+ onClick: handleCopyArtifact,
276
276
  ref,
277
277
  size: "icon",
278
278
  type: "button",
@@ -285,7 +285,7 @@ const AIArtifactCopyButton = forwardRef(({ className, onClick, ...rest }, ref) =
285
285
  AIArtifactCopyButton.displayName = "AIArtifactCopyButton";
286
286
  const AIArtifactEditButton = forwardRef(({ className, onClick, ...rest }, ref) => {
287
287
  const { hasOnEdit, labels, onEdit } = useAIArtifact();
288
- const handleClick = useCallback(
288
+ const handleEditArtifact = useCallback(
289
289
  (event) => {
290
290
  onClick?.(event);
291
291
  if (event.defaultPrevented) return;
@@ -299,7 +299,7 @@ const AIArtifactEditButton = forwardRef(({ className, onClick, ...rest }, ref) =
299
299
  {
300
300
  "aria-label": labels.edit,
301
301
  className: cn("size-8", className),
302
- onClick: handleClick,
302
+ onClick: handleEditArtifact,
303
303
  ref,
304
304
  size: "icon",
305
305
  type: "button",
@@ -312,7 +312,7 @@ const AIArtifactEditButton = forwardRef(({ className, onClick, ...rest }, ref) =
312
312
  AIArtifactEditButton.displayName = "AIArtifactEditButton";
313
313
  const AIArtifactDownloadButton = forwardRef(({ className, onClick, ...rest }, ref) => {
314
314
  const { download, labels } = useAIArtifact();
315
- const handleClick = useCallback(
315
+ const handleDownloadArtifact = useCallback(
316
316
  (event) => {
317
317
  onClick?.(event);
318
318
  if (event.defaultPrevented) return;
@@ -325,7 +325,7 @@ const AIArtifactDownloadButton = forwardRef(({ className, onClick, ...rest }, re
325
325
  {
326
326
  "aria-label": labels.download,
327
327
  className: cn("size-8", className),
328
- onClick: handleClick,
328
+ onClick: handleDownloadArtifact,
329
329
  ref,
330
330
  size: "icon",
331
331
  type: "button",
@@ -338,7 +338,7 @@ const AIArtifactDownloadButton = forwardRef(({ className, onClick, ...rest }, re
338
338
  AIArtifactDownloadButton.displayName = "AIArtifactDownloadButton";
339
339
  const AIArtifactFullscreenButton = forwardRef(({ className, onClick, ...rest }, ref) => {
340
340
  const { fullscreen, labels, toggleFullscreen } = useAIArtifact();
341
- const handleClick = useCallback(
341
+ const handleToggleArtifactFullscreen = useCallback(
342
342
  (event) => {
343
343
  onClick?.(event);
344
344
  if (event.defaultPrevented) return;
@@ -352,7 +352,7 @@ const AIArtifactFullscreenButton = forwardRef(({ className, onClick, ...rest },
352
352
  "aria-label": fullscreen ? labels.exitFullscreen : labels.enterFullscreen,
353
353
  "aria-pressed": fullscreen,
354
354
  className: cn("size-8", className),
355
- onClick: handleClick,
355
+ onClick: handleToggleArtifactFullscreen,
356
356
  ref,
357
357
  size: "icon",
358
358
  type: "button",
@@ -169,7 +169,7 @@ const AISidebarTitle = forwardRef(({ children, className, ...rest }, ref) => {
169
169
  AISidebarTitle.displayName = "AISidebarTitle";
170
170
  const AISidebarClose = forwardRef(({ className, onClick, ...rest }, ref) => {
171
171
  const { close, labels } = useAISidebar();
172
- const handleClick = useCallback(
172
+ const handleCloseSidebar = useCallback(
173
173
  (event) => {
174
174
  onClick?.(event);
175
175
  if (event.defaultPrevented) return;
@@ -182,7 +182,7 @@ const AISidebarClose = forwardRef(({ className, onClick, ...rest }, ref) => {
182
182
  {
183
183
  "aria-label": labels.close,
184
184
  className: cn("size-8", className),
185
- onClick: handleClick,
185
+ onClick: handleCloseSidebar,
186
186
  ref,
187
187
  size: "icon",
188
188
  type: "button",
@@ -215,7 +215,7 @@ const AISidebarFooter = forwardRef(({ children, className, ...rest }, ref) => /*
215
215
  AISidebarFooter.displayName = "AISidebarFooter";
216
216
  const AISidebarTrigger = forwardRef(({ children, className, onClick, ...rest }, ref) => {
217
217
  const { labels, openState, toggle } = useAISidebar();
218
- const handleClick = useCallback(
218
+ const handleToggleSidebar = useCallback(
219
219
  (event) => {
220
220
  onClick?.(event);
221
221
  if (event.defaultPrevented) return;
@@ -230,7 +230,7 @@ const AISidebarTrigger = forwardRef(({ children, className, onClick, ...rest },
230
230
  "aria-label": children ? void 0 : labels.open,
231
231
  className: cn(className),
232
232
  "data-state": openState ? "open" : "closed",
233
- onClick: handleClick,
233
+ onClick: handleToggleSidebar,
234
234
  ref,
235
235
  size: children ? "sm" : "icon",
236
236
  type: "button",
@@ -43,7 +43,9 @@ function ContentCard({
43
43
  ] }) }) : null
44
44
  ] }) });
45
45
  }
46
- const BlogCard = ContentCard;
46
+ function BlogCard(props) {
47
+ return /* @__PURE__ */ jsx(ContentCard, { ...props });
48
+ }
47
49
  export {
48
50
  BlogCard,
49
51
  ContentCard
@@ -34,7 +34,7 @@ const Chip = (props) => {
34
34
  const { event } = props;
35
35
  const tone = event.tone ?? "neutral";
36
36
  if (event.onActivate) {
37
- const handleClick = () => {
37
+ const handleActivateEvent = () => {
38
38
  event.onActivate?.();
39
39
  };
40
40
  return /* @__PURE__ */ jsx(
@@ -43,7 +43,7 @@ const Chip = (props) => {
43
43
  className: "flex items-center rounded-full border border-border bg-background px-2 py-1 text-[11px] transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
44
44
  "data-strip-event": event.id,
45
45
  "data-strip-event-tone": tone,
46
- onClick: handleClick,
46
+ onClick: handleActivateEvent,
47
47
  type: "button",
48
48
  children: /* @__PURE__ */ jsx(ChipBody, { event })
49
49
  }
@@ -158,6 +158,7 @@ function SessionPill({ sessionChange }) {
158
158
  }
159
159
  const CandlestickChart = React.forwardRef(
160
160
  ({
161
+ as: Heading = "h3",
161
162
  className,
162
163
  data,
163
164
  height = DEFAULT_HEIGHT,
@@ -185,7 +186,7 @@ const CandlestickChart = React.forwardRef(
185
186
  /* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-wrap items-start justify-between gap-3", children: [
186
187
  /* @__PURE__ */ jsxs("div", { children: [
187
188
  /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-[0.28em] text-muted-foreground", children: "OHLC session" }),
188
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground", children: "Candlestick chart" })
189
+ /* @__PURE__ */ jsx(Heading, { className: "text-lg font-semibold text-foreground", children: "Candlestick chart" })
189
190
  ] }),
190
191
  /* @__PURE__ */ jsx(SessionPill, { sessionChange })
191
192
  ] }),
@@ -67,13 +67,14 @@ function ChecklistItemRow({
67
67
  }
68
68
  function ChecklistHeader({
69
69
  checked,
70
+ Heading,
70
71
  progress,
71
72
  title,
72
73
  total
73
74
  }) {
74
75
  if (!title) return null;
75
76
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
76
- /* @__PURE__ */ jsxs("h4", { className: "font-semibold flex items-center gap-2", children: [
77
+ /* @__PURE__ */ jsxs(Heading, { className: "font-semibold flex items-center gap-2", children: [
77
78
  /* @__PURE__ */ jsx(
78
79
  "svg",
79
80
  {
@@ -105,6 +106,7 @@ function ChecklistHeader({
105
106
  ] });
106
107
  }
107
108
  function Checklist({
109
+ as: Heading = "h4",
108
110
  className,
109
111
  items,
110
112
  onComplete,
@@ -153,6 +155,7 @@ function Checklist({
153
155
  ChecklistHeader,
154
156
  {
155
157
  checked: checked.size,
158
+ Heading,
156
159
  progress,
157
160
  title,
158
161
  total: items.length
@@ -168,7 +168,7 @@ const ChronoEvent = forwardRef(
168
168
  },
169
169
  [eventId, forwardedRef, registerEvent]
170
170
  );
171
- const handleFocus = useCallback(() => {
171
+ const handleFocusEvent = useCallback(() => {
172
172
  setActiveId(eventId);
173
173
  }, [eventId, setActiveId]);
174
174
  return /* @__PURE__ */ jsxs(
@@ -182,7 +182,7 @@ const ChronoEvent = forwardRef(
182
182
  "data-event-id": eventId,
183
183
  "data-featured": featured ? "true" : void 0,
184
184
  id: eventId,
185
- onFocus: handleFocus,
185
+ onFocus: handleFocusEvent,
186
186
  ref: refCallback,
187
187
  ...rest,
188
188
  children: [
@@ -13,6 +13,7 @@ function CodeLine({ highlightLines, line, lineNumber }) {
13
13
  }
14
14
  const EMPTY_HIGHLIGHT_LINES = [];
15
15
  function CodePlayground({
16
+ as: Heading = "h4",
16
17
  children,
17
18
  description,
18
19
  filename,
@@ -36,7 +37,7 @@ function CodePlayground({
36
37
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
37
38
  /* @__PURE__ */ jsx("div", { className: "flex size-8 items-center justify-center rounded bg-primary/10", children: /* @__PURE__ */ jsx(Code, { className: "size-4 text-primary" }) }),
38
39
  /* @__PURE__ */ jsxs("div", { children: [
39
- /* @__PURE__ */ jsx("h4", { className: "font-semibold text-sm", children: title }),
40
+ /* @__PURE__ */ jsx(Heading, { className: "font-semibold text-sm", children: title }),
40
41
  description ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: description }) : null
41
42
  ] })
42
43
  ] }),
@@ -57,7 +57,7 @@ const CommentPin = forwardRef(
57
57
  const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
58
58
  const showBadge = typeof unread === "number" && unread > 0;
59
59
  const ariaLabel = showBadge ? `${resolvedLabels.region}, ${unread} ${resolvedLabels.unreadSuffix}` : resolvedLabels.region;
60
- const handleClick = () => {
60
+ const handleActivateComment = () => {
61
61
  onActivate?.();
62
62
  };
63
63
  const body = /* @__PURE__ */ jsx(
@@ -89,7 +89,7 @@ const CommentPin = forwardRef(
89
89
  "aria-label": ariaLabel,
90
90
  className: "relative inline-flex rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
91
91
  "data-comment-pin-trigger": true,
92
- onClick: handleClick,
92
+ onClick: handleActivateComment,
93
93
  type: "button",
94
94
  children: body
95
95
  }
@@ -24,6 +24,7 @@ const variantConfig = {
24
24
  };
25
25
  function Comparison({
26
26
  after,
27
+ as: Heading = "h4",
27
28
  before,
28
29
  title,
29
30
  ...rest
@@ -40,7 +41,7 @@ function Comparison({
40
41
  const BeforeIcon = beforeConfig.icon;
41
42
  const AfterIcon = afterConfig.icon;
42
43
  return /* @__PURE__ */ jsxs("div", { className: "my-6", children: [
43
- title ? /* @__PURE__ */ jsx("h4", { className: "font-semibold mb-3", children: title }) : null,
44
+ title ? /* @__PURE__ */ jsx(Heading, { className: "font-semibold mb-3", children: title }) : null,
44
45
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [
45
46
  /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border", beforeConfig.className), children: [
46
47
  /* @__PURE__ */ jsx(
@@ -4,6 +4,7 @@ import { memo, useCallback, useEffect, useRef } from "react";
4
4
  import { cn } from "../../lib/utils";
5
5
  import { Button } from "../button";
6
6
  function DialogContent({
7
+ as: Heading = "h2",
7
8
  cancelLabel = "Skip",
8
9
  cancelShortcut = "S",
9
10
  className,
@@ -57,7 +58,7 @@ function DialogContent({
57
58
  }
58
59
  ),
59
60
  /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
60
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", id: "completion-dialog-title", children: title }),
61
+ /* @__PURE__ */ jsx(Heading, { className: "text-lg font-semibold", id: "completion-dialog-title", children: title }),
61
62
  description ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground mt-1.5", children: description }) : null
62
63
  ] }),
63
64
  /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-2", children: [
@@ -83,6 +84,7 @@ function DialogContent({
83
84
  );
84
85
  }
85
86
  function CompletionDialogImpl({
87
+ as,
86
88
  cancelLabel,
87
89
  cancelShortcut = "S",
88
90
  className,
@@ -149,6 +151,7 @@ function CompletionDialogImpl({
149
151
  /* @__PURE__ */ jsx(
150
152
  DialogContent,
151
153
  {
154
+ as,
152
155
  cancelLabel,
153
156
  cancelShortcut,
154
157
  className,
@@ -19,7 +19,9 @@ function ContentIntroImpl({
19
19
  onStart,
20
20
  renderIntroContent,
21
21
  sections,
22
- title
22
+ title,
23
+ titleAs: TitleHeading = "h2",
24
+ tocLabelAs: TocHeading = "h3"
23
25
  }) {
24
26
  const mergedLabels = { ...DEFAULT_LABELS, ...labels };
25
27
  const hasProgress = completedSections.size > 0;
@@ -41,11 +43,11 @@ function ContentIntroImpl({
41
43
  return /* @__PURE__ */ jsxs(Fragment, { children: [
42
44
  /* @__PURE__ */ jsxs("div", { className: "animate-in fade-in-0 duration-500 pb-24", children: [
43
45
  /* @__PURE__ */ jsxs("section", { className: "py-6", children: [
44
- /* @__PURE__ */ jsx("h2", { className: "text-2xl md:text-3xl font-bold mb-6", children: title }),
46
+ /* @__PURE__ */ jsx(TitleHeading, { className: "text-2xl md:text-3xl font-semibold mb-6", children: title }),
45
47
  /* @__PURE__ */ jsx("div", { className: cn("max-w-none", "[&_h2:first-of-type]:hidden"), children: renderIntroContent() })
46
48
  ] }),
47
49
  /* @__PURE__ */ jsxs("section", { className: "mt-8 py-6 border-t border-border", children: [
48
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-4", children: mergedLabels.tableOfContentsLabel }),
50
+ /* @__PURE__ */ jsx(TocHeading, { className: "text-lg font-semibold mb-4", children: mergedLabels.tableOfContentsLabel }),
49
51
  /* @__PURE__ */ jsx("ol", { className: "space-y-2", children: sections.map((section, index) => {
50
52
  const isCompleted = !isLoading && completedSections.has(section.id);
51
53
  return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
@@ -9,12 +9,18 @@ const difficultyConfig = {
9
9
  hard: { className: "text-red-600 dark:text-red-400", label: "Hard" },
10
10
  medium: { className: "text-amber-600 dark:text-amber-400", label: "Medium" }
11
11
  };
12
- function ExerciseHeader({ completed, config, onToggle, title }) {
12
+ function ExerciseHeader({
13
+ completed,
14
+ config,
15
+ Heading,
16
+ onToggle,
17
+ title
18
+ }) {
13
19
  return /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 mb-4", children: [
14
20
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
15
21
  /* @__PURE__ */ jsx("div", { className: "flex size-10 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(Dumbbell, { className: "size-5 text-primary" }) }),
16
22
  /* @__PURE__ */ jsxs("div", { children: [
17
- /* @__PURE__ */ jsx("h4", { className: "font-semibold text-foreground", children: title }),
23
+ /* @__PURE__ */ jsx(Heading, { className: "font-semibold text-foreground", children: title }),
18
24
  /* @__PURE__ */ jsx("span", { className: cn("text-xs font-medium", config.className), children: config.label })
19
25
  ] })
20
26
  ] }),
@@ -63,6 +69,7 @@ function ExerciseSolution({ onToggle, showSolution, solution }) {
63
69
  ] });
64
70
  }
65
71
  function Exercise({
72
+ as: Heading = "h4",
66
73
  children,
67
74
  difficulty = "medium",
68
75
  hint,
@@ -78,6 +85,7 @@ function Exercise({
78
85
  {
79
86
  completed,
80
87
  config: difficultyConfig[difficulty],
88
+ Heading,
81
89
  onToggle: () => {
82
90
  setCompleted(!completed);
83
91
  },
@@ -40,11 +40,15 @@ function FAQItem({ children, defaultOpen = false, question }) {
40
40
  )
41
41
  ] });
42
42
  }
43
- function FAQ({ children, title = "Frequently Asked Questions" }) {
43
+ function FAQ({
44
+ as: Heading = "h4",
45
+ children,
46
+ title = "Frequently Asked Questions"
47
+ }) {
44
48
  return /* @__PURE__ */ jsxs("div", { className: "my-6 rounded-lg border bg-card", children: [
45
49
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-4 border-b border-border", children: [
46
50
  /* @__PURE__ */ jsx(HelpCircle, { className: "size-5 text-primary" }),
47
- /* @__PURE__ */ jsx("h4", { className: "font-semibold", children: title })
51
+ /* @__PURE__ */ jsx(Heading, { className: "font-semibold", children: title })
48
52
  ] }),
49
53
  /* @__PURE__ */ jsx("div", { className: "px-4", children })
50
54
  ] });
@@ -31,7 +31,7 @@ const FloatingToolbar = forwardRef(
31
31
  ...rest,
32
32
  children: actions.map((action) => {
33
33
  const variant = action.variant ?? "ghost";
34
- const handleClick = () => {
34
+ const handleActivateToolbarAction = () => {
35
35
  action.onActivate();
36
36
  };
37
37
  return /* @__PURE__ */ jsxs(
@@ -46,7 +46,7 @@ const FloatingToolbar = forwardRef(
46
46
  "data-action-id": action.id,
47
47
  "data-variant": variant,
48
48
  disabled: action.disabled,
49
- onClick: handleClick,
49
+ onClick: handleActivateToolbarAction,
50
50
  type: "button",
51
51
  children: [
52
52
  action.glyph ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex size-3", children: action.glyph }) : null,
@@ -6,6 +6,7 @@ import { useHorizontalScroll } from "../../lib/use-horizontal-scroll";
6
6
  import { cn } from "../../lib/utils";
7
7
  import { Button } from "../button/button";
8
8
  const HorizontalScrollRow = memo(function HorizontalScrollRow2({
9
+ as: Heading = "h3",
9
10
  children,
10
11
  className,
11
12
  description,
@@ -16,7 +17,7 @@ const HorizontalScrollRow = memo(function HorizontalScrollRow2({
16
17
  return /* @__PURE__ */ jsxs("section", { className: cn("space-y-4", className), children: [
17
18
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
18
19
  /* @__PURE__ */ jsxs("div", { children: [
19
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: title }),
20
+ /* @__PURE__ */ jsx(Heading, { className: "text-lg font-semibold", children: title }),
20
21
  description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }) : null
21
22
  ] }),
22
23
  showControls ? /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
@@ -219,7 +219,7 @@ function EventNode({
219
219
  if (!visible) return null;
220
220
  const titleText = typeof event.title === "string" ? event.title : "Event";
221
221
  const tooltipId = `${event.id}-tooltip`;
222
- const handleClick = (mouseEvent) => {
222
+ const handleSelectTimelineEvent = (mouseEvent) => {
223
223
  mouseEvent.stopPropagation();
224
224
  onSelect(event);
225
225
  };
@@ -236,7 +236,7 @@ function EventNode({
236
236
  "data-event-id": event.id,
237
237
  "data-event-track": event.track ?? "",
238
238
  "data-selected": active ? "true" : void 0,
239
- onClick: handleClick,
239
+ onClick: handleSelectTimelineEvent,
240
240
  style: {
241
241
  left: `${(left * 100).toString()}%`,
242
242
  width: isDuration ? `${(width * 100).toString()}%` : void 0
@@ -17,7 +17,7 @@ const DEFAULT_LABELS = {
17
17
  const ActionButton = (props) => {
18
18
  const { action } = props;
19
19
  const tone = action.tone ?? "neutral";
20
- const handleClick = () => {
20
+ const handleActivateAction = () => {
21
21
  action.onActivate();
22
22
  };
23
23
  return /* @__PURE__ */ jsxs(
@@ -26,7 +26,7 @@ const ActionButton = (props) => {
26
26
  className: "group relative flex size-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
27
  "data-jarvis-action": action.id,
28
28
  "data-jarvis-tone": tone,
29
- onClick: handleClick,
29
+ onClick: handleActivateAction,
30
30
  type: "button",
31
31
  children: [
32
32
  /* @__PURE__ */ jsx(
@@ -43,13 +43,14 @@ function KeyConcept({
43
43
  );
44
44
  }
45
45
  function Glossary({
46
+ as: Heading = "h4",
46
47
  children,
47
48
  className,
48
49
  icon,
49
50
  title = "Key Terms"
50
51
  }) {
51
52
  return /* @__PURE__ */ jsxs("div", { className: cn("my-6", className), children: [
52
- /* @__PURE__ */ jsxs("h4", { className: "font-semibold mb-3 flex items-center gap-2", children: [
53
+ /* @__PURE__ */ jsxs(Heading, { className: "font-semibold mb-3 flex items-center gap-2", children: [
53
54
  icon ? /* @__PURE__ */ jsx("span", { className: "size-4", children: icon }) : /* @__PURE__ */ jsx(
54
55
  "svg",
55
56
  {
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { memo, useEffect, useRef } from "react";
4
4
  import { cn } from "../../lib/utils";
5
5
  function KeyboardShortcutsHelpImpl({
6
+ as: Heading = "h2",
6
7
  className,
7
8
  closeIcon,
8
9
  footer,
@@ -58,7 +59,7 @@ function KeyboardShortcutsHelpImpl({
58
59
  ),
59
60
  children: [
60
61
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
61
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", id: "shortcuts-title", children: title }),
62
+ /* @__PURE__ */ jsx(Heading, { className: "text-lg font-semibold", id: "shortcuts-title", children: title }),
62
63
  /* @__PURE__ */ jsx(
63
64
  "button",
64
65
  {
@@ -238,13 +238,13 @@ function FillBlankField({
238
238
  onChange,
239
239
  value
240
240
  }) {
241
- const handleChange = useCallback(
241
+ const handleBlankValueChange = useCallback(
242
242
  (event) => {
243
243
  onChange(event.target.value);
244
244
  },
245
245
  [onChange]
246
246
  );
247
- return /* @__PURE__ */ jsx(Input, { id: inputId, onChange: handleChange, value });
247
+ return /* @__PURE__ */ jsx(Input, { id: inputId, onChange: handleBlankValueChange, value });
248
248
  }
249
249
  function QuestionField({
250
250
  groupName,
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { CheckCircle2, Clock, GraduationCap, Target } from "lucide-react";
4
4
  function LearningObjectives({
5
+ as: Heading = "h4",
5
6
  estimatedTime,
6
7
  objectives,
7
8
  title = "What you'll learn"
@@ -10,7 +11,7 @@ function LearningObjectives({
10
11
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
11
12
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
12
13
  /* @__PURE__ */ jsx(Target, { className: "size-5 text-primary" }),
13
- /* @__PURE__ */ jsx("h4", { className: "font-semibold text-foreground", children: title })
14
+ /* @__PURE__ */ jsx(Heading, { className: "font-semibold text-foreground", children: title })
14
15
  ] }),
15
16
  estimatedTime ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
16
17
  /* @__PURE__ */ jsx(Clock, { className: "size-4" }),
@@ -214,7 +214,7 @@ MapTimelineControls.displayName = "MapTimelineControls";
214
214
  const MapTimelineSlider = forwardRef(({ className, ...rest }, ref) => {
215
215
  const { endYear, labels, setYear, startYear, year } = useTimelineContext();
216
216
  const sliderId = useId();
217
- const handleChange = (event) => {
217
+ const handleYearChange = (event) => {
218
218
  setYear(Number.parseInt(event.target.value, 10));
219
219
  };
220
220
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center gap-2 text-xs font-medium text-muted-foreground", children: [
@@ -230,7 +230,7 @@ const MapTimelineSlider = forwardRef(({ className, ...rest }, ref) => {
230
230
  id: sliderId,
231
231
  max: endYear,
232
232
  min: startYear,
233
- onChange: handleChange,
233
+ onChange: handleYearChange,
234
234
  ref,
235
235
  type: "range",
236
236
  value: year,
@@ -251,7 +251,7 @@ const MapTimelineSlider = forwardRef(({ className, ...rest }, ref) => {
251
251
  MapTimelineSlider.displayName = "MapTimelineSlider";
252
252
  const MapTimelinePlayButton = forwardRef(({ className, ...rest }, ref) => {
253
253
  const { isPlaying, labels, setIsPlaying } = useTimelineContext();
254
- const handleClick = () => {
254
+ const handleTogglePlayback = () => {
255
255
  setIsPlaying(!isPlaying);
256
256
  };
257
257
  return /* @__PURE__ */ jsx(
@@ -264,7 +264,7 @@ const MapTimelinePlayButton = forwardRef(({ className, ...rest }, ref) => {
264
264
  className
265
265
  ),
266
266
  "data-playing": isPlaying ? "true" : void 0,
267
- onClick: handleClick,
267
+ onClick: handleTogglePlayback,
268
268
  ref,
269
269
  type: "button",
270
270
  ...rest,
@@ -67,7 +67,7 @@ function MarketTreemapTile({
67
67
  }
68
68
  );
69
69
  }
70
- const MarketTreemap = React.forwardRef(({ className, items, ...props }, reference) => {
70
+ const MarketTreemap = React.forwardRef(({ as: Heading = "h2", className, items, ...props }, reference) => {
71
71
  if (items.length === 0) {
72
72
  return null;
73
73
  }
@@ -85,7 +85,7 @@ const MarketTreemap = React.forwardRef(({ className, items, ...props }, referenc
85
85
  /* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-3", children: [
86
86
  /* @__PURE__ */ jsxs("div", { children: [
87
87
  /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-[0.28em] text-muted-foreground", children: "Sector heatmap" }),
88
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Market treemap" })
88
+ /* @__PURE__ */ jsx(Heading, { className: "text-lg font-semibold text-foreground", children: "Market treemap" })
89
89
  ] }),
90
90
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Tile size maps market cap proxy; color reflects session change." })
91
91
  ] }),
@@ -37,9 +37,9 @@ const MDXComponents = {
37
37
  );
38
38
  },
39
39
  em: ({ children, ...props }) => /* @__PURE__ */ jsx("em", { className: "italic", ...props, children }),
40
- h1: ({ children, ...props }) => /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold mt-8 mb-4", ...props, children }),
41
- h2: ({ children, ...props }) => /* @__PURE__ */ jsx("h2", { className: "text-xl font-bold mt-6 mb-3", ...props, children }),
42
- h3: ({ children, ...props }) => /* @__PURE__ */ jsx("h3", { className: "text-lg font-bold mt-4 mb-2", ...props, children }),
40
+ h1: ({ children, ...props }) => /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold mt-8 mb-4", ...props, children }),
41
+ h2: ({ children, ...props }) => /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold mt-6 mb-3", ...props, children }),
42
+ h3: ({ children, ...props }) => /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mt-4 mb-2", ...props, children }),
43
43
  hr: ({ ...props }) => /* @__PURE__ */ jsx("hr", { className: "my-8 border-border", ...props }),
44
44
  li: ({ children, ...props }) => /* @__PURE__ */ jsx(
45
45
  "li",
@@ -24,6 +24,7 @@ function NavbarSaas({
24
24
  Button,
25
25
  {
26
26
  className: "lg:hidden",
27
+ "data-testid": "navbar-saas-mobile-trigger",
27
28
  onClick: () => {
28
29
  setOpen(!open);
29
30
  },