prev-cli 0.3.4 → 0.5.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 +34 -101
- package/package.json +1 -1
- package/src/theme/Layout.tsx +116 -0
- package/src/theme/entry.tsx +26 -17
- package/src/theme/styles.css +122 -2
package/dist/cli.js
CHANGED
|
@@ -12,9 +12,9 @@ import react from "@vitejs/plugin-react-swc";
|
|
|
12
12
|
import mdx from "@mdx-js/rollup";
|
|
13
13
|
import remarkGfm from "remark-gfm";
|
|
14
14
|
import rehypeHighlight from "rehype-highlight";
|
|
15
|
-
import
|
|
15
|
+
import path4 from "path";
|
|
16
16
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17
|
-
import { existsSync as
|
|
17
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
18
18
|
|
|
19
19
|
// src/utils/cache.ts
|
|
20
20
|
import { createHash } from "crypto";
|
|
@@ -82,7 +82,7 @@ function fileToRoute(file) {
|
|
|
82
82
|
return "/" + withoutExt;
|
|
83
83
|
}
|
|
84
84
|
async function scanPages(rootDir) {
|
|
85
|
-
const files = await fg.glob("**/*.mdx", {
|
|
85
|
+
const files = await fg.glob("**/*.{md,mdx}", {
|
|
86
86
|
cwd: rootDir,
|
|
87
87
|
ignore: ["node_modules/**", "dist/**", ".cache/**"]
|
|
88
88
|
});
|
|
@@ -164,7 +164,7 @@ function pagesPlugin(rootDir) {
|
|
|
164
164
|
}
|
|
165
165
|
},
|
|
166
166
|
handleHotUpdate({ file, server }) {
|
|
167
|
-
if (file.endsWith(".mdx")) {
|
|
167
|
+
if (file.endsWith(".mdx") || file.endsWith(".md")) {
|
|
168
168
|
const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
|
|
169
169
|
if (mod) {
|
|
170
170
|
server.moduleGraph.invalidateModule(mod);
|
|
@@ -268,72 +268,6 @@ function entryPlugin(rootDir) {
|
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
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
|
-
|
|
337
271
|
// src/vite/config.ts
|
|
338
272
|
function createFriendlyLogger() {
|
|
339
273
|
const logger = createLogger("info", { allowClearScreen: false });
|
|
@@ -389,10 +323,10 @@ function createFriendlyLogger() {
|
|
|
389
323
|
};
|
|
390
324
|
}
|
|
391
325
|
function findCliRoot2() {
|
|
392
|
-
let dir =
|
|
326
|
+
let dir = path4.dirname(fileURLToPath2(import.meta.url));
|
|
393
327
|
for (let i = 0;i < 10; i++) {
|
|
394
|
-
const pkgPath =
|
|
395
|
-
if (
|
|
328
|
+
const pkgPath = path4.join(dir, "package.json");
|
|
329
|
+
if (existsSync2(pkgPath)) {
|
|
396
330
|
try {
|
|
397
331
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
398
332
|
if (pkg.name === "prev-cli") {
|
|
@@ -400,24 +334,24 @@ function findCliRoot2() {
|
|
|
400
334
|
}
|
|
401
335
|
} catch {}
|
|
402
336
|
}
|
|
403
|
-
const parent =
|
|
337
|
+
const parent = path4.dirname(dir);
|
|
404
338
|
if (parent === dir)
|
|
405
339
|
break;
|
|
406
340
|
dir = parent;
|
|
407
341
|
}
|
|
408
|
-
return
|
|
342
|
+
return path4.dirname(path4.dirname(fileURLToPath2(import.meta.url)));
|
|
409
343
|
}
|
|
410
344
|
function findNodeModules(cliRoot2) {
|
|
411
|
-
const localNodeModules =
|
|
412
|
-
if (
|
|
345
|
+
const localNodeModules = path4.join(cliRoot2, "node_modules");
|
|
346
|
+
if (existsSync2(path4.join(localNodeModules, "react"))) {
|
|
413
347
|
return localNodeModules;
|
|
414
348
|
}
|
|
415
349
|
let dir = cliRoot2;
|
|
416
350
|
for (let i = 0;i < 10; i++) {
|
|
417
|
-
const parent =
|
|
351
|
+
const parent = path4.dirname(dir);
|
|
418
352
|
if (parent === dir)
|
|
419
353
|
break;
|
|
420
|
-
if (
|
|
354
|
+
if (path4.basename(parent) === "node_modules" && existsSync2(path4.join(parent, "react"))) {
|
|
421
355
|
return parent;
|
|
422
356
|
}
|
|
423
357
|
dir = parent;
|
|
@@ -426,7 +360,7 @@ function findNodeModules(cliRoot2) {
|
|
|
426
360
|
}
|
|
427
361
|
var cliRoot2 = findCliRoot2();
|
|
428
362
|
var cliNodeModules = findNodeModules(cliRoot2);
|
|
429
|
-
var srcRoot2 =
|
|
363
|
+
var srcRoot2 = path4.join(cliRoot2, "src");
|
|
430
364
|
async function createViteConfig(options) {
|
|
431
365
|
const { rootDir, mode, port } = options;
|
|
432
366
|
const cacheDir = await ensureCacheDir(rootDir);
|
|
@@ -437,7 +371,6 @@ async function createViteConfig(options) {
|
|
|
437
371
|
customLogger: createFriendlyLogger(),
|
|
438
372
|
logLevel: mode === "production" ? "silent" : "info",
|
|
439
373
|
plugins: [
|
|
440
|
-
fumadocsPlugin(cliNodeModules),
|
|
441
374
|
mdx({
|
|
442
375
|
remarkPlugins: [remarkGfm],
|
|
443
376
|
rehypePlugins: [rehypeHighlight]
|
|
@@ -448,22 +381,19 @@ async function createViteConfig(options) {
|
|
|
448
381
|
],
|
|
449
382
|
resolve: {
|
|
450
383
|
alias: {
|
|
451
|
-
"@prev/ui":
|
|
452
|
-
"@prev/theme":
|
|
453
|
-
react:
|
|
454
|
-
"react-dom":
|
|
455
|
-
"@tanstack/react-router":
|
|
456
|
-
mermaid:
|
|
457
|
-
dayjs:
|
|
458
|
-
"@terrastruct/d2":
|
|
384
|
+
"@prev/ui": path4.join(srcRoot2, "ui"),
|
|
385
|
+
"@prev/theme": path4.join(srcRoot2, "theme"),
|
|
386
|
+
react: path4.join(cliNodeModules, "react"),
|
|
387
|
+
"react-dom": path4.join(cliNodeModules, "react-dom"),
|
|
388
|
+
"@tanstack/react-router": path4.join(cliNodeModules, "@tanstack/react-router"),
|
|
389
|
+
mermaid: path4.join(cliNodeModules, "mermaid"),
|
|
390
|
+
dayjs: path4.join(cliNodeModules, "dayjs"),
|
|
391
|
+
"@terrastruct/d2": path4.join(cliNodeModules, "@terrastruct/d2")
|
|
459
392
|
},
|
|
460
393
|
dedupe: [
|
|
461
394
|
"react",
|
|
462
395
|
"react-dom",
|
|
463
|
-
"@tanstack/react-router"
|
|
464
|
-
"fumadocs-core",
|
|
465
|
-
"fumadocs-ui",
|
|
466
|
-
"@fumadocs/ui"
|
|
396
|
+
"@tanstack/react-router"
|
|
467
397
|
]
|
|
468
398
|
},
|
|
469
399
|
optimizeDeps: {
|
|
@@ -478,11 +408,6 @@ async function createViteConfig(options) {
|
|
|
478
408
|
"mermaid",
|
|
479
409
|
"dayjs",
|
|
480
410
|
"@terrastruct/d2"
|
|
481
|
-
],
|
|
482
|
-
exclude: [
|
|
483
|
-
"fumadocs-core",
|
|
484
|
-
"fumadocs-ui",
|
|
485
|
-
"@fumadocs/ui"
|
|
486
411
|
]
|
|
487
412
|
},
|
|
488
413
|
ssr: {
|
|
@@ -493,6 +418,12 @@ async function createViteConfig(options) {
|
|
|
493
418
|
strictPort: false,
|
|
494
419
|
fs: {
|
|
495
420
|
allow: [rootDir, cliRoot2]
|
|
421
|
+
},
|
|
422
|
+
warmup: {
|
|
423
|
+
clientFiles: [
|
|
424
|
+
path4.join(srcRoot2, "theme/entry.tsx"),
|
|
425
|
+
path4.join(srcRoot2, "theme/styles.css")
|
|
426
|
+
]
|
|
496
427
|
}
|
|
497
428
|
},
|
|
498
429
|
preview: {
|
|
@@ -500,7 +431,7 @@ async function createViteConfig(options) {
|
|
|
500
431
|
strictPort: false
|
|
501
432
|
},
|
|
502
433
|
build: {
|
|
503
|
-
outDir:
|
|
434
|
+
outDir: path4.join(rootDir, "dist"),
|
|
504
435
|
reportCompressedSize: false,
|
|
505
436
|
chunkSizeWarningLimit: 1e4
|
|
506
437
|
}
|
|
@@ -551,7 +482,7 @@ function printWelcome(type) {
|
|
|
551
482
|
}
|
|
552
483
|
function printReady() {
|
|
553
484
|
console.log();
|
|
554
|
-
console.log(" Edit your .mdx files and see changes instantly.");
|
|
485
|
+
console.log(" Edit your .md/.mdx files and see changes instantly.");
|
|
555
486
|
console.log(" Press Ctrl+C to stop.");
|
|
556
487
|
console.log();
|
|
557
488
|
}
|
|
@@ -606,12 +537,13 @@ var { values, positionals } = parseArgs({
|
|
|
606
537
|
options: {
|
|
607
538
|
port: { type: "string", short: "p" },
|
|
608
539
|
days: { type: "string", short: "d" },
|
|
540
|
+
cwd: { type: "string", short: "c" },
|
|
609
541
|
help: { type: "boolean", short: "h" }
|
|
610
542
|
},
|
|
611
543
|
allowPositionals: true
|
|
612
544
|
});
|
|
613
545
|
var command = positionals[0] || "dev";
|
|
614
|
-
var rootDir = positionals[1] || process.cwd();
|
|
546
|
+
var rootDir = values.cwd || positionals[1] || process.cwd();
|
|
615
547
|
function printHelp() {
|
|
616
548
|
console.log(`
|
|
617
549
|
prev - Zero-config documentation site generator
|
|
@@ -626,6 +558,7 @@ Commands:
|
|
|
626
558
|
clean Remove old cache directories
|
|
627
559
|
|
|
628
560
|
Options:
|
|
561
|
+
-c, --cwd <path> Set working directory
|
|
629
562
|
-p, --port <port> Specify port (dev/preview)
|
|
630
563
|
-d, --days <days> Cache age threshold for clean (default: 30)
|
|
631
564
|
-h, --help Show this help message
|
package/package.json
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Link, useLocation } from '@tanstack/react-router'
|
|
3
|
+
import type { PageTree } from 'fumadocs-core/server'
|
|
4
|
+
|
|
5
|
+
interface LayoutProps {
|
|
6
|
+
tree: PageTree.Root
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface SidebarItemProps {
|
|
11
|
+
item: PageTree.Item | PageTree.Folder
|
|
12
|
+
depth?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function SidebarItem({ item, depth = 0 }: SidebarItemProps) {
|
|
16
|
+
const location = useLocation()
|
|
17
|
+
const [isOpen, setIsOpen] = useState(true)
|
|
18
|
+
|
|
19
|
+
if (item.type === 'folder') {
|
|
20
|
+
return (
|
|
21
|
+
<div className="sidebar-folder">
|
|
22
|
+
<button
|
|
23
|
+
className="sidebar-folder-toggle"
|
|
24
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
25
|
+
style={{ paddingLeft: `${depth * 12 + 8}px` }}
|
|
26
|
+
>
|
|
27
|
+
<span className={`folder-icon ${isOpen ? 'open' : ''}`}>
|
|
28
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
|
|
29
|
+
<path d="M4.5 2L8.5 6L4.5 10" stroke="currentColor" strokeWidth="1.5" fill="none"/>
|
|
30
|
+
</svg>
|
|
31
|
+
</span>
|
|
32
|
+
{item.name}
|
|
33
|
+
</button>
|
|
34
|
+
{isOpen && (
|
|
35
|
+
<div className="sidebar-folder-children">
|
|
36
|
+
{item.children.map((child, i) => (
|
|
37
|
+
<SidebarItem key={i} item={child} depth={depth + 1} />
|
|
38
|
+
))}
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const isActive = location.pathname === item.url ||
|
|
46
|
+
(item.url === '/' && location.pathname === '/')
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Link
|
|
50
|
+
to={item.url}
|
|
51
|
+
className={`sidebar-link ${isActive ? 'active' : ''}`}
|
|
52
|
+
style={{ paddingLeft: `${depth * 12 + 16}px` }}
|
|
53
|
+
>
|
|
54
|
+
{item.name}
|
|
55
|
+
</Link>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ThemeToggle() {
|
|
60
|
+
const [isDark, setIsDark] = useState(() => {
|
|
61
|
+
if (typeof window !== 'undefined') {
|
|
62
|
+
return document.documentElement.classList.contains('dark')
|
|
63
|
+
}
|
|
64
|
+
return false
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const toggle = () => {
|
|
68
|
+
const newDark = !isDark
|
|
69
|
+
setIsDark(newDark)
|
|
70
|
+
document.documentElement.classList.toggle('dark', newDark)
|
|
71
|
+
localStorage.setItem('theme', newDark ? 'dark' : 'light')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<button className="theme-toggle" onClick={toggle} aria-label="Toggle theme">
|
|
76
|
+
{isDark ? (
|
|
77
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
78
|
+
<circle cx="12" cy="12" r="5"/>
|
|
79
|
+
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
|
|
80
|
+
</svg>
|
|
81
|
+
) : (
|
|
82
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
83
|
+
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
|
|
84
|
+
</svg>
|
|
85
|
+
)}
|
|
86
|
+
</button>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function Layout({ tree, children }: LayoutProps) {
|
|
91
|
+
// Initialize theme from localStorage
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
const saved = localStorage.getItem('theme')
|
|
94
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
95
|
+
const isDark = saved === 'dark' || (!saved && prefersDark)
|
|
96
|
+
document.documentElement.classList.toggle('dark', isDark)
|
|
97
|
+
}, [])
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className="prev-layout">
|
|
101
|
+
<aside className="prev-sidebar">
|
|
102
|
+
<nav className="sidebar-nav">
|
|
103
|
+
{tree.children.map((item, i) => (
|
|
104
|
+
<SidebarItem key={i} item={item} />
|
|
105
|
+
))}
|
|
106
|
+
</nav>
|
|
107
|
+
<div className="sidebar-footer">
|
|
108
|
+
<ThemeToggle />
|
|
109
|
+
</div>
|
|
110
|
+
</aside>
|
|
111
|
+
<main className="prev-main">
|
|
112
|
+
{children}
|
|
113
|
+
</main>
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
package/src/theme/entry.tsx
CHANGED
|
@@ -6,16 +6,30 @@ import {
|
|
|
6
6
|
createRootRoute,
|
|
7
7
|
createRoute,
|
|
8
8
|
Outlet,
|
|
9
|
-
Link,
|
|
10
9
|
} 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
10
|
import { pages, sidebar } from 'virtual:prev-pages'
|
|
15
11
|
import { useDiagrams } from './diagrams'
|
|
16
|
-
import '
|
|
12
|
+
import { Layout } from './Layout'
|
|
17
13
|
import './styles.css'
|
|
18
14
|
|
|
15
|
+
// PageTree types (simplified from fumadocs-core)
|
|
16
|
+
namespace PageTree {
|
|
17
|
+
export interface Item {
|
|
18
|
+
type: 'page'
|
|
19
|
+
name: string
|
|
20
|
+
url: string
|
|
21
|
+
}
|
|
22
|
+
export interface Folder {
|
|
23
|
+
type: 'folder'
|
|
24
|
+
name: string
|
|
25
|
+
children: (Item | Folder)[]
|
|
26
|
+
}
|
|
27
|
+
export interface Root {
|
|
28
|
+
name: string
|
|
29
|
+
children: (Item | Folder)[]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
// Convert prev-cli sidebar to Fumadocs PageTree format
|
|
20
34
|
function convertToPageTree(items: any[]): PageTree.Root {
|
|
21
35
|
function convertItem(item: any): PageTree.Item | PageTree.Folder {
|
|
@@ -40,7 +54,7 @@ function convertToPageTree(items: any[]): PageTree.Root {
|
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
// Dynamic imports for MDX pages
|
|
43
|
-
const pageModules = import.meta.glob('/**/*.mdx', { eager: true })
|
|
57
|
+
const pageModules = import.meta.glob('/**/*.{md,mdx}', { eager: true })
|
|
44
58
|
|
|
45
59
|
function getPageComponent(file: string): React.ComponentType | null {
|
|
46
60
|
const mod = pageModules[`/${file}`] as { default: React.ComponentType } | undefined
|
|
@@ -53,21 +67,16 @@ function PageWrapper({ Component }: { Component: React.ComponentType }) {
|
|
|
53
67
|
return <Component />
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
// Root layout with
|
|
70
|
+
// Root layout with custom lightweight Layout
|
|
57
71
|
function RootLayout() {
|
|
58
72
|
const pageTree = convertToPageTree(sidebar)
|
|
59
73
|
|
|
60
74
|
return (
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<article className="prev-content">
|
|
67
|
-
<Outlet />
|
|
68
|
-
</article>
|
|
69
|
-
</DocsLayout>
|
|
70
|
-
</TanstackProvider>
|
|
75
|
+
<Layout tree={pageTree}>
|
|
76
|
+
<article className="prev-content">
|
|
77
|
+
<Outlet />
|
|
78
|
+
</article>
|
|
79
|
+
</Layout>
|
|
71
80
|
)
|
|
72
81
|
}
|
|
73
82
|
|
package/src/theme/styles.css
CHANGED
|
@@ -1,8 +1,128 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Prev CLI Theme
|
|
3
|
-
Overrides fumadocs-ui's default theme with warm stone palette
|
|
2
|
+
Prev CLI Theme - Lightweight Documentation Layout
|
|
4
3
|
*/
|
|
5
4
|
|
|
5
|
+
/* Reset */
|
|
6
|
+
*, *::before, *::after {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
font-family: var(--fd-font-sans);
|
|
13
|
+
background: var(--fd-background);
|
|
14
|
+
color: var(--fd-foreground);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Layout structure */
|
|
18
|
+
.prev-layout {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-template-columns: 260px 1fr;
|
|
21
|
+
min-height: 100vh;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Sidebar */
|
|
25
|
+
.prev-sidebar {
|
|
26
|
+
position: sticky;
|
|
27
|
+
top: 0;
|
|
28
|
+
height: 100vh;
|
|
29
|
+
background: var(--fd-background);
|
|
30
|
+
border-right: 1px solid var(--fd-border);
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sidebar-nav {
|
|
37
|
+
flex: 1;
|
|
38
|
+
overflow-y: auto;
|
|
39
|
+
padding: 1rem 0.5rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.sidebar-link {
|
|
43
|
+
display: block;
|
|
44
|
+
padding: 0.5rem 1rem;
|
|
45
|
+
color: var(--fd-muted-foreground);
|
|
46
|
+
text-decoration: none;
|
|
47
|
+
font-size: 0.9rem;
|
|
48
|
+
border-radius: 0.375rem;
|
|
49
|
+
transition: all 0.15s ease;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sidebar-link:hover {
|
|
53
|
+
background: var(--fd-muted);
|
|
54
|
+
color: var(--fd-foreground);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sidebar-link.active {
|
|
58
|
+
background: var(--fd-accent);
|
|
59
|
+
color: var(--fd-accent-foreground);
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sidebar-folder-toggle {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 0.5rem;
|
|
67
|
+
width: 100%;
|
|
68
|
+
padding: 0.5rem 1rem;
|
|
69
|
+
background: none;
|
|
70
|
+
border: none;
|
|
71
|
+
color: var(--fd-foreground);
|
|
72
|
+
font-size: 0.9rem;
|
|
73
|
+
font-weight: 500;
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
border-radius: 0.375rem;
|
|
76
|
+
text-align: left;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.sidebar-folder-toggle:hover {
|
|
80
|
+
background: var(--fd-muted);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.folder-icon {
|
|
84
|
+
display: flex;
|
|
85
|
+
transition: transform 0.15s ease;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.folder-icon.open {
|
|
89
|
+
transform: rotate(90deg);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.sidebar-folder-children {
|
|
93
|
+
margin-left: 0.5rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.sidebar-footer {
|
|
97
|
+
padding: 1rem;
|
|
98
|
+
border-top: 1px solid var(--fd-border);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.theme-toggle {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
width: 36px;
|
|
106
|
+
height: 36px;
|
|
107
|
+
background: var(--fd-muted);
|
|
108
|
+
border: none;
|
|
109
|
+
border-radius: 0.5rem;
|
|
110
|
+
color: var(--fd-muted-foreground);
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
transition: all 0.15s ease;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.theme-toggle:hover {
|
|
116
|
+
background: var(--fd-accent);
|
|
117
|
+
color: var(--fd-accent-foreground);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Main content area */
|
|
121
|
+
.prev-main {
|
|
122
|
+
padding: 2rem;
|
|
123
|
+
overflow-x: hidden;
|
|
124
|
+
}
|
|
125
|
+
|
|
6
126
|
/* Custom fonts */
|
|
7
127
|
:root {
|
|
8
128
|
--fd-font-sans: "DM Sans", system-ui, sans-serif;
|