veslx 0.1.65 → 0.1.67

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/README.md +0 -16
  2. package/dist/client/components/mdx-components.js +0 -14
  3. package/dist/client/components/mdx-components.js.map +1 -1
  4. package/dist/client/components/post-list-item.js +13 -8
  5. package/dist/client/components/post-list-item.js.map +1 -1
  6. package/dist/client/components/post-list.js +6 -1
  7. package/dist/client/components/post-list.js.map +1 -1
  8. package/dist/client/lib/content-classification.js +11 -2
  9. package/dist/client/lib/content-classification.js.map +1 -1
  10. package/dist/client/plugin/src/client.js +7 -40
  11. package/dist/client/plugin/src/client.js.map +1 -1
  12. package/dist/plugin/src/client.js +7 -0
  13. package/dist/plugin/src/plugin.js +5 -2
  14. package/package.json +1 -1
  15. package/plugin/src/client.tsx +10 -0
  16. package/plugin/src/plugin.ts +5 -2
  17. package/src/components/index.ts +0 -3
  18. package/src/components/mdx-components.tsx +0 -21
  19. package/src/components/post-list-item.tsx +17 -10
  20. package/src/components/post-list.tsx +8 -2
  21. package/src/lib/content-classification.ts +12 -2
  22. package/dist/client/components/parameter-badge.js +0 -48
  23. package/dist/client/components/parameter-badge.js.map +0 -1
  24. package/dist/client/components/parameter-table.js +0 -216
  25. package/dist/client/components/parameter-table.js.map +0 -1
  26. package/dist/client/components/slides/figure-slide.js +0 -14
  27. package/dist/client/components/slides/figure-slide.js.map +0 -1
  28. package/dist/client/components/slides/hero-slide.js +0 -21
  29. package/dist/client/components/slides/hero-slide.js.map +0 -1
  30. package/dist/client/components/slides/slide-outline.js +0 -28
  31. package/dist/client/components/slides/slide-outline.js.map +0 -1
  32. package/dist/client/components/slides/text-slide.js +0 -18
  33. package/dist/client/components/slides/text-slide.js.map +0 -1
  34. package/dist/client/components/veslx-cat.js +0 -40
  35. package/dist/client/components/veslx-cat.js.map +0 -1
  36. package/dist/client/lib/parameter-utils.js +0 -108
  37. package/dist/client/lib/parameter-utils.js.map +0 -1
  38. package/src/components/parameter-badge.tsx +0 -78
  39. package/src/components/parameter-table.tsx +0 -369
  40. package/src/components/slides/figure-slide.tsx +0 -16
  41. package/src/components/slides/hero-slide.tsx +0 -34
  42. package/src/components/slides/slide-outline.tsx +0 -38
  43. package/src/components/slides/text-slide.tsx +0 -35
  44. package/src/components/veslx-cat.tsx +0 -73
@@ -1,108 +0,0 @@
1
- import { load } from "js-yaml";
2
- function extractPath(data, path) {
3
- if (!path || path === ".") return data;
4
- const cleanPath = path.startsWith(".") ? path.slice(1) : path;
5
- if (!cleanPath) return data;
6
- const segments = [];
7
- let current = "";
8
- let i = 0;
9
- while (i < cleanPath.length) {
10
- const char = cleanPath[i];
11
- if (char === ".") {
12
- if (current) {
13
- segments.push({ type: "key", value: current });
14
- current = "";
15
- }
16
- i++;
17
- } else if (char === "[") {
18
- if (current) {
19
- segments.push({ type: "key", value: current });
20
- current = "";
21
- }
22
- const closeIdx = cleanPath.indexOf("]", i);
23
- if (closeIdx === -1) return void 0;
24
- const inner = cleanPath.slice(i + 1, closeIdx);
25
- if (inner === "") {
26
- segments.push({ type: "all", value: 0 });
27
- } else {
28
- const idx = parseInt(inner, 10);
29
- if (isNaN(idx)) return void 0;
30
- segments.push({ type: "index", value: idx });
31
- }
32
- i = closeIdx + 1;
33
- } else {
34
- current += char;
35
- i++;
36
- }
37
- }
38
- if (current) {
39
- segments.push({ type: "key", value: current });
40
- }
41
- let result = data;
42
- for (const seg of segments) {
43
- if (result === null || result === void 0) return void 0;
44
- if (seg.type === "key") {
45
- if (typeof result !== "object" || Array.isArray(result)) return void 0;
46
- result = result[seg.value];
47
- } else if (seg.type === "index") {
48
- if (!Array.isArray(result)) return void 0;
49
- result = result[seg.value];
50
- } else if (seg.type === "all") {
51
- if (!Array.isArray(result)) return void 0;
52
- }
53
- }
54
- return result;
55
- }
56
- function getValueType(value) {
57
- if (value === null) return "null";
58
- if (Array.isArray(value)) return "array";
59
- if (typeof value === "object") return "object";
60
- if (typeof value === "boolean") return "boolean";
61
- if (typeof value === "number") return "number";
62
- return "string";
63
- }
64
- function formatNumber(value) {
65
- if (Math.abs(value) < 1e-4 && value !== 0) return value.toExponential(1);
66
- if (Math.abs(value) >= 1e4) return value.toExponential(1);
67
- if (Number.isInteger(value)) return value.toString();
68
- return value.toFixed(3);
69
- }
70
- function formatValue(value) {
71
- if (value === null) return "null";
72
- if (typeof value === "boolean") return value ? "true" : "false";
73
- if (typeof value === "number") return formatNumber(value);
74
- if (Array.isArray(value)) return `[${value.length}]`;
75
- if (typeof value === "object") return "{...}";
76
- return String(value);
77
- }
78
- function parseConfigFile(content, path) {
79
- if (path.endsWith(".yaml") || path.endsWith(".yml")) {
80
- try {
81
- return load(content);
82
- } catch {
83
- return null;
84
- }
85
- }
86
- if (path.endsWith(".json")) {
87
- try {
88
- return JSON.parse(content);
89
- } catch {
90
- return null;
91
- }
92
- }
93
- return null;
94
- }
95
- function deriveLabelFromPath(keyPath) {
96
- const cleanPath = keyPath.startsWith(".") ? keyPath.slice(1) : keyPath;
97
- const parts = cleanPath.split(".");
98
- return parts[parts.length - 1].replace(/\[\d+\]/g, "");
99
- }
100
- export {
101
- deriveLabelFromPath,
102
- extractPath,
103
- formatNumber,
104
- formatValue,
105
- getValueType,
106
- parseConfigFile
107
- };
108
- //# sourceMappingURL=parameter-utils.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"parameter-utils.js","sources":["../../../src/lib/parameter-utils.ts"],"sourcesContent":["import { load } from \"js-yaml\";\n\nexport type ParameterValue = string | number | boolean | null | ParameterValue[] | { [key: string]: ParameterValue };\n\n/**\n * Extract a value from nested data using a jq-like path.\n * Supports:\n * - .foo.bar → nested keys\n * - .foo[0] → array index\n * - .foo[] → all array elements (returns array)\n */\nexport function extractPath(data: ParameterValue, path: string): ParameterValue | undefined {\n if (!path || path === \".\") return data;\n\n const cleanPath = path.startsWith(\".\") ? path.slice(1) : path;\n if (!cleanPath) return data;\n\n const segments: Array<{ type: \"key\" | \"index\" | \"all\"; value: string | number }> = [];\n let current = \"\";\n let i = 0;\n\n while (i < cleanPath.length) {\n const char = cleanPath[i];\n\n if (char === \".\") {\n if (current) {\n segments.push({ type: \"key\", value: current });\n current = \"\";\n }\n i++;\n } else if (char === \"[\") {\n if (current) {\n segments.push({ type: \"key\", value: current });\n current = \"\";\n }\n const closeIdx = cleanPath.indexOf(\"]\", i);\n if (closeIdx === -1) return undefined;\n const inner = cleanPath.slice(i + 1, closeIdx);\n if (inner === \"\") {\n segments.push({ type: \"all\", value: 0 });\n } else {\n const idx = parseInt(inner, 10);\n if (isNaN(idx)) return undefined;\n segments.push({ type: \"index\", value: idx });\n }\n i = closeIdx + 1;\n } else {\n current += char;\n i++;\n }\n }\n if (current) {\n segments.push({ type: \"key\", value: current });\n }\n\n let result: ParameterValue | undefined = data;\n\n for (const seg of segments) {\n if (result === null || result === undefined) return undefined;\n\n if (seg.type === \"key\") {\n if (typeof result !== \"object\" || Array.isArray(result)) return undefined;\n result = (result as Record<string, ParameterValue>)[seg.value as string];\n } else if (seg.type === \"index\") {\n if (!Array.isArray(result)) return undefined;\n result = result[seg.value as number];\n } else if (seg.type === \"all\") {\n if (!Array.isArray(result)) return undefined;\n // Return the array itself for further processing\n }\n }\n\n return result;\n}\n\nexport function getValueType(value: ParameterValue): \"string\" | \"number\" | \"boolean\" | \"null\" | \"array\" | \"object\" {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n if (typeof value === \"object\") return \"object\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"number\") return \"number\";\n return \"string\";\n}\n\nexport function formatNumber(value: number): string {\n if (Math.abs(value) < 0.0001 && value !== 0) return value.toExponential(1);\n if (Math.abs(value) >= 10000) return value.toExponential(1);\n if (Number.isInteger(value)) return value.toString();\n return value.toFixed(3);\n}\n\nexport function formatValue(value: ParameterValue): string {\n if (value === null) return \"null\";\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (typeof value === \"number\") return formatNumber(value);\n if (Array.isArray(value)) return `[${value.length}]`;\n if (typeof value === \"object\") return \"{...}\";\n return String(value);\n}\n\n/**\n * Parse YAML or JSON content based on file extension.\n */\nexport function parseConfigFile(content: string, path: string): Record<string, ParameterValue> | null {\n if (path.endsWith(\".yaml\") || path.endsWith(\".yml\")) {\n try {\n return load(content) as Record<string, ParameterValue>;\n } catch {\n return null;\n }\n }\n\n if (path.endsWith(\".json\")) {\n try {\n return JSON.parse(content) as Record<string, ParameterValue>;\n } catch {\n return null;\n }\n }\n\n return null;\n}\n\n/**\n * Derive a label from a jq-like keyPath.\n * E.g., \".base.N_E\" → \"N_E\"\n */\nexport function deriveLabelFromPath(keyPath: string): string {\n const cleanPath = keyPath.startsWith(\".\") ? keyPath.slice(1) : keyPath;\n const parts = cleanPath.split(\".\");\n return parts[parts.length - 1].replace(/\\[\\d+\\]/g, \"\");\n}\n"],"names":[],"mappings":"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"}
@@ -1,78 +0,0 @@
1
- import { useFileContent } from "../../plugin/src/client";
2
- import { useMemo } from "react";
3
- import { cn } from "@/lib/utils";
4
- import {
5
- type ParameterValue,
6
- extractPath,
7
- getValueType,
8
- formatValue,
9
- parseConfigFile,
10
- deriveLabelFromPath,
11
- } from "@/lib/parameter-utils";
12
-
13
- interface ParameterBadgeProps {
14
- /** Path to the YAML or JSON file */
15
- path: string;
16
- /** jq-like path to the value (e.g., ".base.N_E") */
17
- keyPath: string;
18
- /** Optional label override (defaults to last segment of keyPath) */
19
- label?: string;
20
- /** Optional unit suffix (e.g., "ms", "Hz") */
21
- unit?: string;
22
- }
23
-
24
- export function ParameterBadge({ path, keyPath, label, unit }: ParameterBadgeProps) {
25
- const { content, loading, error } = useFileContent(path);
26
-
27
- const { value, displayLabel } = useMemo(() => {
28
- if (!content) return { value: undefined, displayLabel: "" };
29
-
30
- const data = parseConfigFile(content, path);
31
- if (!data) return { value: undefined, displayLabel: "" };
32
-
33
- const extracted = extractPath(data, keyPath);
34
- const derivedLabel = label || deriveLabelFromPath(keyPath);
35
-
36
- return { value: extracted, displayLabel: derivedLabel };
37
- }, [content, path, keyPath, label]);
38
-
39
- if (loading) {
40
- return (
41
- <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-muted/50 border border-border/50">
42
- <span className="w-2 h-2 border border-muted-foreground/40 border-t-transparent rounded-full animate-spin" />
43
- </span>
44
- );
45
- }
46
-
47
- if (error || value === undefined) {
48
- return (
49
- <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-destructive/10 border border-destructive/30">
50
- <span className="text-[10px] font-mono text-destructive">—</span>
51
- </span>
52
- );
53
- }
54
-
55
- const type = getValueType(value);
56
- const formattedValue = formatValue(value);
57
-
58
- return (
59
- <span className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md font-mono text-[11px]">
60
- <span className="text-muted-foreground">{displayLabel}</span>
61
- <span className="text-muted-foreground/40">=</span>
62
- <span
63
- className={cn(
64
- "font-medium tabular-nums",
65
- type === "number" && "text-foreground",
66
- type === "string" && "text-amber-600 dark:text-amber-500",
67
- type === "boolean" && "text-cyan-600 dark:text-cyan-500",
68
- type === "null" && "text-muted-foreground/50",
69
- type === "array" && "text-purple-600 dark:text-purple-400",
70
- type === "object" && "text-purple-600 dark:text-purple-400"
71
- )}
72
- >
73
- {type === "string" ? `"${formattedValue}"` : formattedValue}
74
- </span>
75
- {unit && <span className="text-muted-foreground/60">{unit}</span>}
76
- </span>
77
- );
78
- }
@@ -1,369 +0,0 @@
1
- import { useFileContent, useDirectory } from "../../plugin/src/client";
2
- import { useMemo, useRef, useEffect } from "react";
3
- import { useParams } from "react-router-dom";
4
- import { cn } from "@/lib/utils";
5
- import { minimatch } from "minimatch";
6
- import {
7
- type ParameterValue,
8
- extractPath,
9
- getValueType,
10
- formatValue,
11
- parseConfigFile,
12
- } from "@/lib/parameter-utils";
13
- import { FileEntry, DirectoryEntry } from "../../plugin/src/lib";
14
-
15
- /**
16
- * Hook to prevent horizontal scroll from triggering browser back/forward gestures.
17
- */
18
- function usePreventSwipeNavigation(ref: React.RefObject<HTMLElement | null>) {
19
- useEffect(() => {
20
- const el = ref.current;
21
- if (!el) return;
22
-
23
- const handleWheel = (e: WheelEvent) => {
24
- if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;
25
-
26
- const { scrollLeft, scrollWidth, clientWidth } = el;
27
- const atLeftEdge = scrollLeft <= 0;
28
- const atRightEdge = scrollLeft + clientWidth >= scrollWidth - 1;
29
-
30
- if ((atLeftEdge && e.deltaX < 0) || (atRightEdge && e.deltaX > 0)) {
31
- e.preventDefault();
32
- }
33
- };
34
-
35
- el.addEventListener('wheel', handleWheel, { passive: false });
36
- return () => el.removeEventListener('wheel', handleWheel);
37
- }, [ref]);
38
- }
39
-
40
- // Check if a path contains glob patterns
41
- function isGlobPattern(path: string): boolean {
42
- return path.includes('*') || path.includes('?') || path.includes('[');
43
- }
44
-
45
- // Recursively collect all config files from a directory tree
46
- function collectAllConfigFiles(entry: DirectoryEntry | FileEntry): FileEntry[] {
47
- if (entry.type === "file") {
48
- if (entry.name.match(/\.(yaml|yml|json)$/i)) {
49
- return [entry];
50
- }
51
- return [];
52
- }
53
- const files: FileEntry[] = [];
54
- for (const child of entry.children || []) {
55
- files.push(...collectAllConfigFiles(child));
56
- }
57
- return files;
58
- }
59
-
60
- // Sort paths numerically
61
- function sortPathsNumerically(paths: string[]): void {
62
- paths.sort((a, b) => {
63
- const nums = (s: string) => (s.match(/\d+/g) || []).map(Number);
64
- const na = nums(a);
65
- const nb = nums(b);
66
- const len = Math.max(na.length, nb.length);
67
- for (let i = 0; i < len; i++) {
68
- const diff = (na[i] ?? 0) - (nb[i] ?? 0);
69
- if (diff !== 0) return diff;
70
- }
71
- return a.localeCompare(b);
72
- });
73
- }
74
-
75
- interface SingleParameterTableProps {
76
- /** Path to the YAML or JSON file */
77
- path: string;
78
- /**
79
- * Required array of key/label pairs to render as a markdown-style table.
80
- * Each key is a jq-like path (e.g., ".base.dt").
81
- */
82
- pairs: Array<{ key: string; label?: string }>;
83
- /** Optional label to show above the table */
84
- label?: string;
85
- /** Whether to include vertical margin (default true) */
86
- withMargin?: boolean;
87
- /**
88
- * Whether this table should manage its own horizontal scrolling.
89
- * Set false when the parent already provides a single shared scrollbar.
90
- */
91
- scrollable?: boolean;
92
- /** Compact mode for dense rendering */
93
- compact?: boolean;
94
- }
95
-
96
- interface ParameterTableProps {
97
- /** Path to the YAML or JSON file, supports glob patterns like "*.yaml" */
98
- path: string;
99
- /**
100
- * Required array of key/label pairs to render Complete table.
101
- */
102
- pairs: Array<{ key: string; label?: string }>;
103
- /** Compact mode for dense rendering */
104
- compact?: boolean;
105
- }
106
-
107
- function SingleParameterTable({
108
- path,
109
- pairs,
110
- label,
111
- withMargin = true,
112
- scrollable = true,
113
- compact = false,
114
- }: SingleParameterTableProps) {
115
- const { content, loading, error } = useFileContent(path);
116
-
117
- if (!pairs || pairs.length === 0) {
118
- return (
119
- <div className={cn("p-3 rounded border border-border/50 bg-card/30", withMargin && "my-6")}>
120
- <p className="text-[11px] font-mono text-muted-foreground">
121
- pairs is required
122
- </p>
123
- </div>
124
- );
125
- }
126
-
127
- const { parsed, parseError } = useMemo(() => {
128
- if (!content) return { parsed: null, parseError: 'no content' };
129
-
130
- const data = parseConfigFile(content, path);
131
- if (!data) {
132
- // Check why parsing failed
133
- if (!path.match(/\.(yaml|yml|json)$/i)) {
134
- return { parsed: null, parseError: `unsupported file type` };
135
- }
136
- // Check if content looks like HTML (404 page)
137
- if (content.trim().startsWith('<!') || content.trim().startsWith('<html')) {
138
- return { parsed: null, parseError: `file not found` };
139
- }
140
- return { parsed: null, parseError: `invalid ${path.split('.').pop()} syntax` };
141
- }
142
-
143
- return { parsed: data, parseError: null };
144
- }, [content, path]);
145
-
146
- if (loading) {
147
- return (
148
- <div className="my-6 p-4 rounded border border-border/50 bg-card/30">
149
- <div className="flex items-center gap-2 text-muted-foreground/60">
150
- <div className="w-3 h-3 border border-current border-t-transparent rounded-full animate-spin" />
151
- <span className="text-[11px] font-mono">loading parameters...</span>
152
- </div>
153
- </div>
154
- );
155
- }
156
-
157
- if (error) {
158
- return (
159
- <div className="my-6 p-3 rounded border border-destructive/30 bg-destructive/5">
160
- <p className="text-[11px] font-mono text-destructive">{error}</p>
161
- </div>
162
- );
163
- }
164
-
165
- if (!parsed) {
166
- return (
167
- <div className={cn("p-3 rounded border border-border/50 bg-card/30", withMargin && "my-6")}>
168
- <p className="text-[11px] font-mono text-muted-foreground">
169
- {label && <span className="text-foreground/60">{label}: </span>}
170
- {parseError || 'unable to parse'}
171
- </p>
172
- </div>
173
- );
174
- }
175
-
176
- const renderPairsTable = () => {
177
- return (
178
- <div className={cn(
179
- "border border-border rounded-md",
180
- scrollable ? "overflow-x-auto" : "overflow-x-hidden"
181
- )}>
182
- <table className={cn(
183
- "w-full border-collapse",
184
- compact ? "text-[11px]" : "text-sm"
185
- )}>
186
- <thead className="bg-muted/50">
187
- <tr className="border-b border-border last:border-b-0">
188
- <th className={cn(
189
- "text-left text-xs font-medium text-muted-foreground uppercase tracking-wider",
190
- compact ? "px-3 py-2" : "px-4 py-3"
191
- )}>
192
- Parameter
193
- </th>
194
- <th className={cn(
195
- "text-left text-xs font-medium text-muted-foreground uppercase tracking-wider",
196
- compact ? "px-3 py-2" : "px-4 py-3"
197
- )}>
198
- Value
199
- </th>
200
- </tr>
201
- </thead>
202
- <tbody>
203
- {pairs.map(({ key, label: rowLabel }) => {
204
- const value = extractPath(parsed, key);
205
- const type = value === undefined ? "missing" : getValueType(value);
206
- const displayValue = value === undefined
207
- ? "—"
208
- : type === "string"
209
- ? `"${formatValue(value)}"`
210
- : formatValue(value);
211
-
212
- return (
213
- <tr key={key} className="border-b border-border last:border-b-0">
214
- <td className={cn(
215
- "align-top",
216
- compact ? "px-3 py-2" : "px-4 py-3"
217
- )}>{rowLabel || key}</td>
218
- <td
219
- className={cn(
220
- "align-top",
221
- compact ? "px-3 py-2" : "px-4 py-3",
222
- type === "missing" && "text-muted-foreground"
223
- )}
224
- >
225
- {displayValue}
226
- </td>
227
- </tr>
228
- );
229
- })}
230
- </tbody>
231
- </table>
232
- </div>
233
- );
234
- };
235
-
236
- return (
237
- <div className={cn("not-prose", withMargin && "my-6")}>
238
- {label && (
239
- <div className="text-[11px] font-mono text-muted-foreground mb-1.5 truncate" title={label}>
240
- {label}
241
- </div>
242
- )}
243
- {renderPairsTable()}
244
- </div>
245
- );
246
- }
247
-
248
- /**
249
- * ParameterTable component that displays YAML/JSON config files.
250
- * Supports glob patterns in the path prop to show multiple files.
251
- */
252
- export function ParameterTable({ path, pairs, compact = false }: ParameterTableProps) {
253
- const { "*": routePath = "" } = useParams();
254
-
255
- if (!pairs || pairs.length === 0) {
256
- return (
257
- <div className="my-6 p-3 rounded border border-border/50 bg-card/30">
258
- <p className="text-[11px] font-mono text-muted-foreground">pairs is required</p>
259
- </div>
260
- );
261
- }
262
-
263
- // Get current directory from route
264
- const currentDir = routePath
265
- .replace(/\/?[^/]+\.mdx$/i, "")
266
- .replace(/\/$/, "")
267
- || ".";
268
-
269
- // Resolve relative paths
270
- let resolvedPath = path;
271
- if (path?.startsWith("./")) {
272
- const relativePart = path.slice(2);
273
- resolvedPath = currentDir === "." ? relativePart : `${currentDir}/${relativePart}`;
274
- } else if (path && !path.startsWith("/") && !path.includes("/") && !isGlobPattern(path)) {
275
- resolvedPath = currentDir === "." ? path : `${currentDir}/${path}`;
276
- }
277
-
278
- // Check if this is a glob pattern
279
- const hasGlob = isGlobPattern(resolvedPath);
280
-
281
- // For glob patterns, get the base directory (directory containing the glob pattern)
282
- const baseDir = useMemo(() => {
283
- if (!hasGlob) return null;
284
- // Get everything before the first glob character
285
- const beforeGlob = resolvedPath.split(/[*?\[]/, 1)[0];
286
- // Extract directory portion (everything up to the last slash)
287
- const lastSlash = beforeGlob.lastIndexOf('/');
288
- if (lastSlash === -1) return ".";
289
- return beforeGlob.slice(0, lastSlash) || ".";
290
- }, [hasGlob, resolvedPath]);
291
-
292
- const { directory } = useDirectory(baseDir || ".");
293
-
294
- // Find matching files for glob patterns
295
- const matchingPaths = useMemo(() => {
296
- if (!hasGlob || !directory) return [];
297
-
298
- const allFiles = collectAllConfigFiles(directory);
299
- const paths = allFiles
300
- .map(f => f.path)
301
- .filter(p => minimatch(p, resolvedPath, { matchBase: true }));
302
-
303
- sortPathsNumerically(paths);
304
-
305
- return paths;
306
- }, [hasGlob, directory, resolvedPath, path, baseDir]);
307
-
308
- // If not a glob pattern, just render the single table
309
- if (!hasGlob) {
310
- return <SingleParameterTable path={resolvedPath} pairs={pairs} compact={compact} />;
311
- }
312
-
313
- // Loading state for glob patterns
314
- if (!directory) {
315
- return (
316
- <div className="my-6 p-4 rounded border border-border/50 bg-card/30">
317
- <div className="flex items-center gap-2 text-muted-foreground/60">
318
- <div className="w-3 h-3 border border-current border-t-transparent rounded-full animate-spin" />
319
- <span className="text-[11px] font-mono">loading parameters...</span>
320
- </div>
321
- </div>
322
- );
323
- }
324
-
325
- // No matches
326
- if (matchingPaths.length === 0) {
327
- return (
328
- <div className="my-6 p-3 rounded border border-border/50 bg-card/30">
329
- <p className="text-[11px] font-mono text-muted-foreground">
330
- no files matching: {resolvedPath}
331
- <br />
332
- <span className="text-muted-foreground/50">(base dir: {baseDir}, original: {path})</span>
333
- </p>
334
- </div>
335
- );
336
- }
337
-
338
- const scrollRef = useRef<HTMLDivElement>(null);
339
- usePreventSwipeNavigation(scrollRef);
340
-
341
- // Breakout width based on count
342
- const count = matchingPaths.length;
343
- // Use translate centering (instead of negative margins) to avoid creating a tiny
344
- // page-level horizontal scrollbar while still "breaking out" of prose width.
345
- const breakoutClass = count >= 4
346
- ? 'relative left-1/2 w-[96vw] -translate-x-1/2'
347
- : count >= 2
348
- ? 'relative left-1/2 w-[75vw] -translate-x-1/2'
349
- : '';
350
-
351
- return (
352
- <div className={`my-6 ${breakoutClass} overflow-x-hidden`}>
353
- <div ref={scrollRef} className="flex gap-4 overflow-x-auto overscroll-x-contain pb-2">
354
- {matchingPaths.map((filePath) => (
355
- <div key={filePath} className="flex-none w-[280px]">
356
- <SingleParameterTable
357
- path={filePath}
358
- pairs={pairs}
359
- label={filePath.split('/').pop() || filePath}
360
- withMargin={false}
361
- scrollable={false}
362
- compact={compact}
363
- />
364
- </div>
365
- ))}
366
- </div>
367
- </div>
368
- );
369
- }
@@ -1,16 +0,0 @@
1
-
2
-
3
- export function FigureSlide({
4
- title,
5
- src,
6
- }: {
7
- title: string;
8
- src: string;
9
- }) {
10
- return (
11
- <div className="figure-slide">
12
- <h2>{title}</h2>
13
- <img src={src} alt={title} />
14
- </div>
15
- );
16
- }
@@ -1,34 +0,0 @@
1
-
2
- export function HeroSlide({
3
- title,
4
- subtitle,
5
- author,
6
- date,
7
- }: {
8
- title: string;
9
- subtitle?: string;
10
- author?: string;
11
- date?: string;
12
- }) {
13
- return (
14
- <div>
15
- <h1 className="text-[clamp(2.5rem,6vw,5rem)] font-semibold leading-[1.1] tracking-[-0.02em] text-foreground text-balance">
16
- {title}
17
- </h1>
18
-
19
- {subtitle && (
20
- <p className="text-[clamp(1rem,2vw,1.5rem)] text-muted-foreground max-w-[50ch] leading-relaxed">
21
- {subtitle}
22
- </p>
23
- )}
24
-
25
- {(author || date) && (
26
- <div className="flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted-foreground mt-4">
27
- {author && <span>{author}</span>}
28
- {author && date && <span className="text-border">·</span>}
29
- {date && <span>{date}</span>}
30
- </div>
31
- )}
32
- </div>
33
- );
34
- }
@@ -1,38 +0,0 @@
1
-
2
-
3
- export function SlideOutline({
4
- children,
5
- className,
6
- size="md"
7
- }: {
8
- children?: React.ReactNode;
9
- className?: string;
10
- size?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "full";
11
- }) {
12
-
13
- const wClasses: Record<string, string> = {
14
- sm: "max-w-lg",
15
- md: "max-w-2xl",
16
- lg: "max-w-5xl",
17
- xl: "max-w-7xl",
18
- full: "max-w-full",
19
- };
20
-
21
- const wClassName = `${wClasses[size]} ${className ?? ""}`;
22
-
23
- const hClasses: Record<string, string> = {
24
- sm: "min-h-[300px]",
25
- md: "min-h-[400px]",
26
- lg: "min-h-[500px]",
27
- xl: "min-h-[600px]",
28
- full: "min-h-[600px]",
29
- };
30
-
31
- const hClassName = `${hClasses[size]} ${className ?? ""}`;
32
-
33
- return (
34
- <div className={`border rounded relative left-1/2 -translate-x-1/2 w-screen ${wClassName} ${hClassName} ${className}`}>
35
- {children}
36
- </div>
37
- );
38
- }