@vllnt/ui 0.2.1-canary.25c4600 → 0.2.1-canary.26ff803

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 (44) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/components/animated-text/animated-text.js +6 -3
  3. package/dist/components/auto-reload/auto-reload.js +1 -1
  4. package/dist/components/carousel/carousel.js +14 -6
  5. package/dist/components/checklist/checklist.js +31 -6
  6. package/dist/components/checklist/index.js +8 -2
  7. package/dist/components/combobox/combobox.js +0 -5
  8. package/dist/components/completion-dialog/completion-dialog.js +14 -9
  9. package/dist/components/content-intro/content-intro.js +12 -11
  10. package/dist/components/conversation-thread/conversation-thread.js +3 -3
  11. package/dist/components/date-picker/date-picker.js +0 -5
  12. package/dist/components/file-upload/file-upload.js +0 -5
  13. package/dist/components/filter-bar/filter-bar.js +6 -6
  14. package/dist/components/gantt-chart/gantt-chart.js +8 -7
  15. package/dist/components/globe-3d/globe-3d.js +16 -4
  16. package/dist/components/interactive-timeline/interactive-timeline.js +6 -3
  17. package/dist/components/lang-provider/lang-provider.js +3 -3
  18. package/dist/components/live-feed/live-feed.js +1 -1
  19. package/dist/components/map-2d/map-2d.js +1 -1
  20. package/dist/components/map-timeline/map-timeline.js +1 -1
  21. package/dist/components/mdx-content/mdx-content.js +14 -5
  22. package/dist/components/navbar-saas/navbar-saas.js +3 -4
  23. package/dist/components/number-ticker/number-ticker.js +1 -1
  24. package/dist/components/progress-tracker/progress-tracker.js +16 -8
  25. package/dist/components/search-dialog/search-dialog.js +483 -50
  26. package/dist/components/sidebar/sidebar.js +11 -7
  27. package/dist/components/social-fab/social-fab.js +4 -4
  28. package/dist/components/spinner/spinner.js +1 -1
  29. package/dist/components/spinner/unicode-spinner.js +4 -2
  30. package/dist/components/stat-card/stat-card.js +1 -1
  31. package/dist/components/status-board/status-board.js +10 -4
  32. package/dist/components/tabs/tabs.js +26 -8
  33. package/dist/components/tags-input/tags-input.js +11 -3
  34. package/dist/components/terminal/terminal.js +10 -2
  35. package/dist/components/theme-toggle/theme-toggle.js +2 -2
  36. package/dist/components/thinking-block/thinking-block.js +2 -2
  37. package/dist/components/tldr-section/tldr-section.js +9 -7
  38. package/dist/components/transaction-list/transaction-list.js +2 -2
  39. package/dist/components/tutorial-complete/tutorial-complete.js +1 -1
  40. package/dist/components/tutorial-intro-content/tutorial-intro-content.js +1 -1
  41. package/dist/components/tutorial-mdx/tutorial-mdx.js +1 -1
  42. package/dist/components/world-clock-bar/world-clock-bar.js +2 -2
  43. package/dist/index.d.ts +19 -6
  44. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
10
10
 
11
11
  ### Added
12
12
 
13
+ - **Release intelligence surface** — `/changelog`, `/releases`, `/rss.xml`, and `/atom.xml` expose one changelog source through HTML, GitHub release cards, and feed readers. `/docs/changelog` redirects to `/changelog`.
13
14
  - **Hooks + utility primitives** — `CopyButton` (+ `useCopyToClipboard` hook), `Banner` + `BannerAction`, `Kbd`, `EmptyState`, `DocumentSiblingNav`.
14
15
  - **Pricing + identity cards** — `PricingTable` + `PricingPlan`, `HistoricalFigureCard`, `CivilizationCard` (+ `CivilizationComparison`).
15
16
  - **Newsletter** — `NewsletterSignup` (state-machine driven submit flow).
@@ -87,13 +87,16 @@ function buildRevealPlan(direction, length, randomness) {
87
87
  return revealPlan;
88
88
  }
89
89
  function useRevealProgress(active, length, stagger) {
90
- const [progress, setProgress] = React.useState(0);
90
+ const [progress, setProgress] = React.useState(() => active ? 0 : length);
91
+ const [revealKey, setRevealKey] = React.useState({ active, length, stagger });
92
+ if (revealKey.active !== active || revealKey.length !== length || revealKey.stagger !== stagger) {
93
+ setRevealKey({ active, length, stagger });
94
+ setProgress(active ? 0 : length);
95
+ }
91
96
  React.useEffect(() => {
92
97
  if (!active) {
93
- setProgress(length);
94
98
  return;
95
99
  }
96
- setProgress(0);
97
100
  const revealInterval = window.setInterval(
98
101
  () => {
99
102
  setProgress((current) => {
@@ -39,7 +39,7 @@ function getCurrencyFormatter(locale, currency) {
39
39
  const key = `${locale}|${currency}`;
40
40
  let formatter = CURRENCY_FORMATTER_CACHE.get(key);
41
41
  if (!formatter) {
42
- formatter = new Intl.NumberFormat(locale, {
42
+ formatter = Intl.NumberFormat(locale, {
43
43
  currency,
44
44
  style: "currency"
45
45
  });
@@ -7,6 +7,7 @@ import {
7
7
  useContext,
8
8
  useEffect,
9
9
  useMemo,
10
+ useRef,
10
11
  useState
11
12
  } from "react";
12
13
  import useEmblaCarousel from "embla-carousel-react";
@@ -43,6 +44,10 @@ function useCarouselLogic({
43
44
  setCanScrollPrevious(api2.canScrollPrev());
44
45
  setCanScrollNext(api2.canScrollNext());
45
46
  }, []);
47
+ const onSelectReference = useRef(onSelect);
48
+ useEffect(() => {
49
+ onSelectReference.current = onSelect;
50
+ }, [onSelect]);
46
51
  const scrollPrevious = useCallback(() => {
47
52
  api?.scrollPrev();
48
53
  }, [api]);
@@ -71,17 +76,20 @@ function useCarouselLogic({
71
76
  if (!api) {
72
77
  return;
73
78
  }
74
- api.on("reInit", onSelect);
75
- api.on("select", onSelect);
79
+ const notifySelection = (selectedApi) => {
80
+ onSelectReference.current(selectedApi);
81
+ };
82
+ api.on("reInit", notifySelection);
83
+ api.on("select", notifySelection);
76
84
  const rafId = requestAnimationFrame(() => {
77
- onSelect(api);
85
+ notifySelection(api);
78
86
  });
79
87
  return () => {
80
- api?.off("select", onSelect);
81
- api?.off("reInit", onSelect);
88
+ api?.off("select", notifySelection);
89
+ api?.off("reInit", notifySelection);
82
90
  cancelAnimationFrame(rafId);
83
91
  };
84
- }, [api, onSelect]);
92
+ }, [api]);
85
93
  return {
86
94
  api,
87
95
  canScrollNext,
@@ -3,6 +3,31 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { cn } from "../../lib/utils";
5
5
  const CHECKLIST_PROGRESS_EVENT = "vllnt:checklist-progress-change";
6
+ const CHECKLIST_STORAGE_VERSION = 1;
7
+ function stringItemsFromUnknown(value) {
8
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
9
+ }
10
+ function parseChecklistStorageValue(saved) {
11
+ try {
12
+ const parsed = JSON.parse(saved);
13
+ if (Array.isArray(parsed)) {
14
+ return stringItemsFromUnknown(parsed);
15
+ }
16
+ if (typeof parsed === "object" && parsed !== null && "version" in parsed && "checked" in parsed && parsed.version === CHECKLIST_STORAGE_VERSION) {
17
+ return stringItemsFromUnknown(parsed.checked);
18
+ }
19
+ } catch {
20
+ return [];
21
+ }
22
+ return [];
23
+ }
24
+ function createChecklistStorageValue(ids) {
25
+ const payload = {
26
+ checked: [...ids],
27
+ version: CHECKLIST_STORAGE_VERSION
28
+ };
29
+ return JSON.stringify(payload);
30
+ }
6
31
  function ChecklistItemRow({
7
32
  isChecked,
8
33
  item,
@@ -117,10 +142,7 @@ function Checklist({
117
142
  if (typeof window !== "undefined" && persistKey) {
118
143
  const saved = localStorage.getItem(`checklist:${persistKey}`);
119
144
  if (saved) {
120
- try {
121
- return new Set(JSON.parse(saved));
122
- } catch {
123
- }
145
+ return new Set(parseChecklistStorageValue(saved));
124
146
  }
125
147
  }
126
148
  return /* @__PURE__ */ new Set();
@@ -134,7 +156,7 @@ function Checklist({
134
156
  try {
135
157
  localStorage.setItem(
136
158
  `checklist:${persistKey}`,
137
- JSON.stringify([...newChecked])
159
+ createChecklistStorageValue(newChecked)
138
160
  );
139
161
  window.dispatchEvent(
140
162
  new CustomEvent(CHECKLIST_PROGRESS_EVENT, {
@@ -187,5 +209,8 @@ function Checklist({
187
209
  }
188
210
  export {
189
211
  CHECKLIST_PROGRESS_EVENT,
190
- Checklist
212
+ CHECKLIST_STORAGE_VERSION,
213
+ Checklist,
214
+ createChecklistStorageValue,
215
+ parseChecklistStorageValue
191
216
  };
@@ -1,8 +1,14 @@
1
1
  import {
2
2
  Checklist,
3
- CHECKLIST_PROGRESS_EVENT
3
+ CHECKLIST_PROGRESS_EVENT,
4
+ CHECKLIST_STORAGE_VERSION,
5
+ createChecklistStorageValue,
6
+ parseChecklistStorageValue
4
7
  } from "./checklist";
5
8
  export {
6
9
  CHECKLIST_PROGRESS_EVENT,
7
- Checklist
10
+ CHECKLIST_STORAGE_VERSION,
11
+ Checklist,
12
+ createChecklistStorageValue,
13
+ parseChecklistStorageValue
8
14
  };
@@ -15,11 +15,6 @@ import {
15
15
  import { Popover, PopoverContent, PopoverTrigger } from "../popover";
16
16
  function useComboboxValue(value, onValueChange) {
17
17
  const [internalValue, setInternalValue] = React.useState(value ?? "");
18
- React.useEffect(() => {
19
- if (value !== void 0) {
20
- setInternalValue(value);
21
- }
22
- }, [value]);
23
18
  const resolvedValue = value ?? internalValue;
24
19
  const setResolvedValue = (nextValue) => {
25
20
  if (value === void 0) {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { memo, useCallback, useEffect, useRef } from "react";
3
+ import { memo, useEffect, useRef } from "react";
4
4
  import { cn } from "../../lib/utils";
5
5
  import { Button } from "../button";
6
6
  function DialogContent({
@@ -98,8 +98,11 @@ function CompletionDialogImpl({
98
98
  onConfirm,
99
99
  title
100
100
  }) {
101
- const handleKeyDown = useCallback(
102
- (event) => {
101
+ const keyDownHandlerRef = useRef(() => {
102
+ return;
103
+ });
104
+ useEffect(() => {
105
+ keyDownHandlerRef.current = (event) => {
103
106
  if (!isOpen) return;
104
107
  if (event.key === "Escape") {
105
108
  event.preventDefault();
@@ -118,16 +121,18 @@ function CompletionDialogImpl({
118
121
  event.stopPropagation();
119
122
  onCancel();
120
123
  }
121
- },
122
- [isOpen, onClose, onConfirm, onCancel, confirmShortcut, cancelShortcut]
123
- );
124
+ };
125
+ }, [cancelShortcut, confirmShortcut, isOpen, onCancel, onClose, onConfirm]);
124
126
  useEffect(() => {
125
127
  if (!isOpen) return;
126
- document.addEventListener("keydown", handleKeyDown, true);
128
+ const onDocumentKeyDown = (event) => {
129
+ keyDownHandlerRef.current(event);
130
+ };
131
+ document.addEventListener("keydown", onDocumentKeyDown, true);
127
132
  return () => {
128
- document.removeEventListener("keydown", handleKeyDown, true);
133
+ document.removeEventListener("keydown", onDocumentKeyDown, true);
129
134
  };
130
- }, [isOpen, handleKeyDown]);
135
+ }, [isOpen]);
131
136
  if (!isOpen) return null;
132
137
  return /* @__PURE__ */ jsxs(
133
138
  "div",
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { memo, useCallback, useEffect } from "react";
3
+ import { memo, useEffect, useRef } from "react";
4
4
  import { cn } from "../../lib/utils";
5
5
  import { Button } from "../button";
6
6
  const DEFAULT_LABELS = {
@@ -25,21 +25,22 @@ function ContentIntroImpl({
25
25
  }) {
26
26
  const mergedLabels = { ...DEFAULT_LABELS, ...labels };
27
27
  const hasProgress = completedSections.size > 0;
28
- const handleKeyDown = useCallback(
29
- (event) => {
28
+ const onStartRef = useRef(onStart);
29
+ useEffect(() => {
30
+ onStartRef.current = onStart;
31
+ }, [onStart]);
32
+ useEffect(() => {
33
+ const onDocumentKeyDown = (event) => {
30
34
  if (event.key === "Enter") {
31
35
  event.preventDefault();
32
- onStart();
36
+ onStartRef.current();
33
37
  }
34
- },
35
- [onStart]
36
- );
37
- useEffect(() => {
38
- document.addEventListener("keydown", handleKeyDown);
38
+ };
39
+ document.addEventListener("keydown", onDocumentKeyDown);
39
40
  return () => {
40
- document.removeEventListener("keydown", handleKeyDown);
41
+ document.removeEventListener("keydown", onDocumentKeyDown);
41
42
  };
42
- }, [handleKeyDown]);
43
+ }, []);
43
44
  return /* @__PURE__ */ jsxs(Fragment, { children: [
44
45
  /* @__PURE__ */ jsxs("div", { className: "animate-in fade-in-0 duration-500 pb-24", children: [
45
46
  /* @__PURE__ */ jsxs("section", { className: "py-6", children: [
@@ -319,18 +319,18 @@ const ConversationLoading = forwardRef(({ className }, reference) => {
319
319
  /* @__PURE__ */ jsx(
320
320
  "span",
321
321
  {
322
- className: "size-2 animate-bounce rounded-full bg-muted-foreground",
322
+ className: "size-2 animate-pulse rounded-full bg-muted-foreground",
323
323
  style: { animationDelay: "-0.3s" }
324
324
  }
325
325
  ),
326
326
  /* @__PURE__ */ jsx(
327
327
  "span",
328
328
  {
329
- className: "size-2 animate-bounce rounded-full bg-muted-foreground",
329
+ className: "size-2 animate-pulse rounded-full bg-muted-foreground",
330
330
  style: { animationDelay: "-0.15s" }
331
331
  }
332
332
  ),
333
- /* @__PURE__ */ jsx("span", { className: "size-2 animate-bounce rounded-full bg-muted-foreground" })
333
+ /* @__PURE__ */ jsx("span", { className: "size-2 animate-pulse rounded-full bg-muted-foreground" })
334
334
  ]
335
335
  }
336
336
  );
@@ -25,11 +25,6 @@ const DatePicker = React.forwardRef(
25
25
  value
26
26
  );
27
27
  const selectedDate = value ?? internalValue;
28
- React.useEffect(() => {
29
- if (value !== void 0) {
30
- setInternalValue(value);
31
- }
32
- }, [value]);
33
28
  const handleSelect = (nextDate) => {
34
29
  if (value === void 0) {
35
30
  setInternalValue(nextDate);
@@ -8,11 +8,6 @@ function useFileUploadState(controlledFiles, multiple, onFilesChange) {
8
8
  const [internalFiles, setInternalFiles] = React.useState(
9
9
  controlledFiles ?? []
10
10
  );
11
- React.useEffect(() => {
12
- if (controlledFiles !== void 0) {
13
- setInternalFiles(controlledFiles);
14
- }
15
- }, [controlledFiles]);
16
11
  const resolvedFiles = controlledFiles ?? internalFiles;
17
12
  const updateFiles = React.useCallback(
18
13
  (nextFiles) => {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { memo, useCallback, useState } from "react";
3
+ import { memo, useCallback, useTransition } from "react";
4
4
  import { cn } from "../../lib/utils";
5
5
  import { Badge } from "../badge";
6
6
  function SearchInput({
@@ -160,15 +160,15 @@ function FilterBarImpl({
160
160
  searchQuery,
161
161
  tags
162
162
  }) {
163
- const [isPending, setIsPending] = useState(false);
163
+ const [isPending, startTransition] = useTransition();
164
164
  const mergedLabels = { ...DEFAULT_LABELS, ...labels };
165
165
  const handleDifficultyChange = useCallback(
166
166
  (difficulty) => {
167
- setIsPending(true);
168
- onFiltersChange({ difficulty });
169
- setIsPending(false);
167
+ startTransition(() => {
168
+ onFiltersChange({ difficulty });
169
+ });
170
170
  },
171
- [onFiltersChange]
171
+ [onFiltersChange, startTransition]
172
172
  );
173
173
  const handleSearchChange = useCallback(
174
174
  (search) => {
@@ -56,7 +56,7 @@ function getTickDateTimeFormatter(locale, scale) {
56
56
  let formatter = TICK_FORMATTER_CACHE.get(key);
57
57
  if (!formatter) {
58
58
  const options = scale === "month" ? { month: "short", year: "numeric" } : { day: "2-digit", month: "short" };
59
- formatter = new Intl.DateTimeFormat(locale, options);
59
+ formatter = Intl.DateTimeFormat(locale, options);
60
60
  TICK_FORMATTER_CACHE.set(key, formatter);
61
61
  }
62
62
  return formatter;
@@ -93,13 +93,14 @@ function buildTicks(input) {
93
93
  const formatter = buildTickFormatter(scale, locale);
94
94
  const stepDays = getTickStep(scale);
95
95
  const tickCount = Math.floor(totalDays / stepDays);
96
- return Array.from({ length: tickCount + 1 }).map((_, index) => {
96
+ return Array.from({ length: tickCount + 1 }).reduce((ticks, _, index) => {
97
97
  const day = index * stepDays;
98
- return {
99
- date: new Date(start.getTime() + day * MS_PER_DAY),
100
- offset: day
101
- };
102
- }).filter((tick) => tick.date.getTime() <= end.getTime()).map((tick) => ({ label: formatter(tick.date), offset: tick.offset }));
98
+ const date = new Date(start.getTime() + day * MS_PER_DAY);
99
+ if (date.getTime() <= end.getTime()) {
100
+ ticks.push({ label: formatter(date), offset: day });
101
+ }
102
+ return ticks;
103
+ }, []);
103
104
  }
104
105
  function useChartGeometry(options) {
105
106
  const { endDate, locale, scale, startDate } = options;
@@ -161,9 +161,12 @@ const GlobeArc = forwardRef(
161
161
  GlobeArc.displayName = "GlobeArc";
162
162
  function buildLine(arguments_) {
163
163
  const { points, rotationLat, rotationLng } = arguments_;
164
- return points.map((coord) => project(coord, rotationLng, rotationLat)).reduce(
165
- (state, projected) => {
166
- if (!projected.visible) return { path: state.path, pen: "up" };
164
+ return points.reduce(
165
+ (state, coord) => {
166
+ const projected = project(coord, rotationLng, rotationLat);
167
+ if (!projected.visible) {
168
+ return { path: state.path, pen: "up" };
169
+ }
167
170
  const head = state.pen === "up" ? "M" : "L";
168
171
  const separator = state.path.length > 0 ? " " : "";
169
172
  return {
@@ -185,7 +188,16 @@ function Graticule({ rotationLat, rotationLng }) {
185
188
  const meridians = range(-150, 180, 30).map(
186
189
  (lng) => range(-85, 85, 5).map((lat) => ({ lat, lng }))
187
190
  );
188
- const lines = [...parallels, ...meridians].map((points) => buildLine({ points, rotationLat, rotationLng })).filter((path) => path.length > 0);
191
+ const lines = [...parallels, ...meridians].reduce(
192
+ (paths, points) => {
193
+ const path = buildLine({ points, rotationLat, rotationLng });
194
+ if (path.length > 0) {
195
+ paths.push(path);
196
+ }
197
+ return paths;
198
+ },
199
+ []
200
+ );
189
201
  return /* @__PURE__ */ jsx(
190
202
  "g",
191
203
  {
@@ -607,9 +607,12 @@ function useTimelineContextValue(arguments_) {
607
607
  function useTimelineFilter(categories, events) {
608
608
  const [hidden, setHidden] = useState(() => /* @__PURE__ */ new Set());
609
609
  const visibleCategories = useMemo(
610
- () => new Set(
611
- categories.filter((category) => !hidden.has(category.id)).map((category) => category.id)
612
- ),
610
+ () => categories.reduce((visible, category) => {
611
+ if (!hidden.has(category.id)) {
612
+ visible.add(category.id);
613
+ }
614
+ return visible;
615
+ }, /* @__PURE__ */ new Set()),
613
616
  [categories, hidden]
614
617
  );
615
618
  const toggleCategory = useCallback((id) => {
@@ -6,11 +6,11 @@ function LangProvider({
6
6
  supportedLanguages = ["en", "fr"]
7
7
  }) {
8
8
  const pathname = usePathname();
9
+ const langMatch = /^\/([a-z]{2})(?:\/|$)/.exec(pathname);
10
+ const lang = langMatch && supportedLanguages.includes(langMatch[1]) ? langMatch[1] : defaultLanguage;
9
11
  useEffect(() => {
10
- const langMatch = /^\/([a-z]{2})(?:\/|$)/.exec(pathname);
11
- const lang = langMatch && supportedLanguages.includes(langMatch[1]) ? langMatch[1] : defaultLanguage;
12
12
  document.documentElement.setAttribute("lang", lang);
13
- }, [pathname, defaultLanguage, supportedLanguages]);
13
+ }, [lang]);
14
14
  return null;
15
15
  }
16
16
  export {
@@ -131,7 +131,7 @@ const LiveFeed = React.forwardRef(
131
131
  [events, maxItems]
132
132
  );
133
133
  return /* @__PURE__ */ jsxs(Card, { className: cn("shadow-sm", className), ref, ...props, children: [
134
- /* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-3 space-y-0 pb-3", children: [
134
+ /* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-3 gap-y-0 pb-3", children: [
135
135
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
136
136
  /* @__PURE__ */ jsx(CardTitle, { className: "text-base", children: title }),
137
137
  description ? /* @__PURE__ */ jsx(CardDescription, { children: description }) : null
@@ -53,7 +53,7 @@ function useMapState(arguments_) {
53
53
  }, [center]);
54
54
  const [pan, setPan] = useState(initialPan);
55
55
  const [zoom, setZoom] = useState(
56
- clamp(initialZoom, MIN_ZOOM, MAX_ZOOM)
56
+ () => clamp(initialZoom, MIN_ZOOM, MAX_ZOOM)
57
57
  );
58
58
  const zoomIn = useCallback(() => {
59
59
  setZoom((current) => clamp(current * ZOOM_STEP, MIN_ZOOM, MAX_ZOOM));
@@ -301,7 +301,7 @@ function useTimelineCtx(arguments_) {
301
301
  function useTimelineState(arguments_) {
302
302
  const { endYear, initialYear, onYearChange, startYear } = arguments_;
303
303
  const [year, setYear] = useState(
304
- clamp(initialYear ?? startYear, startYear, endYear)
304
+ () => clamp(initialYear ?? startYear, startYear, endYear)
305
305
  );
306
306
  const [isPlaying, setIsPlaying] = useState(false);
307
307
  const updateYear = useCallback(
@@ -2,6 +2,7 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { evaluate } from "@mdx-js/mdx";
3
3
  import * as runtime from "react/jsx-runtime";
4
4
  import ReactMarkdown from "react-markdown";
5
+ import remarkGfm from "remark-gfm";
5
6
  import { CodeBlock } from "../code-block/code-block";
6
7
  const MDXComponents = {
7
8
  a: ({ children, href, ...props }) => /* @__PURE__ */ jsx(
@@ -16,7 +17,7 @@ const MDXComponents = {
16
17
  blockquote: ({ children, ...props }) => /* @__PURE__ */ jsx(
17
18
  "blockquote",
18
19
  {
19
- className: "border-l-4 border-primary pl-4 italic text-muted-foreground my-6 py-2 text-sm",
20
+ className: "border-l border-primary pl-4 italic text-muted-foreground my-6 py-2 text-sm",
20
21
  ...props,
21
22
  children
22
23
  }
@@ -89,14 +90,15 @@ const proseClasses = [
89
90
  "prose-strong:font-semibold prose-em:italic",
90
91
  "prose-a:text-primary prose-a:underline prose-a:underline-offset-4 hover:prose-a:text-primary/80",
91
92
  "prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono",
92
- "prose-pre:my-6 prose-pre:overflow-x-auto prose-pre:rounded-lg prose-pre:border prose-pre:bg-black prose-pre:py-4 prose-pre:font-mono prose-pre:text-sm prose-pre:text-white prose-pre:shadow-lg dark:prose-pre:bg-zinc-900",
93
- "prose-blockquote:border-l-4 prose-blockquote:border-primary prose-blockquote:pl-4 prose-blockquote:italic prose-blockquote:text-muted-foreground prose-blockquote:my-6 prose-blockquote:py-2",
93
+ "prose-pre:my-6 prose-pre:overflow-x-auto prose-pre:rounded-lg prose-pre:border prose-pre:bg-zinc-950 prose-pre:py-4 prose-pre:font-mono prose-pre:text-sm prose-pre:text-white prose-pre:shadow-lg dark:prose-pre:bg-zinc-900",
94
+ "prose-blockquote:border-l prose-blockquote:border-primary prose-blockquote:pl-4 prose-blockquote:italic prose-blockquote:text-muted-foreground prose-blockquote:my-6 prose-blockquote:py-2",
94
95
  "prose-hr:my-8 prose-hr:border-border",
95
96
  "prose-table:w-full prose-table:border-collapse prose-table:border prose-table:border-border",
96
97
  "prose-th:border prose-th:border-border prose-th:bg-muted prose-th:p-2 prose-th:text-left prose-th:font-medium",
97
98
  "prose-td:border prose-td:border-border prose-td:p-2",
98
99
  "prose-img:rounded-lg prose-img:border prose-img:border-border prose-img:shadow-lg"
99
100
  ].join(" ");
101
+ const markdownPlugins = [remarkGfm];
100
102
  function removeImportStatements(content, componentNames) {
101
103
  let processed = content.replaceAll(/^import\s+.*CodeBlock.*from.*$/gm, "");
102
104
  componentNames.forEach((name) => {
@@ -143,9 +145,16 @@ async function MDXContent({
143
145
  if (Component) {
144
146
  return /* @__PURE__ */ jsx("div", { className: proseClasses, children: /* @__PURE__ */ jsx(Component, { components: allComponents }) });
145
147
  }
146
- return /* @__PURE__ */ jsx("div", { className: proseClasses, children: /* @__PURE__ */ jsx(ReactMarkdown, { components: allComponents, children: content }) });
148
+ return /* @__PURE__ */ jsx("div", { className: proseClasses, children: /* @__PURE__ */ jsx(
149
+ ReactMarkdown,
150
+ {
151
+ components: allComponents,
152
+ remarkPlugins: markdownPlugins,
153
+ children: content
154
+ }
155
+ ) });
147
156
  }
148
- return /* @__PURE__ */ jsx("div", { className: proseClasses, children: /* @__PURE__ */ jsx(ReactMarkdown, { components: allComponents, children: content }) });
157
+ return /* @__PURE__ */ jsx("div", { className: proseClasses, children: /* @__PURE__ */ jsx(ReactMarkdown, { components: allComponents, remarkPlugins: markdownPlugins, children: content }) });
149
158
  }
150
159
  export {
151
160
  MDXContent
@@ -7,7 +7,6 @@ import { cn } from "../../lib/utils";
7
7
  import { Button } from "../button";
8
8
  import { useSidebar } from "../sidebar-provider";
9
9
  import { ThemeToggle } from "../theme-toggle";
10
- import { useMobile } from "./use-mobile";
11
10
  const EMPTY_NAV_ITEMS = [];
12
11
  function NavbarSaas({
13
12
  brand,
@@ -17,13 +16,13 @@ function NavbarSaas({
17
16
  }) {
18
17
  const pathname = usePathname();
19
18
  const { open, setOpen } = useSidebar();
20
- const isMobile = useMobile();
21
19
  return /* @__PURE__ */ jsx("header", { className: "sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 shrink-0", children: /* @__PURE__ */ jsx("div", { className: "w-full", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex h-16 items-center justify-between px-4", children: [
22
20
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
23
- showMobileMenu && isMobile ? /* @__PURE__ */ jsx(
21
+ showMobileMenu ? /* @__PURE__ */ jsx(
24
22
  Button,
25
23
  {
26
- className: "lg:hidden",
24
+ "aria-expanded": open,
25
+ "aria-label": "Toggle sidebar",
27
26
  "data-testid": "navbar-saas-mobile-trigger",
28
27
  onClick: () => {
29
28
  setOpen(!open);
@@ -7,7 +7,7 @@ function getNumberTickerFormatter(locale, formatOptions) {
7
7
  const key = `${locale ?? ""}|${formatOptions ? JSON.stringify(formatOptions) : ""}`;
8
8
  let formatter = NUMBER_FORMATTER_CACHE.get(key);
9
9
  if (!formatter) {
10
- formatter = new Intl.NumberFormat(locale, formatOptions);
10
+ formatter = Intl.NumberFormat(locale, formatOptions);
11
11
  NUMBER_FORMATTER_CACHE.set(key, formatter);
12
12
  }
13
13
  return formatter;
@@ -10,7 +10,10 @@ import {
10
10
  CardHeader,
11
11
  CardTitle
12
12
  } from "../card";
13
- import { CHECKLIST_PROGRESS_EVENT } from "../checklist";
13
+ import {
14
+ CHECKLIST_PROGRESS_EVENT,
15
+ parseChecklistStorageValue
16
+ } from "../checklist";
14
17
  import { ProgressBar } from "../progress-bar";
15
18
  const ProgressTrackerContext = React.createContext(null);
16
19
  function clampPercentage(value) {
@@ -41,15 +44,13 @@ function readPersistedChecklistItems(persistKey) {
41
44
  try {
42
45
  const saved = localStorage.getItem(`checklist:${persistKey}`);
43
46
  if (!saved) return [];
44
- const parsed = JSON.parse(saved);
45
- return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
47
+ return parseChecklistStorageValue(saved);
46
48
  } catch {
47
49
  return [];
48
50
  }
49
51
  }
50
52
  function areStringArraysEqual(left, right) {
51
- if (left.length !== right.length) return false;
52
- return left.every((value, index) => value === right[index]);
53
+ return left.length === right.length && left.every((value, index) => value === right[index]);
53
54
  }
54
55
  function getChecklistPersistKey(event) {
55
56
  if (!(event instanceof CustomEvent)) return null;
@@ -79,14 +80,16 @@ function useChecklistProgress(checklistItems = [], persistKey) {
79
80
  const [persistedIds, setPersistedIds] = React.useState(
80
81
  () => readPersistedChecklistItems(persistKey)
81
82
  );
83
+ const [trackedPersistKey, setTrackedPersistKey] = React.useState(persistKey);
82
84
  const setPersistedIdsIfChanged = React.useCallback((nextIds) => {
83
85
  setPersistedIds(
84
86
  (currentIds) => areStringArraysEqual(currentIds, nextIds) ? currentIds : nextIds
85
87
  );
86
88
  }, []);
87
- React.useEffect(() => {
89
+ if (trackedPersistKey !== persistKey) {
90
+ setTrackedPersistKey(persistKey);
88
91
  setPersistedIdsIfChanged(readPersistedChecklistItems(persistKey));
89
- }, [persistKey, setPersistedIdsIfChanged]);
92
+ }
90
93
  React.useEffect(() => {
91
94
  if (!persistKey || typeof window === "undefined") return;
92
95
  const sync = (event) => {
@@ -162,7 +165,12 @@ function ProgressTrackerOverview({
162
165
  }) {
163
166
  const { modules, overallProgress, streak, title } = useProgressTrackerContext();
164
167
  const trackedPersistKeys = React.useMemo(
165
- () => modules.map((module) => module.persistKey).filter(Boolean),
168
+ () => modules.reduce((keys, module) => {
169
+ if (module.persistKey) {
170
+ keys.push(module.persistKey);
171
+ }
172
+ return keys;
173
+ }, []),
166
174
  [modules]
167
175
  );
168
176
  const [, forceChecklistRefresh] = React.useState(0);