@vllnt/ui 0.2.1-canary.06f0e84 → 0.2.1-canary.55e0e0a

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 (33) hide show
  1. package/dist/components/activity-heatmap/activity-heatmap.js +14 -12
  2. package/dist/components/animated-text/animated-text.js +1 -4
  3. package/dist/components/auto-reload/auto-reload.js +14 -4
  4. package/dist/components/avatar-group/avatar-group.js +1 -1
  5. package/dist/components/breadcrumb/breadcrumb.js +33 -26
  6. package/dist/components/chronological-timeline/chronological-timeline.js +2 -1
  7. package/dist/components/code-playground/code-playground.js +3 -2
  8. package/dist/components/comparison/comparison.js +4 -4
  9. package/dist/components/content-intro/content-intro.js +3 -2
  10. package/dist/components/countdown-timer/countdown-timer.js +8 -7
  11. package/dist/components/data-table/data-table.js +2 -1
  12. package/dist/components/filter-bar/filter-bar.js +2 -1
  13. package/dist/components/gantt-chart/gantt-chart.js +14 -12
  14. package/dist/components/learning-objectives/learning-objectives.js +6 -6
  15. package/dist/components/live-feed/live-feed.js +12 -10
  16. package/dist/components/mdx-content/mdx-content.js +2 -1
  17. package/dist/components/navbar-saas/navbar-saas.js +2 -1
  18. package/dist/components/number-ticker/number-ticker.js +11 -4
  19. package/dist/components/progress-card/progress-card.js +4 -3
  20. package/dist/components/progress-tracker/progress-tracker.js +4 -2
  21. package/dist/components/quiz/quiz.js +1 -1
  22. package/dist/components/search-bar/search-bar.js +24 -2
  23. package/dist/components/slideshow/slideshow.js +3 -2
  24. package/dist/components/social-fab/social-fab.js +2 -1
  25. package/dist/components/step-by-step/step-by-step.js +3 -2
  26. package/dist/components/table-of-contents-panel/table-of-contents-panel.js +1 -1
  27. package/dist/components/terminal/terminal.js +36 -22
  28. package/dist/components/transaction-list/transaction-list.js +30 -9
  29. package/dist/components/tutorial-complete/tutorial-complete.js +1 -1
  30. package/dist/components/usage-breakdown/usage-breakdown.js +7 -3
  31. package/dist/components/world-clock-bar/world-clock-bar.js +32 -12
  32. package/dist/index.d.ts +1 -1
  33. package/package.json +1 -1
@@ -72,19 +72,21 @@ function getGridData(data, endDate, weeks) {
72
72
  });
73
73
  });
74
74
  }
75
+ const MONTH_LABEL_FORMATTER = new Intl.DateTimeFormat("en-US", {
76
+ month: "short",
77
+ timeZone: "UTC"
78
+ });
79
+ const TOOLTIP_DATE_FORMATTER = new Intl.DateTimeFormat("en-US", {
80
+ day: "numeric",
81
+ month: "short",
82
+ timeZone: "UTC",
83
+ year: "numeric"
84
+ });
75
85
  function formatMonthLabel(date) {
76
- return new Intl.DateTimeFormat("en-US", {
77
- month: "short",
78
- timeZone: "UTC"
79
- }).format(date);
86
+ return MONTH_LABEL_FORMATTER.format(date);
80
87
  }
81
88
  function formatTooltip(date, count) {
82
- const formattedDate = new Intl.DateTimeFormat("en-US", {
83
- day: "numeric",
84
- month: "short",
85
- timeZone: "UTC",
86
- year: "numeric"
87
- }).format(date);
89
+ const formattedDate = TOOLTIP_DATE_FORMATTER.format(date);
88
90
  return `${count} activity ${count === 1 ? "event" : "events"} on ${formattedDate}`;
89
91
  }
90
92
  function HeatmapGrid({
@@ -127,7 +129,7 @@ function HeatmapGrid({
127
129
  ),
128
130
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 text-xs text-muted-foreground", children: [
129
131
  /* @__PURE__ */ jsx("span", { children: "Less" }),
130
- LEVEL_CLASS_NAMES.map((className, index) => /* @__PURE__ */ jsx(
132
+ LEVEL_CLASS_NAMES.map((className) => /* @__PURE__ */ jsx(
131
133
  "span",
132
134
  {
133
135
  className: cn(
@@ -135,7 +137,7 @@ function HeatmapGrid({
135
137
  className
136
138
  )
137
139
  },
138
- `legend-${index}`
140
+ `legend-${className}`
139
141
  )),
140
142
  /* @__PURE__ */ jsx("span", { children: "More" })
141
143
  ] })
@@ -24,10 +24,7 @@ const ANIMATED_TEXT_RANDOM_CHARACTER_PRESETS = {
24
24
  const DEFAULT_RANDOM_CHARACTERS = ANIMATED_TEXT_RANDOM_CHARACTER_PRESETS.matrix;
25
25
  function getSegments(text, splitBy) {
26
26
  if (splitBy === "character") {
27
- const segmenter = new Intl.Segmenter(void 0, {
28
- granularity: "grapheme"
29
- });
30
- return Array.from(segmenter.segment(text), ({ segment }) => segment);
27
+ return Array.from(GLYPH_SEGMENTER.segment(text), ({ segment }) => segment);
31
28
  }
32
29
  return text.match(/\S+\s*/g) ?? [];
33
30
  }
@@ -34,11 +34,21 @@ function valueToCents(value) {
34
34
  if (Number.isNaN(parsed)) return 0;
35
35
  return Math.round(parsed * CENTS_PER_UNIT);
36
36
  }
37
+ const CURRENCY_FORMATTER_CACHE = /* @__PURE__ */ new Map();
38
+ function getCurrencyFormatter(locale, currency) {
39
+ const key = `${locale}|${currency}`;
40
+ let formatter = CURRENCY_FORMATTER_CACHE.get(key);
41
+ if (!formatter) {
42
+ formatter = new Intl.NumberFormat(locale, {
43
+ currency,
44
+ style: "currency"
45
+ });
46
+ CURRENCY_FORMATTER_CACHE.set(key, formatter);
47
+ }
48
+ return formatter;
49
+ }
37
50
  function getCurrencySymbol(locale, currency) {
38
- const formatted = new Intl.NumberFormat(locale, {
39
- currency,
40
- style: "currency"
41
- }).format(0);
51
+ const formatted = getCurrencyFormatter(locale, currency).format(0);
42
52
  const symbol = formatted.replaceAll(/[\d\s,.]/g, "");
43
53
  return symbol.length > 0 ? symbol : currency;
44
54
  }
@@ -66,7 +66,7 @@ const AvatarGroup = React.forwardRef(
66
66
  /* @__PURE__ */ jsx(AvatarFallback, { children: item.fallback })
67
67
  ]
68
68
  },
69
- `${item.alt}-${index}`
69
+ item.src ?? item.alt
70
70
  )),
71
71
  hiddenCount > 0 ? /* @__PURE__ */ jsx("span", { className: overflowBadgeVariants({ size }), children: overflowLabel ? overflowLabel(hiddenCount) : `+${hiddenCount}` }) : null
72
72
  ]
@@ -20,39 +20,46 @@ function Breadcrumb({
20
20
  {
21
21
  "aria-label": "Breadcrumb",
22
22
  className: cn("flex items-center space-x-1 text-sm", className),
23
- children: items.map((item, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
24
- index > 0 && /* @__PURE__ */ jsx("span", { className: "mx-2", children: /* @__PURE__ */ jsx(SeparatorIcon, { type: separator }) }),
25
- item.href ? /* @__PURE__ */ jsxs(
26
- Link,
27
- {
28
- className: "flex items-center gap-1 hover:text-foreground transition-colors",
29
- href: item.href,
30
- children: [
23
+ children: items.map((item, index) => /* @__PURE__ */ jsxs(
24
+ "div",
25
+ {
26
+ className: "flex items-center",
27
+ children: [
28
+ index > 0 && /* @__PURE__ */ jsx("span", { className: "mx-2", children: /* @__PURE__ */ jsx(SeparatorIcon, { type: separator }) }),
29
+ item.href ? /* @__PURE__ */ jsxs(
30
+ Link,
31
+ {
32
+ className: "flex items-center gap-1 hover:text-foreground transition-colors",
33
+ href: item.href,
34
+ children: [
35
+ item.icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: item.icon }) : null,
36
+ /* @__PURE__ */ jsx(
37
+ "span",
38
+ {
39
+ className: cn(
40
+ // Truncate plain string labels; custom React elements handle their own overflow
41
+ typeof item.label === "string" && "truncate",
42
+ variant === "minimal" ? "text-muted-foreground" : "text-foreground"
43
+ ),
44
+ children: item.label
45
+ }
46
+ )
47
+ ]
48
+ }
49
+ ) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-muted-foreground", children: [
31
50
  item.icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: item.icon }) : null,
32
51
  /* @__PURE__ */ jsx(
33
52
  "span",
34
53
  {
35
- className: cn(
36
- // Truncate plain string labels; custom React elements handle their own overflow
37
- typeof item.label === "string" && "truncate",
38
- variant === "minimal" ? "text-muted-foreground" : "text-foreground"
39
- ),
54
+ className: cn(typeof item.label === "string" && "truncate"),
40
55
  children: item.label
41
56
  }
42
57
  )
43
- ]
44
- }
45
- ) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-muted-foreground", children: [
46
- item.icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: item.icon }) : null,
47
- /* @__PURE__ */ jsx(
48
- "span",
49
- {
50
- className: cn(typeof item.label === "string" && "truncate"),
51
- children: item.label
52
- }
53
- )
54
- ] })
55
- ] }, index))
58
+ ] })
59
+ ]
60
+ },
61
+ typeof item.label === "string" ? item.label : `breadcrumb-${index}`
62
+ ))
56
63
  }
57
64
  );
58
65
  }
@@ -38,6 +38,7 @@ function ImageMedia({ media }) {
38
38
  ] });
39
39
  }
40
40
  function VideoMedia({ media }) {
41
+ const iframeTitle = media.title || "Embedded timeline video";
41
42
  return /* @__PURE__ */ jsxs("figure", { className: "overflow-hidden rounded-xl border bg-muted", children: [
42
43
  /* @__PURE__ */ jsx("div", { className: "aspect-video w-full", children: /* @__PURE__ */ jsx(
43
44
  "iframe",
@@ -46,7 +47,7 @@ function VideoMedia({ media }) {
46
47
  allowFullScreen: true,
47
48
  className: "h-full w-full",
48
49
  src: media.src,
49
- title: media.title ?? "Embedded video"
50
+ title: iframeTitle
50
51
  }
51
52
  ) }),
52
53
  media.caption || media.credit ? /* @__PURE__ */ jsxs("figcaption", { className: "border-t bg-background px-3 py-2 text-xs text-muted-foreground", children: [
@@ -11,11 +11,12 @@ function CodeLine({ highlightLines, line, lineNumber }) {
11
11
  /* @__PURE__ */ jsx("span", { children: line })
12
12
  ] });
13
13
  }
14
+ const EMPTY_HIGHLIGHT_LINES = [];
14
15
  function CodePlayground({
15
16
  children,
16
17
  description,
17
18
  filename,
18
- highlightLines = [],
19
+ highlightLines = EMPTY_HIGHLIGHT_LINES,
19
20
  language = "typescript",
20
21
  showLineNumbers = false,
21
22
  title
@@ -63,7 +64,7 @@ function CodePlayground({
63
64
  line,
64
65
  lineNumber: index + 1
65
66
  },
66
- index
67
+ `${line}-${index + 1}`
67
68
  )) }) : /* @__PURE__ */ jsx("code", { children: code }) }) }),
68
69
  /* @__PURE__ */ jsx("div", { className: "px-4 py-2 border-t bg-muted/30", children: /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-muted-foreground uppercase tracking-wider", children: language }) })
69
70
  ] });
@@ -53,7 +53,7 @@ function Comparison({
53
53
  children: before.title
54
54
  }
55
55
  ),
56
- /* @__PURE__ */ jsx("ul", { className: "p-4 space-y-2", children: before.items.map((item, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-sm", children: [
56
+ /* @__PURE__ */ jsx("ul", { className: "p-4 space-y-2", children: before.items.map((item) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-sm", children: [
57
57
  /* @__PURE__ */ jsx(
58
58
  BeforeIcon,
59
59
  {
@@ -64,7 +64,7 @@ function Comparison({
64
64
  }
65
65
  ),
66
66
  /* @__PURE__ */ jsx("span", { children: item })
67
- ] }, index)) })
67
+ ] }, item)) })
68
68
  ] }),
69
69
  /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border", afterConfig.className), children: [
70
70
  /* @__PURE__ */ jsx(
@@ -77,7 +77,7 @@ function Comparison({
77
77
  children: after.title
78
78
  }
79
79
  ),
80
- /* @__PURE__ */ jsx("ul", { className: "p-4 space-y-2", children: after.items.map((item, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-sm", children: [
80
+ /* @__PURE__ */ jsx("ul", { className: "p-4 space-y-2", children: after.items.map((item) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-sm", children: [
81
81
  /* @__PURE__ */ jsx(
82
82
  AfterIcon,
83
83
  {
@@ -88,7 +88,7 @@ function Comparison({
88
88
  }
89
89
  ),
90
90
  /* @__PURE__ */ jsx("span", { children: item })
91
- ] }, index)) })
91
+ ] }, item)) })
92
92
  ] })
93
93
  ] })
94
94
  ] });
@@ -8,12 +8,13 @@ const DEFAULT_LABELS = {
8
8
  startLabel: "Start Tutorial",
9
9
  tableOfContentsLabel: "Table of Contents"
10
10
  };
11
+ const EMPTY_CONTENT_INTRO_LABELS = {};
11
12
  function ContentIntroImpl({
12
13
  additionalContent,
13
14
  completedSections,
14
15
  estimatedTime,
15
16
  isLoading = false,
16
- labels = {},
17
+ labels = EMPTY_CONTENT_INTRO_LABELS,
17
18
  onGoToSection,
18
19
  onStart,
19
20
  renderIntroContent,
@@ -97,7 +98,7 @@ function ContentIntroImpl({
97
98
  )
98
99
  ]
99
100
  }
100
- ) }, `${section.id}-${index}`);
101
+ ) }, section.id);
101
102
  }) })
102
103
  ] }),
103
104
  additionalContent
@@ -73,14 +73,15 @@ function formatSegments(milliseconds) {
73
73
  { label: "Seconds", value: String(seconds).padStart(2, "0") }
74
74
  ];
75
75
  }
76
+ const DEADLINE_FORMATTER = new Intl.DateTimeFormat("en-US", {
77
+ day: "numeric",
78
+ hour: "numeric",
79
+ minute: "2-digit",
80
+ month: "short",
81
+ timeZoneName: "short"
82
+ });
76
83
  function formatDeadline(date) {
77
- return new Intl.DateTimeFormat("en-US", {
78
- day: "numeric",
79
- hour: "numeric",
80
- minute: "2-digit",
81
- month: "short",
82
- timeZoneName: "short"
83
- }).format(date);
84
+ return DEADLINE_FORMATTER.format(date);
84
85
  }
85
86
  function getProgress(deadline, now, startedAt) {
86
87
  if (!startedAt) {
@@ -38,6 +38,7 @@ function SortIcon({ direction }) {
38
38
  }
39
39
  return /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-4 w-4" });
40
40
  }
41
+ const EMPTY_FILTERABLE_COLUMNS = [];
41
42
  function DataTableComponent({
42
43
  caption,
43
44
  className,
@@ -47,7 +48,7 @@ function DataTableComponent({
47
48
  enableFiltering = true,
48
49
  enablePagination = true,
49
50
  enableSelection = false,
50
- filterableColumns = [],
51
+ filterableColumns = EMPTY_FILTERABLE_COLUMNS,
51
52
  getRowId,
52
53
  pageSize = 10,
53
54
  searchPlaceholder = "Search rows...",
@@ -149,12 +149,13 @@ const DEFAULT_LABELS = {
149
149
  searchPlaceholder: "Search...",
150
150
  tagsLabel: "Tags:"
151
151
  };
152
+ const EMPTY_FILTER_BAR_LABELS = {};
152
153
  function FilterBarImpl({
153
154
  className,
154
155
  currentDifficulty,
155
156
  currentTags,
156
157
  difficultyOptions,
157
- labels = {},
158
+ labels = EMPTY_FILTER_BAR_LABELS,
158
159
  onFiltersChange,
159
160
  searchQuery,
160
161
  tags
@@ -50,23 +50,25 @@ function clamp(value, min, max) {
50
50
  function diffInDays(later, earlier) {
51
51
  return (later.getTime() - earlier.getTime()) / MS_PER_DAY;
52
52
  }
53
+ const TICK_FORMATTER_CACHE = /* @__PURE__ */ new Map();
54
+ function getTickDateTimeFormatter(locale, scale) {
55
+ const key = `${locale}|${scale}`;
56
+ let formatter = TICK_FORMATTER_CACHE.get(key);
57
+ if (!formatter) {
58
+ const options = scale === "month" ? { month: "short", year: "numeric" } : { day: "2-digit", month: "short" };
59
+ formatter = new Intl.DateTimeFormat(locale, options);
60
+ TICK_FORMATTER_CACHE.set(key, formatter);
61
+ }
62
+ return formatter;
63
+ }
53
64
  function buildTickFormatter(scale, locale) {
54
65
  switch (scale) {
55
66
  case "day":
56
- return new Intl.DateTimeFormat(locale, {
57
- day: "2-digit",
58
- month: "short"
59
- }).format;
67
+ return getTickDateTimeFormatter(locale, "day").format;
60
68
  case "week":
61
- return new Intl.DateTimeFormat(locale, {
62
- day: "2-digit",
63
- month: "short"
64
- }).format;
69
+ return getTickDateTimeFormatter(locale, "week").format;
65
70
  case "month":
66
- return new Intl.DateTimeFormat(locale, {
67
- month: "short",
68
- year: "numeric"
69
- }).format;
71
+ return getTickDateTimeFormatter(locale, "month").format;
70
72
  case "quarter":
71
73
  return (date) => {
72
74
  const quarter = Math.floor(date.getMonth() / 3) + 1;
@@ -17,10 +17,10 @@ function LearningObjectives({
17
17
  /* @__PURE__ */ jsx("span", { children: estimatedTime })
18
18
  ] }) : null
19
19
  ] }),
20
- /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: objectives.map((objective, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
20
+ /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: objectives.map((objective) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
21
21
  /* @__PURE__ */ jsx(CheckCircle2, { className: "h-4 w-4 text-primary flex-shrink-0 mt-0.5" }),
22
22
  /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: objective })
23
- ] }, index)) })
23
+ ] }, objective)) })
24
24
  ] });
25
25
  }
26
26
  function Prerequisites({
@@ -36,7 +36,7 @@ function Prerequisites({
36
36
  ] }),
37
37
  level ? /* @__PURE__ */ jsx("span", { className: "text-xs font-medium px-2 py-1 rounded-full bg-primary/10 text-primary capitalize", children: level }) : null
38
38
  ] }),
39
- /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: items.map((item, index) => /* @__PURE__ */ jsxs(
39
+ /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: items.map((item) => /* @__PURE__ */ jsxs(
40
40
  "li",
41
41
  {
42
42
  className: "flex items-start gap-2 text-sm text-muted-foreground",
@@ -45,7 +45,7 @@ function Prerequisites({
45
45
  item
46
46
  ]
47
47
  },
48
- index
48
+ item
49
49
  )) })
50
50
  ] });
51
51
  }
@@ -62,10 +62,10 @@ function Summary({
62
62
  /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground [&>p]:mb-2", children }),
63
63
  keyTakeaways && keyTakeaways.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-4 pt-4 border-t border-border", children: [
64
64
  /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground mb-2", children: "Key Takeaways" }),
65
- /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: keyTakeaways.map((takeaway, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-sm", children: [
65
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: keyTakeaways.map((takeaway) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-sm", children: [
66
66
  /* @__PURE__ */ jsx(CheckCircle2, { className: "h-4 w-4 text-green-500 flex-shrink-0 mt-0.5" }),
67
67
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: takeaway })
68
- ] }, index)) })
68
+ ] }, takeaway)) })
69
69
  ] }) : null
70
70
  ] });
71
71
  }
@@ -60,18 +60,20 @@ function formatRelative(eventDate, now) {
60
60
  if (deltaMs < 7 * DAY_MS) {
61
61
  return `${Math.floor(deltaMs / DAY_MS)}d ago`;
62
62
  }
63
- return new Intl.DateTimeFormat("en-US", {
64
- day: "numeric",
65
- month: "short"
66
- }).format(eventDate);
63
+ return SHORT_DATE_FORMATTER.format(eventDate);
67
64
  }
65
+ const SHORT_DATE_FORMATTER = new Intl.DateTimeFormat("en-US", {
66
+ day: "numeric",
67
+ month: "short"
68
+ });
69
+ const ABSOLUTE_FORMATTER = new Intl.DateTimeFormat("en-US", {
70
+ hour: "numeric",
71
+ minute: "2-digit",
72
+ month: "short",
73
+ second: "2-digit"
74
+ });
68
75
  function formatAbsolute(eventDate) {
69
- return new Intl.DateTimeFormat("en-US", {
70
- hour: "numeric",
71
- minute: "2-digit",
72
- month: "short",
73
- second: "2-digit"
74
- }).format(eventDate);
76
+ return ABSOLUTE_FORMATTER.format(eventDate);
75
77
  }
76
78
  function sortEventsDesc(events) {
77
79
  return [...events].sort(
@@ -114,8 +114,9 @@ function buildCustomComponents(injectedComponents) {
114
114
  ...injectedComponents
115
115
  };
116
116
  }
117
+ const EMPTY_MDX_COMPONENTS = {};
117
118
  async function MDXContent({
118
- components = {},
119
+ components = EMPTY_MDX_COMPONENTS,
119
120
  content,
120
121
  enableMDX = true
121
122
  }) {
@@ -8,9 +8,10 @@ import { Button } from "../button";
8
8
  import { useSidebar } from "../sidebar-provider";
9
9
  import { ThemeToggle } from "../theme-toggle";
10
10
  import { useMobile } from "./use-mobile";
11
+ const EMPTY_NAV_ITEMS = [];
11
12
  function NavbarSaas({
12
13
  brand,
13
- navItems = [],
14
+ navItems = EMPTY_NAV_ITEMS,
14
15
  rightSlot,
15
16
  showMobileMenu = true
16
17
  }) {
@@ -2,6 +2,16 @@
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "../../lib/utils";
5
+ const NUMBER_FORMATTER_CACHE = /* @__PURE__ */ new Map();
6
+ function getNumberTickerFormatter(locale, formatOptions) {
7
+ const key = `${locale ?? ""}|${formatOptions ? JSON.stringify(formatOptions) : ""}`;
8
+ let formatter = NUMBER_FORMATTER_CACHE.get(key);
9
+ if (!formatter) {
10
+ formatter = new Intl.NumberFormat(locale, formatOptions);
11
+ NUMBER_FORMATTER_CACHE.set(key, formatter);
12
+ }
13
+ return formatter;
14
+ }
5
15
  const NumberTicker = React.forwardRef(
6
16
  ({
7
17
  className,
@@ -42,10 +52,7 @@ const NumberTicker = React.forwardRef(
42
52
  window.cancelAnimationFrame(animationFrame);
43
53
  };
44
54
  }, [delay, duration, from, value]);
45
- const formatter = React.useMemo(
46
- () => new Intl.NumberFormat(locale, formatOptions),
47
- [formatOptions, locale]
48
- );
55
+ const formatter = getNumberTickerFormatter(locale, formatOptions);
49
56
  return /* @__PURE__ */ jsx(
50
57
  "span",
51
58
  {
@@ -17,6 +17,7 @@ function DefaultLink({
17
17
  }) {
18
18
  return /* @__PURE__ */ jsx("a", { className, href, children });
19
19
  }
20
+ const EMPTY_PROGRESS_CARD_LIST = [];
20
21
  function ContentCardImpl({
21
22
  badgeLabel,
22
23
  badgeVariant = "default",
@@ -24,9 +25,9 @@ function ContentCardImpl({
24
25
  getProgress,
25
26
  href,
26
27
  linkComponent: LinkComponent = DefaultLink,
27
- metadata = [],
28
+ metadata = EMPTY_PROGRESS_CARD_LIST,
28
29
  progressLabel = "completed",
29
- tags = [],
30
+ tags = EMPTY_PROGRESS_CARD_LIST,
30
31
  title
31
32
  }) {
32
33
  const [progress, setProgress] = useState(null);
@@ -59,7 +60,7 @@ function ContentCardImpl({
59
60
  metadata.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 text-xs text-muted-foreground", children: metadata.map((item, index) => /* @__PURE__ */ jsxs("span", { children: [
60
61
  index > 0 ? /* @__PURE__ */ jsx("span", { className: "mr-2", children: "\u2022" }) : null,
61
62
  item
62
- ] }, index)) }) : null,
63
+ ] }, item)) }) : null,
63
64
  tags.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: tags.map((tag) => /* @__PURE__ */ jsx(Badge, { className: "text-xs", variant: "outline", children: tag }, tag)) }) : null
64
65
  ] })
65
66
  ] }) });
@@ -124,10 +124,12 @@ function useProgressTrackerContext() {
124
124
  }
125
125
  return context;
126
126
  }
127
+ const EMPTY_PROGRESS_TRACKER_MODULES = [];
128
+ const EMPTY_PROGRESS_TRACKER_SKILLS = [];
127
129
  function ProgressTrackerRoot({
128
130
  children,
129
131
  className,
130
- modules = [],
132
+ modules = EMPTY_PROGRESS_TRACKER_MODULES,
131
133
  overallProgress,
132
134
  streak = 0,
133
135
  title = "Learning progress",
@@ -352,7 +354,7 @@ function ProgressTrackerModule({
352
354
  lessons,
353
355
  persistKey,
354
356
  progress,
355
- skills = [],
357
+ skills = EMPTY_PROGRESS_TRACKER_SKILLS,
356
358
  status,
357
359
  timeSpent,
358
360
  title,
@@ -172,7 +172,7 @@ function Quiz({
172
172
  selectedIndex,
173
173
  submitted
174
174
  },
175
- index
175
+ typeof opt === "string" ? opt : `quiz-option-${String(opt)}`
176
176
  )) }),
177
177
  hint && !submitted ? /* @__PURE__ */ jsx(
178
178
  QuizHint,
@@ -1,11 +1,33 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useRef, useState } from "react";
3
+ import { Suspense, useEffect, useRef, useState } from "react";
4
4
  import { useRouter, useSearchParams } from "next/navigation";
5
5
  import { useDebounce } from "../../lib/use-debounce";
6
6
  import { Button } from "../button/button";
7
7
  import { Input } from "../input/input";
8
- function SearchBar({
8
+ function SearchBar(props) {
9
+ return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(SearchBarFallback, { ...props }), children: /* @__PURE__ */ jsx(SearchBarInner, { ...props }) });
10
+ }
11
+ function SearchBarFallback({
12
+ className,
13
+ placeholder = "Search posts..."
14
+ }) {
15
+ return /* @__PURE__ */ jsxs("form", { className: `flex gap-2 ${className}`, children: [
16
+ /* @__PURE__ */ jsx(
17
+ Input,
18
+ {
19
+ "aria-label": placeholder,
20
+ className: "flex-1",
21
+ disabled: true,
22
+ placeholder,
23
+ type: "text",
24
+ value: ""
25
+ }
26
+ ),
27
+ /* @__PURE__ */ jsx(Button, { disabled: true, type: "submit", variant: "outline", children: "Search" })
28
+ ] });
29
+ }
30
+ function SearchBarInner({
9
31
  className,
10
32
  onSearch,
11
33
  placeholder = "Search posts..."
@@ -15,11 +15,12 @@ const DEFAULT_LABELS = {
15
15
  prevLabel: "Prev",
16
16
  sectionsLabel: "Sections"
17
17
  };
18
+ const EMPTY_SLIDESHOW_LABELS = {};
18
19
  function SlideshowImpl({
19
20
  completedSections,
20
21
  completionDialogTitle = "Mark section as complete?",
21
22
  currentIndex,
22
- labels = {},
23
+ labels = EMPTY_SLIDESHOW_LABELS,
23
24
  onComplete,
24
25
  onExit,
25
26
  onNavigate,
@@ -333,7 +334,7 @@ function SlideshowImpl({
333
334
  )
334
335
  ]
335
336
  },
336
- `${section.id}-${index}`
337
+ section.id
337
338
  );
338
339
  }) })
339
340
  ]
@@ -140,6 +140,7 @@ function ExpandedPanel({
140
140
  }
141
141
  );
142
142
  }
143
+ const EMPTY_SHARE_PLATFORMS = [];
143
144
  function SocialFAB({
144
145
  actions,
145
146
  bottomOffset = 24,
@@ -150,7 +151,7 @@ function SocialFAB({
150
151
  onClose,
151
152
  onOpen,
152
153
  rightOffset = 24,
153
- sharePlatforms = []
154
+ sharePlatforms = EMPTY_SHARE_PLATFORMS
154
155
  }) {
155
156
  const options = { onAction, onClose, onOpen };
156
157
  const fab = useSocialFab(options);
@@ -131,6 +131,7 @@ function StepByStep({
131
131
  ] }) : null,
132
132
  /* @__PURE__ */ jsx("div", { className: "space-y-0", children: steps.map((step, index) => {
133
133
  const stepElement = step;
134
+ const stepKey = `${stepElement.props.title}-${index + 1}`;
134
135
  return /* @__PURE__ */ jsx(
135
136
  Step,
136
137
  {
@@ -138,7 +139,7 @@ function StepByStep({
138
139
  title: stepElement.props.title,
139
140
  children: stepElement.props.children
140
141
  },
141
- index
142
+ stepKey
142
143
  );
143
144
  }) })
144
145
  ] });
@@ -183,7 +184,7 @@ function StepByStep({
183
184
  title: step.props.title,
184
185
  children: step.props.children
185
186
  },
186
- index
187
+ `${step.props.title}-${index + 1}`
187
188
  )) })
188
189
  ] });
189
190
  }
@@ -175,7 +175,7 @@ function TableOfContentsPanelImpl({
175
175
  )
176
176
  ]
177
177
  }
178
- ) }, `${section.id}-${index}`);
178
+ ) }, section.id);
179
179
  }) })
180
180
  }
181
181
  ),
@@ -30,17 +30,24 @@ function Terminal({
30
30
  ] })
31
31
  ] }),
32
32
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
33
- /* @__PURE__ */ jsx("div", { className: "p-4 font-mono text-sm space-y-1 overflow-x-auto", children: lines.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-start", children: [
34
- line.type === "command" && /* @__PURE__ */ jsxs(Fragment, { children: [
35
- /* @__PURE__ */ jsx("span", { className: "text-green-400 mr-2 select-none", children: "$" }),
36
- /* @__PURE__ */ jsx("span", { className: "text-zinc-100", children: line.content })
37
- ] }),
38
- line.type === "output" && /* @__PURE__ */ jsx("span", { className: "text-zinc-400", children: line.content }),
39
- line.type === "comment" && /* @__PURE__ */ jsxs("span", { className: "text-zinc-600 italic", children: [
40
- "# ",
41
- line.content
42
- ] })
43
- ] }, index)) }),
33
+ /* @__PURE__ */ jsx("div", { className: "p-4 font-mono text-sm space-y-1 overflow-x-auto", children: lines.map((line) => /* @__PURE__ */ jsxs(
34
+ "div",
35
+ {
36
+ className: "flex items-start",
37
+ children: [
38
+ line.type === "command" && /* @__PURE__ */ jsxs(Fragment, { children: [
39
+ /* @__PURE__ */ jsx("span", { className: "text-green-400 mr-2 select-none", children: "$" }),
40
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-100", children: line.content })
41
+ ] }),
42
+ line.type === "output" && /* @__PURE__ */ jsx("span", { className: "text-zinc-400", children: line.content }),
43
+ line.type === "comment" && /* @__PURE__ */ jsxs("span", { className: "text-zinc-600 italic", children: [
44
+ "# ",
45
+ line.content
46
+ ] })
47
+ ]
48
+ },
49
+ `${line.type}-${line.content}`
50
+ )) }),
44
51
  copyable && commands.length > 0 ? /* @__PURE__ */ jsx(
45
52
  Button,
46
53
  {
@@ -89,17 +96,24 @@ function SimpleTerminal({
89
96
  ] })
90
97
  ] }),
91
98
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
92
- /* @__PURE__ */ jsx("div", { className: "p-4 font-mono text-sm space-y-1 overflow-x-auto", children: lines.map((line, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-start", children: [
93
- line.type === "command" && /* @__PURE__ */ jsxs(Fragment, { children: [
94
- /* @__PURE__ */ jsx("span", { className: "text-green-400 mr-2 select-none", children: "$" }),
95
- /* @__PURE__ */ jsx("span", { className: "text-zinc-100", children: line.content })
96
- ] }),
97
- line.type === "output" && /* @__PURE__ */ jsx("span", { className: "text-zinc-400", children: line.content }),
98
- line.type === "comment" && /* @__PURE__ */ jsxs("span", { className: "text-zinc-600 italic", children: [
99
- "# ",
100
- line.content
101
- ] })
102
- ] }, index)) }),
99
+ /* @__PURE__ */ jsx("div", { className: "p-4 font-mono text-sm space-y-1 overflow-x-auto", children: lines.map((line) => /* @__PURE__ */ jsxs(
100
+ "div",
101
+ {
102
+ className: "flex items-start",
103
+ children: [
104
+ line.type === "command" && /* @__PURE__ */ jsxs(Fragment, { children: [
105
+ /* @__PURE__ */ jsx("span", { className: "text-green-400 mr-2 select-none", children: "$" }),
106
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-100", children: line.content })
107
+ ] }),
108
+ line.type === "output" && /* @__PURE__ */ jsx("span", { className: "text-zinc-400", children: line.content }),
109
+ line.type === "comment" && /* @__PURE__ */ jsxs("span", { className: "text-zinc-600 italic", children: [
110
+ "# ",
111
+ line.content
112
+ ] })
113
+ ]
114
+ },
115
+ `${line.type}-${line.content}`
116
+ )) }),
103
117
  commands.length > 0 && /* @__PURE__ */ jsx(
104
118
  Button,
105
119
  {
@@ -7,6 +7,32 @@ import { Badge } from "../badge/badge";
7
7
  const CENTS_PER_UNIT = 100;
8
8
  const DEFAULT_LOCALE = "en-US";
9
9
  const DEFAULT_CURRENCY = "USD";
10
+ const CURRENCY_FORMATTER_CACHE = /* @__PURE__ */ new Map();
11
+ function getCurrencyFormatter(locale, currency) {
12
+ const key = `${locale}|${currency}`;
13
+ let formatter = CURRENCY_FORMATTER_CACHE.get(key);
14
+ if (!formatter) {
15
+ formatter = new Intl.NumberFormat(locale, {
16
+ currency,
17
+ style: "currency"
18
+ });
19
+ CURRENCY_FORMATTER_CACHE.set(key, formatter);
20
+ }
21
+ return formatter;
22
+ }
23
+ const DATE_FORMATTER_CACHE = /* @__PURE__ */ new Map();
24
+ function getTransactionDateFormatter(locale) {
25
+ let formatter = DATE_FORMATTER_CACHE.get(locale);
26
+ if (!formatter) {
27
+ formatter = new Intl.DateTimeFormat(locale, {
28
+ day: "numeric",
29
+ month: "short",
30
+ year: "numeric"
31
+ });
32
+ DATE_FORMATTER_CACHE.set(locale, formatter);
33
+ }
34
+ return formatter;
35
+ }
10
36
  const DEFAULT_LABELS = {
11
37
  active: "Active",
12
38
  canceled: "Canceled",
@@ -44,17 +70,12 @@ const INTERVAL_LABEL = {
44
70
  };
45
71
  function formatTransactionAmount(amountCents, options = {}) {
46
72
  const { currency = DEFAULT_CURRENCY, locale = DEFAULT_LOCALE } = options;
47
- return new Intl.NumberFormat(locale, {
48
- currency,
49
- style: "currency"
50
- }).format(amountCents / CENTS_PER_UNIT);
73
+ return getCurrencyFormatter(locale, currency).format(
74
+ amountCents / CENTS_PER_UNIT
75
+ );
51
76
  }
52
77
  function formatTransactionDate(timestamp, locale = DEFAULT_LOCALE) {
53
- return new Intl.DateTimeFormat(locale, {
54
- day: "numeric",
55
- month: "short",
56
- year: "numeric"
57
- }).format(new Date(timestamp));
78
+ return getTransactionDateFormatter(locale).format(new Date(timestamp));
58
79
  }
59
80
  const TransactionListBase = forwardRef(
60
81
  (props, ref) => {
@@ -72,7 +72,7 @@ function TutorialCompleteImpl({
72
72
  /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
73
73
  ]
74
74
  },
75
- `${section.id}-${index}`
75
+ section.id
76
76
  );
77
77
  }) })
78
78
  ] }),
@@ -21,10 +21,14 @@ function formatPercent(value) {
21
21
  if (!Number.isFinite(value)) return "0%";
22
22
  return `${Math.round(value)}%`;
23
23
  }
24
+ const LARGE_VALUE_FORMATTER = new Intl.NumberFormat("en-US", {
25
+ maximumFractionDigits: 0
26
+ });
27
+ const SMALL_VALUE_FORMATTER = new Intl.NumberFormat("en-US", {
28
+ maximumFractionDigits: 1
29
+ });
24
30
  function formatValue(value) {
25
- return new Intl.NumberFormat("en-US", {
26
- maximumFractionDigits: value >= 100 ? 0 : 1
27
- }).format(value);
31
+ return value >= 100 ? LARGE_VALUE_FORMATTER.format(value) : SMALL_VALUE_FORMATTER.format(value);
28
32
  }
29
33
  function getRelativeWidth(value, maxValue) {
30
34
  if (maxValue <= 0) return 0;
@@ -29,20 +29,40 @@ function useLiveDate(now, updateIntervalMs) {
29
29
  }, [fixedNow, updateIntervalMs]);
30
30
  return liveNow;
31
31
  }
32
+ const TIME_FORMATTER_CACHE = /* @__PURE__ */ new Map();
33
+ function getTimeFormatter(locale, timeZone) {
34
+ const key = `${locale}|${timeZone}`;
35
+ let formatter = TIME_FORMATTER_CACHE.get(key);
36
+ if (!formatter) {
37
+ formatter = new Intl.DateTimeFormat(locale, {
38
+ hour: "numeric",
39
+ minute: "2-digit",
40
+ timeZone,
41
+ timeZoneName: "short"
42
+ });
43
+ TIME_FORMATTER_CACHE.set(key, formatter);
44
+ }
45
+ return formatter;
46
+ }
47
+ const DATE_FORMATTER_CACHE = /* @__PURE__ */ new Map();
48
+ function getDateFormatter(locale, timeZone) {
49
+ const key = `${locale}|${timeZone}`;
50
+ let formatter = DATE_FORMATTER_CACHE.get(key);
51
+ if (!formatter) {
52
+ formatter = new Intl.DateTimeFormat(locale, {
53
+ day: "numeric",
54
+ month: "short",
55
+ timeZone,
56
+ weekday: "short"
57
+ });
58
+ DATE_FORMATTER_CACHE.set(key, formatter);
59
+ }
60
+ return formatter;
61
+ }
32
62
  function formatZoneDateTime(zone, date, showDate) {
33
63
  const locale = zone.locale ?? "en-US";
34
- const timeFormatter = new Intl.DateTimeFormat(locale, {
35
- hour: "numeric",
36
- minute: "2-digit",
37
- timeZone: zone.timeZone,
38
- timeZoneName: "short"
39
- });
40
- const dateFormatter = new Intl.DateTimeFormat(locale, {
41
- day: "numeric",
42
- month: "short",
43
- timeZone: zone.timeZone,
44
- weekday: "short"
45
- });
64
+ const timeFormatter = getTimeFormatter(locale, zone.timeZone);
65
+ const dateFormatter = getDateFormatter(locale, zone.timeZone);
46
66
  return {
47
67
  date: showDate ? dateFormatter.format(date) : "",
48
68
  time: timeFormatter.format(date)
package/dist/index.d.ts CHANGED
@@ -4692,7 +4692,7 @@ type SearchBarProps = {
4692
4692
  onSearch?: (query: string) => void;
4693
4693
  placeholder?: string;
4694
4694
  };
4695
- declare function SearchBar({ className, onSearch, placeholder, }: SearchBarProps): react_jsx_runtime.JSX.Element;
4695
+ declare function SearchBar(props: SearchBarProps): react_jsx_runtime.JSX.Element;
4696
4696
 
4697
4697
  type ScopeSelectorNode = {
4698
4698
  badge?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vllnt/ui",
3
- "version": "0.2.1-canary.06f0e84",
3
+ "version": "0.2.1-canary.55e0e0a",
4
4
  "description": "React component library — 225 components built on Radix UI, Tailwind CSS, and CVA",
5
5
  "license": "MIT",
6
6
  "author": "vllnt",