@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.
- package/CHANGELOG.md +1 -0
- package/dist/components/animated-text/animated-text.js +6 -3
- package/dist/components/auto-reload/auto-reload.js +1 -1
- package/dist/components/carousel/carousel.js +14 -6
- package/dist/components/checklist/checklist.js +31 -6
- package/dist/components/checklist/index.js +8 -2
- package/dist/components/combobox/combobox.js +0 -5
- package/dist/components/completion-dialog/completion-dialog.js +14 -9
- package/dist/components/content-intro/content-intro.js +12 -11
- package/dist/components/conversation-thread/conversation-thread.js +3 -3
- package/dist/components/date-picker/date-picker.js +0 -5
- package/dist/components/file-upload/file-upload.js +0 -5
- package/dist/components/filter-bar/filter-bar.js +6 -6
- package/dist/components/gantt-chart/gantt-chart.js +8 -7
- package/dist/components/globe-3d/globe-3d.js +16 -4
- package/dist/components/interactive-timeline/interactive-timeline.js +6 -3
- package/dist/components/lang-provider/lang-provider.js +3 -3
- package/dist/components/live-feed/live-feed.js +1 -1
- package/dist/components/map-2d/map-2d.js +1 -1
- package/dist/components/map-timeline/map-timeline.js +1 -1
- package/dist/components/mdx-content/mdx-content.js +14 -5
- package/dist/components/navbar-saas/navbar-saas.js +3 -4
- package/dist/components/number-ticker/number-ticker.js +1 -1
- package/dist/components/progress-tracker/progress-tracker.js +16 -8
- package/dist/components/search-dialog/search-dialog.js +483 -50
- package/dist/components/sidebar/sidebar.js +11 -7
- package/dist/components/social-fab/social-fab.js +4 -4
- package/dist/components/spinner/spinner.js +1 -1
- package/dist/components/spinner/unicode-spinner.js +4 -2
- package/dist/components/stat-card/stat-card.js +1 -1
- package/dist/components/status-board/status-board.js +10 -4
- package/dist/components/tabs/tabs.js +26 -8
- package/dist/components/tags-input/tags-input.js +11 -3
- package/dist/components/terminal/terminal.js +10 -2
- package/dist/components/theme-toggle/theme-toggle.js +2 -2
- package/dist/components/thinking-block/thinking-block.js +2 -2
- package/dist/components/tldr-section/tldr-section.js +9 -7
- package/dist/components/transaction-list/transaction-list.js +2 -2
- package/dist/components/tutorial-complete/tutorial-complete.js +1 -1
- package/dist/components/tutorial-intro-content/tutorial-intro-content.js +1 -1
- package/dist/components/tutorial-mdx/tutorial-mdx.js +1 -1
- package/dist/components/world-clock-bar/world-clock-bar.js +2 -2
- package/dist/index.d.ts +19 -6
- 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 =
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
85
|
+
notifySelection(api);
|
|
78
86
|
});
|
|
79
87
|
return () => {
|
|
80
|
-
api?.off("select",
|
|
81
|
-
api?.off("reInit",
|
|
88
|
+
api?.off("select", notifySelection);
|
|
89
|
+
api?.off("reInit", notifySelection);
|
|
82
90
|
cancelAnimationFrame(rafId);
|
|
83
91
|
};
|
|
84
|
-
}, [api
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
102
|
-
|
|
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
|
-
|
|
123
|
-
);
|
|
124
|
+
};
|
|
125
|
+
}, [cancelShortcut, confirmShortcut, isOpen, onCancel, onClose, onConfirm]);
|
|
124
126
|
useEffect(() => {
|
|
125
127
|
if (!isOpen) return;
|
|
126
|
-
|
|
128
|
+
const onDocumentKeyDown = (event) => {
|
|
129
|
+
keyDownHandlerRef.current(event);
|
|
130
|
+
};
|
|
131
|
+
document.addEventListener("keydown", onDocumentKeyDown, true);
|
|
127
132
|
return () => {
|
|
128
|
-
document.removeEventListener("keydown",
|
|
133
|
+
document.removeEventListener("keydown", onDocumentKeyDown, true);
|
|
129
134
|
};
|
|
130
|
-
}, [isOpen
|
|
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,
|
|
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
|
|
29
|
-
|
|
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
|
-
|
|
36
|
+
onStartRef.current();
|
|
33
37
|
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
38
|
+
};
|
|
39
|
+
document.addEventListener("keydown", onDocumentKeyDown);
|
|
39
40
|
return () => {
|
|
40
|
-
document.removeEventListener("keydown",
|
|
41
|
+
document.removeEventListener("keydown", onDocumentKeyDown);
|
|
41
42
|
};
|
|
42
|
-
}, [
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
|
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,
|
|
163
|
+
const [isPending, startTransition] = useTransition();
|
|
164
164
|
const mergedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
165
165
|
const handleDifficultyChange = useCallback(
|
|
166
166
|
(difficulty) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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 =
|
|
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 }).
|
|
96
|
+
return Array.from({ length: tickCount + 1 }).reduce((ticks, _, index) => {
|
|
97
97
|
const day = index * stepDays;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
offset: day
|
|
101
|
-
}
|
|
102
|
-
|
|
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.
|
|
165
|
-
(state,
|
|
166
|
-
|
|
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].
|
|
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
|
-
() =>
|
|
611
|
-
|
|
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
|
-
}, [
|
|
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
|
|
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
|
|
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-
|
|
93
|
-
"prose-blockquote:border-l
|
|
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(
|
|
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
|
|
21
|
+
showMobileMenu ? /* @__PURE__ */ jsx(
|
|
24
22
|
Button,
|
|
25
23
|
{
|
|
26
|
-
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
+
if (trackedPersistKey !== persistKey) {
|
|
90
|
+
setTrackedPersistKey(persistKey);
|
|
88
91
|
setPersistedIdsIfChanged(readPersistedChecklistItems(persistKey));
|
|
89
|
-
}
|
|
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.
|
|
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);
|