prev-cli 0.2.1 → 0.3.0

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/dist/cli.js CHANGED
@@ -9,13 +9,12 @@ import { createServer as createServer2, build, preview } from "vite";
9
9
  // src/vite/config.ts
10
10
  import { createLogger } from "vite";
11
11
  import react from "@vitejs/plugin-react-swc";
12
- import tailwindcss from "@tailwindcss/vite";
13
12
  import mdx from "@mdx-js/rollup";
14
13
  import remarkGfm from "remark-gfm";
15
14
  import rehypeHighlight from "rehype-highlight";
16
- import path4 from "path";
15
+ import path5 from "path";
17
16
  import { fileURLToPath as fileURLToPath2 } from "url";
18
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
17
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
19
18
 
20
19
  // src/utils/cache.ts
21
20
  import { createHash } from "crypto";
@@ -269,6 +268,72 @@ function entryPlugin(rootDir) {
269
268
  };
270
269
  }
271
270
 
271
+ // src/vite/plugins/fumadocs-plugin.ts
272
+ import path4 from "path";
273
+ import { existsSync as existsSync2 } from "fs";
274
+ function fumadocsPlugin(nodeModulesPath) {
275
+ const fumadocsCorePath = path4.join(nodeModulesPath, "fumadocs-core");
276
+ const fumadocsUiPath = path4.join(nodeModulesPath, "fumadocs-ui");
277
+ const fumadocsUiScopedPath = path4.join(nodeModulesPath, "@fumadocs/ui");
278
+ function resolveSubpath(pkgPath, subpath) {
279
+ const directPath = path4.join(pkgPath, "dist", `${subpath}.js`);
280
+ if (existsSync2(directPath)) {
281
+ return directPath;
282
+ }
283
+ const indexPath = path4.join(pkgPath, "dist", subpath, "index.js");
284
+ if (existsSync2(indexPath)) {
285
+ return indexPath;
286
+ }
287
+ const rawPath = path4.join(pkgPath, "dist", subpath);
288
+ if (existsSync2(rawPath)) {
289
+ return rawPath;
290
+ }
291
+ return null;
292
+ }
293
+ return {
294
+ name: "fumadocs-resolver",
295
+ enforce: "pre",
296
+ resolveId(id, importer) {
297
+ if (importer && (id.startsWith("./") || id.startsWith("../"))) {
298
+ if (importer.includes("fumadocs-core") || importer.includes("fumadocs-ui") || importer.includes("@fumadocs")) {
299
+ const importerDir = path4.dirname(importer);
300
+ const resolved = path4.resolve(importerDir, id);
301
+ const withExt = resolved.endsWith(".js") ? resolved : `${resolved}.js`;
302
+ if (existsSync2(withExt)) {
303
+ return withExt;
304
+ }
305
+ const indexPath = path4.join(resolved, "index.js");
306
+ if (existsSync2(indexPath)) {
307
+ return indexPath;
308
+ }
309
+ }
310
+ }
311
+ if (id.startsWith("fumadocs-core/")) {
312
+ const subpath = id.slice("fumadocs-core/".length);
313
+ const resolved = resolveSubpath(fumadocsCorePath, subpath);
314
+ if (resolved) {
315
+ return resolved;
316
+ }
317
+ }
318
+ if (id.startsWith("fumadocs-ui/")) {
319
+ const subpath = id.slice("fumadocs-ui/".length);
320
+ const resolved = resolveSubpath(fumadocsUiPath, subpath);
321
+ if (resolved) {
322
+ return resolved;
323
+ }
324
+ }
325
+ if (id.startsWith("@fumadocs/ui/")) {
326
+ const subpath = id.slice("@fumadocs/ui/".length);
327
+ const resolved = resolveSubpath(fumadocsUiScopedPath, subpath);
328
+ if (resolved) {
329
+ return resolved;
330
+ }
331
+ }
332
+ return null;
333
+ }
334
+ };
335
+ }
336
+
272
337
  // src/vite/config.ts
273
338
  function createFriendlyLogger() {
274
339
  const logger = createLogger("info", { allowClearScreen: false });
@@ -324,10 +389,10 @@ function createFriendlyLogger() {
324
389
  };
325
390
  }
326
391
  function findCliRoot2() {
327
- let dir = path4.dirname(fileURLToPath2(import.meta.url));
392
+ let dir = path5.dirname(fileURLToPath2(import.meta.url));
328
393
  for (let i = 0;i < 10; i++) {
329
- const pkgPath = path4.join(dir, "package.json");
330
- if (existsSync2(pkgPath)) {
394
+ const pkgPath = path5.join(dir, "package.json");
395
+ if (existsSync3(pkgPath)) {
331
396
  try {
332
397
  const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
333
398
  if (pkg.name === "prev-cli") {
@@ -335,24 +400,24 @@ function findCliRoot2() {
335
400
  }
336
401
  } catch {}
337
402
  }
338
- const parent = path4.dirname(dir);
403
+ const parent = path5.dirname(dir);
339
404
  if (parent === dir)
340
405
  break;
341
406
  dir = parent;
342
407
  }
343
- return path4.dirname(path4.dirname(fileURLToPath2(import.meta.url)));
408
+ return path5.dirname(path5.dirname(fileURLToPath2(import.meta.url)));
344
409
  }
345
410
  function findNodeModules(cliRoot2) {
346
- const localNodeModules = path4.join(cliRoot2, "node_modules");
347
- if (existsSync2(path4.join(localNodeModules, "react"))) {
411
+ const localNodeModules = path5.join(cliRoot2, "node_modules");
412
+ if (existsSync3(path5.join(localNodeModules, "react"))) {
348
413
  return localNodeModules;
349
414
  }
350
415
  let dir = cliRoot2;
351
416
  for (let i = 0;i < 10; i++) {
352
- const parent = path4.dirname(dir);
417
+ const parent = path5.dirname(dir);
353
418
  if (parent === dir)
354
419
  break;
355
- if (path4.basename(parent) === "node_modules" && existsSync2(path4.join(parent, "react"))) {
420
+ if (path5.basename(parent) === "node_modules" && existsSync3(path5.join(parent, "react"))) {
356
421
  return parent;
357
422
  }
358
423
  dir = parent;
@@ -361,7 +426,7 @@ function findNodeModules(cliRoot2) {
361
426
  }
362
427
  var cliRoot2 = findCliRoot2();
363
428
  var cliNodeModules = findNodeModules(cliRoot2);
364
- var srcRoot2 = path4.join(cliRoot2, "src");
429
+ var srcRoot2 = path5.join(cliRoot2, "src");
365
430
  async function createViteConfig(options) {
366
431
  const { rootDir, mode, port } = options;
367
432
  const cacheDir = await ensureCacheDir(rootDir);
@@ -372,26 +437,34 @@ async function createViteConfig(options) {
372
437
  customLogger: createFriendlyLogger(),
373
438
  logLevel: mode === "production" ? "silent" : "info",
374
439
  plugins: [
440
+ fumadocsPlugin(cliNodeModules),
375
441
  mdx({
376
442
  remarkPlugins: [remarkGfm],
377
443
  rehypePlugins: [rehypeHighlight]
378
444
  }),
379
445
  react(),
380
- tailwindcss(),
381
446
  pagesPlugin(rootDir),
382
447
  entryPlugin(rootDir)
383
448
  ],
384
449
  resolve: {
385
450
  alias: {
386
- "@prev/ui": path4.join(srcRoot2, "ui"),
387
- "@prev/theme": path4.join(srcRoot2, "theme"),
388
- react: path4.join(cliNodeModules, "react"),
389
- "react-dom": path4.join(cliNodeModules, "react-dom"),
390
- "react-router-dom": path4.join(cliNodeModules, "react-router-dom"),
391
- mermaid: path4.join(cliNodeModules, "mermaid"),
392
- dayjs: path4.join(cliNodeModules, "dayjs"),
393
- "@terrastruct/d2": path4.join(cliNodeModules, "@terrastruct/d2")
394
- }
451
+ "@prev/ui": path5.join(srcRoot2, "ui"),
452
+ "@prev/theme": path5.join(srcRoot2, "theme"),
453
+ react: path5.join(cliNodeModules, "react"),
454
+ "react-dom": path5.join(cliNodeModules, "react-dom"),
455
+ "@tanstack/react-router": path5.join(cliNodeModules, "@tanstack/react-router"),
456
+ mermaid: path5.join(cliNodeModules, "mermaid"),
457
+ dayjs: path5.join(cliNodeModules, "dayjs"),
458
+ "@terrastruct/d2": path5.join(cliNodeModules, "@terrastruct/d2")
459
+ },
460
+ dedupe: [
461
+ "react",
462
+ "react-dom",
463
+ "@tanstack/react-router",
464
+ "fumadocs-core",
465
+ "fumadocs-ui",
466
+ "@fumadocs/ui"
467
+ ]
395
468
  },
396
469
  optimizeDeps: {
397
470
  entries: [],
@@ -399,17 +472,18 @@ async function createViteConfig(options) {
399
472
  "react",
400
473
  "react-dom",
401
474
  "react-dom/client",
402
- "react-router-dom",
475
+ "@tanstack/react-router",
403
476
  "react/jsx-runtime",
404
477
  "react/jsx-dev-runtime",
405
478
  "mermaid",
406
479
  "dayjs",
407
- "@terrastruct/d2"
408
- ],
409
- exclude: [
410
- "clsx",
411
- "class-variance-authority",
412
- "tailwind-merge"
480
+ "@terrastruct/d2",
481
+ "fumadocs-core/framework",
482
+ "fumadocs-core/framework/tanstack",
483
+ "fumadocs-core/link",
484
+ "fumadocs-core/breadcrumb",
485
+ "fumadocs-ui/provider/tanstack",
486
+ "fumadocs-ui/layouts/docs"
413
487
  ]
414
488
  },
415
489
  ssr: {
@@ -427,7 +501,7 @@ async function createViteConfig(options) {
427
501
  strictPort: false
428
502
  },
429
503
  build: {
430
- outDir: path4.join(rootDir, "dist"),
504
+ outDir: path5.join(rootDir, "dist"),
431
505
  reportCompressedSize: false,
432
506
  chunkSizeWarningLimit: 1e4
433
507
  }
@@ -0,0 +1,9 @@
1
+ import type { Plugin } from 'vite';
2
+ /**
3
+ * Vite plugin to resolve fumadocs subpath exports.
4
+ *
5
+ * Fumadocs uses Node.js subpath exports which Vite's esbuild pre-bundler
6
+ * can't resolve when packages are in CLI's node_modules (not user's project).
7
+ * This plugin uses resolveId hook to dynamically resolve imports to dist files.
8
+ */
9
+ export declare function fumadocsPlugin(nodeModulesPath: string): Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prev-cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Transform MDX directories into beautiful documentation websites",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,12 +41,15 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@mdx-js/rollup": "^3.0.0",
44
- "@terrastruct/d2": "^0.1.33",
45
44
  "@tailwindcss/vite": "^4.0.0",
45
+ "@tanstack/react-router": "^1.145.7",
46
+ "@terrastruct/d2": "^0.1.33",
46
47
  "@vitejs/plugin-react-swc": "^4.2.2",
47
48
  "class-variance-authority": "^0.7.0",
48
49
  "clsx": "^2.1.0",
49
50
  "fast-glob": "^3.3.0",
51
+ "fumadocs-core": "^16.4.3",
52
+ "fumadocs-ui": "^16.4.3",
50
53
  "lucide-react": "^0.460.0",
51
54
  "mermaid": "^11.0.0",
52
55
  "react": "^19.0.0",
@@ -1,7 +1,5 @@
1
- import React, { useEffect } from 'react'
2
- import { Outlet, useLocation } from 'react-router-dom'
3
- import { Sidebar } from './Sidebar'
4
- import './styles.css'
1
+ import { useEffect } from 'react'
2
+ import { useLocation } from '@tanstack/react-router'
5
3
 
6
4
  // Simple hash function for cache keys
7
5
  function hashCode(str: string): string {
@@ -159,7 +157,8 @@ async function renderDiagrams() {
159
157
  ])
160
158
  }
161
159
 
162
- export function Layout() {
160
+ // Hook for diagram rendering
161
+ export function useDiagrams() {
163
162
  const location = useLocation()
164
163
 
165
164
  useEffect(() => {
@@ -172,17 +171,4 @@ export function Layout() {
172
171
  cleanupDiagrams()
173
172
  }
174
173
  }, [location.pathname])
175
-
176
- return (
177
- <div className="min-h-screen bg-background text-foreground">
178
- <div className="flex">
179
- <Sidebar />
180
- <main className="flex-1 p-8 max-w-4xl animate-fade-in">
181
- <article className="prose max-w-none">
182
- <Outlet />
183
- </article>
184
- </main>
185
- </div>
186
- </div>
187
- )
188
174
  }
@@ -1,10 +1,98 @@
1
1
  import React from 'react'
2
2
  import { createRoot } from 'react-dom/client'
3
+ import {
4
+ RouterProvider,
5
+ createRouter,
6
+ createRootRoute,
7
+ createRoute,
8
+ Outlet,
9
+ Link,
10
+ } from '@tanstack/react-router'
11
+ import { TanstackProvider } from 'fumadocs-core/framework/tanstack'
12
+ import { DocsLayout } from 'fumadocs-ui/layouts/docs'
13
+ import type { PageTree } from 'fumadocs-core/server'
14
+ import { pages, sidebar } from 'virtual:prev-pages'
15
+ import { useDiagrams } from './diagrams'
16
+ import 'fumadocs-ui/style.css'
3
17
  import './styles.css'
4
- import { App } from './App'
5
18
 
19
+ // Convert prev-cli sidebar to Fumadocs PageTree format
20
+ function convertToPageTree(items: any[]): PageTree.Root {
21
+ function convertItem(item: any): PageTree.Item | PageTree.Folder {
22
+ if (item.children) {
23
+ return {
24
+ type: 'folder',
25
+ name: item.title,
26
+ children: item.children.map(convertItem),
27
+ }
28
+ }
29
+ return {
30
+ type: 'page',
31
+ name: item.title,
32
+ url: item.route || '/',
33
+ }
34
+ }
35
+
36
+ return {
37
+ name: 'docs',
38
+ children: items.map(convertItem),
39
+ }
40
+ }
41
+
42
+ // Dynamic imports for MDX pages
43
+ const pageModules = import.meta.glob('/**/*.mdx', { eager: true })
44
+
45
+ function getPageComponent(file: string): React.ComponentType | null {
46
+ const mod = pageModules[`/${file}`] as { default: React.ComponentType } | undefined
47
+ return mod?.default || null
48
+ }
49
+
50
+ // Page wrapper that renders diagrams after content loads
51
+ function PageWrapper({ Component }: { Component: React.ComponentType }) {
52
+ useDiagrams()
53
+ return <Component />
54
+ }
55
+
56
+ // Root layout with Fumadocs DocsLayout - TanstackProvider wraps inside router
57
+ function RootLayout() {
58
+ const pageTree = convertToPageTree(sidebar)
59
+
60
+ return (
61
+ <TanstackProvider>
62
+ <DocsLayout
63
+ tree={pageTree}
64
+ nav={{ enabled: false }}
65
+ >
66
+ <article className="prose max-w-none">
67
+ <Outlet />
68
+ </article>
69
+ </DocsLayout>
70
+ </TanstackProvider>
71
+ )
72
+ }
73
+
74
+ // Create root route
75
+ const rootRoute = createRootRoute({
76
+ component: RootLayout,
77
+ })
78
+
79
+ // Create routes from pages
80
+ const pageRoutes = pages.map((page: { route: string; file: string }) => {
81
+ const Component = getPageComponent(page.file)
82
+ return createRoute({
83
+ getParentRoute: () => rootRoute,
84
+ path: page.route === '/' ? '/' : page.route,
85
+ component: Component ? () => <PageWrapper Component={Component} /> : () => null,
86
+ })
87
+ })
88
+
89
+ // Create router
90
+ const routeTree = rootRoute.addChildren(pageRoutes)
91
+ const router = createRouter({ routeTree })
92
+
93
+ // Mount app - RouterProvider must be outermost so TanStack Router context is available
6
94
  const container = document.getElementById('root')
7
95
  if (container) {
8
96
  const root = createRoot(container)
9
- root.render(<App />)
97
+ root.render(<RouterProvider router={router} />)
10
98
  }
@@ -1,274 +1,99 @@
1
- /* Fonts loaded via HTML preload for better performance */
2
- @import "tailwindcss";
3
-
4
- @custom-variant dark (&:is(.dark *));
5
-
6
- /* Scan CLI source files for Tailwind classes */
7
- @source "../theme";
8
- @source "../ui";
9
-
10
1
  /*
11
- Design OS - Refined Utility Design System
12
- A professional, editorial-inspired aesthetic with warm neutrals
13
- Using Tailwind's stone palette for warmth
2
+ Prev CLI Theme Customization
3
+ Overrides fumadocs-ui's default theme with warm stone palette
14
4
  */
15
- @theme {
16
- /* Font families - DM Sans for clean, geometric type */
17
- --font-display: "DM Sans", system-ui, sans-serif;
18
- --font-body: "DM Sans", system-ui, sans-serif;
19
- --font-sans: "DM Sans", system-ui, sans-serif;
20
- --font-mono: "IBM Plex Mono", ui-monospace, monospace;
21
- }
22
5
 
23
- /* Shadcn theme integration */
24
- @theme inline {
25
- --radius-sm: calc(var(--radius) - 4px);
26
- --radius-md: calc(var(--radius) - 2px);
27
- --radius-lg: var(--radius);
28
- --radius-xl: calc(var(--radius) + 4px);
29
- --color-background: var(--background);
30
- --color-foreground: var(--foreground);
31
- --color-card: var(--card);
32
- --color-card-foreground: var(--card-foreground);
33
- --color-popover: var(--popover);
34
- --color-popover-foreground: var(--popover-foreground);
35
- --color-primary: var(--primary);
36
- --color-primary-foreground: var(--primary-foreground);
37
- --color-secondary: var(--secondary);
38
- --color-secondary-foreground: var(--secondary-foreground);
39
- --color-muted: var(--muted);
40
- --color-muted-foreground: var(--muted-foreground);
41
- --color-accent: var(--accent);
42
- --color-accent-foreground: var(--accent-foreground);
43
- --color-destructive: var(--destructive);
44
- --color-border: var(--border);
45
- --color-input: var(--input);
46
- --color-ring: var(--ring);
47
- --color-chart-1: var(--chart-1);
48
- --color-chart-2: var(--chart-2);
49
- --color-chart-3: var(--chart-3);
50
- --color-chart-4: var(--chart-4);
51
- --color-chart-5: var(--chart-5);
52
- --color-sidebar: var(--sidebar);
53
- --color-sidebar-foreground: var(--sidebar-foreground);
54
- --color-sidebar-primary: var(--sidebar-primary);
55
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
56
- --color-sidebar-accent: var(--sidebar-accent);
57
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
58
- --color-sidebar-border: var(--sidebar-border);
59
- --color-sidebar-ring: var(--sidebar-ring);
6
+ /* Custom fonts */
7
+ :root {
8
+ --fd-font-sans: "DM Sans", system-ui, sans-serif;
9
+ --fd-font-mono: "IBM Plex Mono", ui-monospace, monospace;
60
10
  }
61
11
 
62
- /*
63
- Light theme - Warm Stone palette
64
- Professional, editorial aesthetic with warmth
65
- */
12
+ /* Override fumadocs colors with warm stone palette - Light theme */
66
13
  :root {
67
- --radius: 0.5rem;
68
- /* Backgrounds & Foregrounds - Stone palette for warmth */
69
- --background: oklch(0.985 0.001 106.424); /* stone-50 #FAFAF9 */
70
- --foreground: oklch(0.216 0.006 56.043); /* stone-900 #1C1917 */
71
- --card: oklch(1 0 0); /* white */
72
- --card-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
73
- --popover: oklch(1 0 0); /* white */
74
- --popover-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
75
- /* Primary - Stone dark */
76
- --primary: oklch(0.216 0.006 56.043); /* stone-900 */
77
- --primary-foreground: oklch(0.985 0.001 106.424); /* stone-50 */
78
- /* Secondary - Stone light */
79
- --secondary: oklch(0.970 0.001 106.424); /* stone-100 */
80
- --secondary-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
81
- /* Muted - Stone for subdued text */
82
- --muted: oklch(0.970 0.001 106.424); /* stone-100 */
83
- --muted-foreground: oklch(0.444 0.011 73.639); /* stone-600 #57534E */
84
- /* Accent - Lime green for subtle pops */
85
- --accent: oklch(0.970 0.001 106.424); /* stone-100 */
86
- --accent-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
87
- /* Destructive - Rose */
88
- --destructive: oklch(0.586 0.253 17.585); /* rose-600 */
89
- /* Borders & Inputs - Stone very light */
90
- --border: oklch(0.923 0.003 48.717); /* stone-200 #E7E5E4 */
91
- --input: oklch(0.923 0.003 48.717); /* stone-200 */
92
- --ring: oklch(0.553 0.013 58.071); /* stone-500 */
93
- /* Chart colors */
94
- --chart-1: oklch(0.532 0.157 131.589); /* lime-600 #65A30D */
95
- --chart-2: oklch(0.6 0.118 184.704); /* teal-600 */
96
- --chart-3: oklch(0.398 0.07 227.392); /* cyan-800 */
97
- --chart-4: oklch(0.828 0.189 84.429); /* yellow-400 */
98
- --chart-5: oklch(0.769 0.188 70.08); /* amber-400 */
99
- /* Sidebar - Stone */
100
- --sidebar: oklch(0.985 0.001 106.424); /* stone-50 */
101
- --sidebar-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
102
- --sidebar-primary: oklch(0.216 0.006 56.043); /* stone-900 */
103
- --sidebar-primary-foreground: oklch(0.985 0.001 106.424); /* stone-50 */
104
- --sidebar-accent: oklch(0.970 0.001 106.424); /* stone-100 */
105
- --sidebar-accent-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
106
- --sidebar-border: oklch(0.923 0.003 48.717); /* stone-200 */
107
- --sidebar-ring: oklch(0.553 0.013 58.071); /* stone-500 */
14
+ --fd-background: oklch(0.985 0.001 106.424); /* stone-50 */
15
+ --fd-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
16
+ --fd-muted: oklch(0.970 0.001 106.424); /* stone-100 */
17
+ --fd-muted-foreground: oklch(0.444 0.011 73.639); /* stone-600 */
18
+ --fd-card: oklch(0.985 0.001 106.424); /* stone-50 */
19
+ --fd-card-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
20
+ --fd-popover: oklch(0.985 0.001 106.424); /* stone-50 */
21
+ --fd-popover-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
22
+ --fd-border: oklch(0.869 0.005 106.424); /* stone-300 */
23
+ --fd-primary: oklch(0.268 0.007 34.298); /* stone-800 */
24
+ --fd-primary-foreground: oklch(0.985 0.001 106.424); /* stone-50 */
25
+ --fd-secondary: oklch(0.923 0.003 106.424); /* stone-200 */
26
+ --fd-secondary-foreground: oklch(0.268 0.007 34.298); /* stone-800 */
27
+ --fd-accent: oklch(0.923 0.003 106.424); /* stone-200 */
28
+ --fd-accent-foreground: oklch(0.268 0.007 34.298); /* stone-800 */
29
+ --fd-ring: oklch(0.553 0.013 73.639); /* stone-500 */
108
30
  }
109
31
 
110
- /*
111
- Dark theme - Warm Stone dark palette
112
- */
113
- .dark {
114
- /* Backgrounds & Foregrounds - Stone dark */
115
- --background: oklch(0.216 0.006 56.043); /* stone-900 #1C1917 */
116
- --foreground: oklch(0.985 0.001 106.424); /* stone-50 */
117
- --card: oklch(0.268 0.007 34.298); /* stone-800 #292524 */
118
- --card-foreground: oklch(0.985 0.001 106.424); /* stone-50 */
119
- --popover: oklch(0.268 0.007 34.298); /* stone-800 */
120
- --popover-foreground: oklch(0.985 0.001 106.424); /* stone-50 */
121
- /* Primary - Stone light */
122
- --primary: oklch(0.923 0.003 48.717); /* stone-200 */
123
- --primary-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
124
- /* Secondary - Stone dark */
125
- --secondary: oklch(0.318 0.008 43.185); /* stone-700 */
126
- --secondary-foreground: oklch(0.985 0.001 106.424); /* stone-50 */
127
- /* Muted - Stone dark */
128
- --muted: oklch(0.318 0.008 43.185); /* stone-700 */
129
- --muted-foreground: oklch(0.709 0.01 56.259); /* stone-400 #A8A29E */
130
- /* Accent - Stone dark */
131
- --accent: oklch(0.318 0.008 43.185); /* stone-700 */
132
- --accent-foreground: oklch(0.985 0.001 106.424); /* stone-50 */
133
- /* Destructive - Rose lighter for dark mode */
134
- --destructive: oklch(0.712 0.194 13.428); /* rose-400 */
135
- /* Borders & Inputs - Stone dark */
136
- --border: oklch(0.370 0.010 67.558); /* stone-600 */
137
- --input: oklch(0.370 0.010 67.558); /* stone-600 */
138
- --ring: oklch(0.553 0.013 58.071); /* stone-500 */
139
- /* Chart colors - brighter for dark mode */
140
- --chart-1: oklch(0.648 0.2 131.684); /* lime-500 */
141
- --chart-2: oklch(0.696 0.17 162.48); /* emerald-400 */
142
- --chart-3: oklch(0.769 0.188 70.08); /* amber-400 */
143
- --chart-4: oklch(0.627 0.265 303.9); /* purple-500 */
144
- --chart-5: oklch(0.645 0.246 16.439); /* rose-500 */
145
- /* Sidebar - Stone dark */
146
- --sidebar: oklch(0.268 0.007 34.298); /* stone-800 */
147
- --sidebar-foreground: oklch(0.985 0.001 106.424); /* stone-50 */
148
- --sidebar-primary: oklch(0.648 0.2 131.684); /* lime-500 */
149
- --sidebar-primary-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
150
- --sidebar-accent: oklch(0.318 0.008 43.185); /* stone-700 */
151
- --sidebar-accent-foreground: oklch(0.985 0.001 106.424); /* stone-50 */
152
- --sidebar-border: oklch(0.370 0.010 67.558); /* stone-600 */
153
- --sidebar-ring: oklch(0.553 0.013 58.071); /* stone-500 */
32
+ /* Dark theme with warm stone palette */
33
+ .dark, [data-theme="dark"] {
34
+ --fd-background: oklch(0.216 0.006 56.043); /* stone-900 */
35
+ --fd-foreground: oklch(0.970 0.001 106.424); /* stone-100 */
36
+ --fd-muted: oklch(0.268 0.007 34.298); /* stone-800 */
37
+ --fd-muted-foreground: oklch(0.709 0.010 106.424); /* stone-400 */
38
+ --fd-card: oklch(0.268 0.007 34.298); /* stone-800 */
39
+ --fd-card-foreground: oklch(0.970 0.001 106.424); /* stone-100 */
40
+ --fd-popover: oklch(0.268 0.007 34.298); /* stone-800 */
41
+ --fd-popover-foreground: oklch(0.970 0.001 106.424); /* stone-100 */
42
+ --fd-border: oklch(0.444 0.011 73.639); /* stone-600 */
43
+ --fd-primary: oklch(0.970 0.001 106.424); /* stone-100 */
44
+ --fd-primary-foreground: oklch(0.216 0.006 56.043); /* stone-900 */
45
+ --fd-secondary: oklch(0.341 0.009 56.043); /* stone-700 */
46
+ --fd-secondary-foreground: oklch(0.970 0.001 106.424); /* stone-100 */
47
+ --fd-accent: oklch(0.341 0.009 56.043); /* stone-700 */
48
+ --fd-accent-foreground: oklch(0.970 0.001 106.424); /* stone-100 */
49
+ --fd-ring: oklch(0.553 0.013 73.639); /* stone-500 */
154
50
  }
155
51
 
156
- /* Base layer styles */
157
- @layer base {
158
- * {
159
- @apply border-border outline-ring/50;
160
- }
161
- body {
162
- @apply bg-background text-foreground font-sans antialiased;
163
- }
164
- h1, h2, h3, h4, h5, h6 {
165
- font-family: var(--font-display);
166
- @apply font-semibold tracking-tight;
167
- }
168
- code, pre, kbd {
169
- font-family: var(--font-mono);
170
- }
52
+ /* Diagram container styles */
53
+ .mermaid-diagram,
54
+ .d2-diagram {
55
+ margin: 1rem 0;
56
+ padding: 1rem;
57
+ background: var(--fd-muted);
58
+ border-radius: 0.5rem;
59
+ display: flex;
60
+ justify-content: center;
171
61
  }
172
62
 
173
- /* Subtle page fade-in animation */
174
- @layer utilities {
175
- .animate-fade-in {
176
- animation: fade-in 200ms ease-out;
177
- }
178
-
179
- @keyframes fade-in {
180
- from {
181
- opacity: 0;
182
- }
183
- to {
184
- opacity: 1;
185
- }
186
- }
187
-
188
- .animate-collapse-down {
189
- animation: collapse-down 200ms ease-out;
190
- }
191
-
192
- .animate-collapse-up {
193
- animation: collapse-up 200ms ease-out;
194
- }
195
-
196
- @keyframes collapse-down {
197
- from {
198
- height: 0;
199
- }
200
- to {
201
- height: var(--radix-collapsible-content-height);
202
- }
203
- }
204
-
205
- @keyframes collapse-up {
206
- from {
207
- height: var(--radix-collapsible-content-height);
208
- }
209
- to {
210
- height: 0;
211
- }
212
- }
63
+ .mermaid-diagram svg,
64
+ .d2-diagram svg {
65
+ max-width: 100%;
66
+ height: auto;
213
67
  }
214
68
 
215
- /* Prose styling with stone palette */
216
- @layer base {
217
- .prose {
218
- --tw-prose-body: var(--foreground);
219
- --tw-prose-headings: var(--foreground);
220
- --tw-prose-lead: var(--muted-foreground);
221
- --tw-prose-links: var(--primary);
222
- --tw-prose-bold: var(--foreground);
223
- --tw-prose-counters: var(--muted-foreground);
224
- --tw-prose-bullets: var(--muted-foreground);
225
- --tw-prose-hr: var(--border);
226
- --tw-prose-quotes: var(--foreground);
227
- --tw-prose-quote-borders: var(--border);
228
- --tw-prose-captions: var(--muted-foreground);
229
- --tw-prose-kbd: var(--foreground);
230
- --tw-prose-kbd-shadows: var(--ring);
231
- --tw-prose-code: var(--foreground);
232
- --tw-prose-pre-code: var(--foreground);
233
- --tw-prose-pre-bg: var(--muted);
234
- --tw-prose-th-borders: var(--border);
235
- --tw-prose-td-borders: var(--border);
236
- }
237
-
238
- .prose :where(code):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
239
- @apply bg-muted px-1.5 py-0.5 rounded-sm text-sm font-mono;
240
- }
241
-
242
- .prose :where(code):not(:where([class~="not-prose"], [class~="not-prose"] *))::before,
243
- .prose :where(code):not(:where([class~="not-prose"], [class~="not-prose"] *))::after {
244
- content: none;
245
- }
246
-
247
- .prose :where(pre):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
248
- @apply bg-muted border border-border rounded-lg;
249
- }
250
-
251
- .prose :where(a):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
252
- @apply text-foreground font-medium underline underline-offset-4 decoration-border hover:decoration-foreground transition-colors;
253
- }
254
-
255
- .prose :where(h1, h2, h3, h4, h5, h6):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
256
- @apply font-display;
257
- }
69
+ /* Smooth page transitions */
70
+ @keyframes fade-in {
71
+ from { opacity: 0; transform: translateY(4px); }
72
+ to { opacity: 1; transform: translateY(0); }
73
+ }
258
74
 
259
- .prose :where(blockquote):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
260
- @apply border-l-4 border-border pl-4 italic text-muted-foreground;
261
- }
75
+ .animate-fade-in {
76
+ animation: fade-in 0.2s ease-out;
77
+ }
262
78
 
263
- .prose :where(table):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
264
- @apply w-full border-collapse;
265
- }
79
+ /* Typography improvements */
80
+ .prose {
81
+ --tw-prose-body: var(--fd-foreground);
82
+ --tw-prose-headings: var(--fd-foreground);
83
+ --tw-prose-links: var(--fd-primary);
84
+ --tw-prose-code: var(--fd-foreground);
85
+ --tw-prose-pre-bg: var(--fd-muted);
86
+ }
266
87
 
267
- .prose :where(th):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
268
- @apply border-b border-border px-4 py-2 text-left font-semibold;
269
- }
88
+ /* Code block styling */
89
+ pre code {
90
+ font-family: var(--fd-font-mono);
91
+ font-size: 0.875rem;
92
+ line-height: 1.7;
93
+ }
270
94
 
271
- .prose :where(td):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
272
- @apply border-b border-border px-4 py-2;
273
- }
95
+ /* Sidebar active item highlight */
96
+ [data-active="true"] {
97
+ background: var(--fd-accent);
98
+ color: var(--fd-accent-foreground);
274
99
  }
@@ -1 +0,0 @@
1
- export declare function App(): import("react/jsx-runtime").JSX.Element;
@@ -1,2 +0,0 @@
1
- import './styles.css';
2
- export declare function Layout(): import("react/jsx-runtime").JSX.Element;
@@ -1 +0,0 @@
1
- export declare function Sidebar(): import("react/jsx-runtime").JSX.Element;
@@ -1 +0,0 @@
1
- import './styles.css';
package/src/theme/App.tsx DELETED
@@ -1,33 +0,0 @@
1
- import React from 'react'
2
- import { BrowserRouter, Routes, Route } from 'react-router-dom'
3
- import { Layout } from './Layout'
4
- import { pages } from 'virtual:prev-pages'
5
-
6
- // Dynamic imports for MDX pages
7
- const pageModules = import.meta.glob('/**/*.mdx', { eager: true })
8
-
9
- function getPageComponent(file: string) {
10
- const mod = pageModules[`/${file}`] as { default: React.ComponentType }
11
- return mod?.default
12
- }
13
-
14
- export function App() {
15
- return (
16
- <BrowserRouter>
17
- <Routes>
18
- <Route element={<Layout />}>
19
- {pages.map((page: { route: string; file: string }) => {
20
- const Component = getPageComponent(page.file)
21
- return Component ? (
22
- <Route
23
- key={page.route}
24
- path={page.route}
25
- element={<Component />}
26
- />
27
- ) : null
28
- })}
29
- </Route>
30
- </Routes>
31
- </BrowserRouter>
32
- )
33
- }
@@ -1,75 +0,0 @@
1
- import React from 'react'
2
- import { Link, useLocation } from 'react-router-dom'
3
- import { sidebar } from 'virtual:prev-pages'
4
- import { cn } from '../ui/utils'
5
-
6
- interface SidebarItem {
7
- title: string
8
- route?: string
9
- children?: SidebarItem[]
10
- }
11
-
12
- export function Sidebar() {
13
- const location = useLocation()
14
-
15
- return (
16
- <aside className="w-64 border-r border-sidebar-border bg-sidebar p-4 min-h-screen sticky top-0">
17
- <nav>
18
- <ul className="space-y-1">
19
- {(sidebar as SidebarItem[]).map((item, i) => (
20
- <SidebarItemComponent
21
- key={i}
22
- item={item}
23
- currentPath={location.pathname}
24
- />
25
- ))}
26
- </ul>
27
- </nav>
28
- </aside>
29
- )
30
- }
31
-
32
- function SidebarItemComponent({
33
- item,
34
- currentPath
35
- }: {
36
- item: SidebarItem
37
- currentPath: string
38
- }) {
39
- const isActive = item.route === currentPath
40
-
41
- if (item.children) {
42
- return (
43
- <li className="mt-4 first:mt-0">
44
- <span className="font-semibold text-xs text-sidebar-foreground/60 uppercase tracking-wider">
45
- {item.title}
46
- </span>
47
- <ul className="ml-3 mt-2 space-y-1">
48
- {item.children.map((child, i) => (
49
- <SidebarItemComponent
50
- key={i}
51
- item={child}
52
- currentPath={currentPath}
53
- />
54
- ))}
55
- </ul>
56
- </li>
57
- )
58
- }
59
-
60
- return (
61
- <li>
62
- <Link
63
- to={item.route || '/'}
64
- className={cn(
65
- 'block py-1.5 px-2 rounded-md text-sm transition-colors',
66
- isActive
67
- ? 'bg-sidebar-accent text-sidebar-accent-foreground font-medium'
68
- : 'text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent/50'
69
- )}
70
- >
71
- {item.title}
72
- </Link>
73
- </li>
74
- )
75
- }