prev-cli 0.19.2 → 0.21.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 +1 -1
- package/package.json +5 -4
- package/src/theme/DevToolsContext.tsx +39 -0
- package/src/theme/Preview.tsx +27 -11
- package/src/theme/Toolbar.css +41 -1
- package/src/theme/Toolbar.tsx +8 -2
- package/src/theme/entry.tsx +114 -12
- package/src/theme/styles.css +36 -0
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createServer as createServer2, build as build2, preview } from "vite";
|
|
|
11
11
|
|
|
12
12
|
// src/vite/config.ts
|
|
13
13
|
import { createLogger } from "vite";
|
|
14
|
-
import react from "@vitejs/plugin-react
|
|
14
|
+
import react from "@vitejs/plugin-react";
|
|
15
15
|
import mdx from "@mdx-js/rollup";
|
|
16
16
|
import remarkGfm from "remark-gfm";
|
|
17
17
|
import rehypeHighlight from "rehype-highlight";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prev-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Transform MDX directories into beautiful documentation websites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,9 +45,10 @@
|
|
|
45
45
|
"@tailwindcss/vite": "^4.0.0",
|
|
46
46
|
"@tanstack/react-router": "^1.145.7",
|
|
47
47
|
"@terrastruct/d2": "^0.1.33",
|
|
48
|
-
"@vitejs/plugin-react
|
|
48
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
49
49
|
"class-variance-authority": "^0.7.0",
|
|
50
50
|
"clsx": "^2.1.0",
|
|
51
|
+
"esbuild": "^0.27.2",
|
|
51
52
|
"fast-glob": "^3.3.0",
|
|
52
53
|
"fumadocs-core": "^16.4.3",
|
|
53
54
|
"fumadocs-ui": "^16.4.3",
|
|
@@ -59,9 +60,9 @@
|
|
|
59
60
|
"react-router-dom": "^7.0.0",
|
|
60
61
|
"rehype-highlight": "^7.0.0",
|
|
61
62
|
"remark-gfm": "^4.0.0",
|
|
62
|
-
"rolldown-vite": "^7.3.0",
|
|
63
63
|
"tailwind-merge": "^2.5.0",
|
|
64
|
-
"tailwindcss": "^4.0.0"
|
|
64
|
+
"tailwindcss": "^4.0.0",
|
|
65
|
+
"vite": "npm:rolldown-vite@^7.3.1"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
68
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
interface DevToolsContextValue {
|
|
4
|
+
devToolsContent: React.ReactNode | null
|
|
5
|
+
setDevToolsContent: (content: React.ReactNode | null) => void
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const DevToolsContext = createContext<DevToolsContextValue | null>(null)
|
|
9
|
+
|
|
10
|
+
export function DevToolsProvider({ children }: { children: React.ReactNode }) {
|
|
11
|
+
const [devToolsContent, setDevToolsContent] = useState<React.ReactNode | null>(null)
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<DevToolsContext.Provider value={{ devToolsContent, setDevToolsContent }}>
|
|
15
|
+
{children}
|
|
16
|
+
</DevToolsContext.Provider>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Fallback for when context isn't available (e.g., SSR or standalone usage)
|
|
21
|
+
const fallbackContext: DevToolsContextValue = {
|
|
22
|
+
devToolsContent: null,
|
|
23
|
+
setDevToolsContent: () => {},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useDevTools() {
|
|
27
|
+
const context = useContext(DevToolsContext)
|
|
28
|
+
// Return fallback instead of throwing - safer for SSR and edge cases
|
|
29
|
+
return context ?? fallbackContext
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useRegisterDevTools(content: React.ReactNode | null, deps: any[] = []) {
|
|
33
|
+
const { setDevToolsContent } = useDevTools()
|
|
34
|
+
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
setDevToolsContent(content)
|
|
37
|
+
return () => setDevToolsContent(null)
|
|
38
|
+
}, deps)
|
|
39
|
+
}
|
package/src/theme/Preview.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react'
|
|
2
2
|
import { Link } from '@tanstack/react-router'
|
|
3
3
|
import { Icon, IconSprite } from './icons'
|
|
4
|
+
import { useDevTools } from './DevToolsContext'
|
|
4
5
|
import type { PreviewConfig, PreviewMessage, BuildResult } from '../preview-runtime/types'
|
|
5
6
|
|
|
6
7
|
interface PreviewProps {
|
|
@@ -32,8 +33,16 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
32
33
|
|
|
33
34
|
const iframeRef = useRef<HTMLIFrameElement>(null)
|
|
34
35
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
36
|
+
// Get devtools context for toolbar integration
|
|
37
|
+
const { setDevToolsContent } = useDevTools()
|
|
38
|
+
|
|
39
|
+
// In production, always use pre-built static previews
|
|
40
|
+
// In dev, use WASM runtime for live bundling
|
|
41
|
+
const isDev = import.meta.env?.DEV ?? false
|
|
42
|
+
const effectiveMode = isDev ? mode : 'legacy'
|
|
43
|
+
|
|
44
|
+
// URL depends on mode - wasm mode needs src param, legacy uses pre-built files
|
|
45
|
+
const previewUrl = effectiveMode === 'wasm' ? `/_preview-runtime?src=${src}` : `/_preview/${src}/`
|
|
37
46
|
const displayTitle = title || src
|
|
38
47
|
|
|
39
48
|
// Calculate current width
|
|
@@ -89,9 +98,9 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
89
98
|
}
|
|
90
99
|
}, [isDarkMode])
|
|
91
100
|
|
|
92
|
-
// WASM preview: Initialize and send config to iframe
|
|
101
|
+
// WASM preview: Initialize and send config to iframe (dev mode only)
|
|
93
102
|
useEffect(() => {
|
|
94
|
-
if (
|
|
103
|
+
if (effectiveMode !== 'wasm') return
|
|
95
104
|
|
|
96
105
|
const iframe = iframeRef.current
|
|
97
106
|
if (!iframe) return
|
|
@@ -141,8 +150,8 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
141
150
|
}
|
|
142
151
|
}, [mode, src])
|
|
143
152
|
|
|
144
|
-
const handleDeviceChange = (
|
|
145
|
-
setDeviceMode(
|
|
153
|
+
const handleDeviceChange = (newMode: DeviceMode) => {
|
|
154
|
+
setDeviceMode(newMode)
|
|
146
155
|
setCustomWidth(null)
|
|
147
156
|
setShowSlider(false)
|
|
148
157
|
}
|
|
@@ -152,7 +161,7 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
152
161
|
setDeviceMode('desktop')
|
|
153
162
|
}
|
|
154
163
|
|
|
155
|
-
// Icon button component
|
|
164
|
+
// Icon button component for devtools
|
|
156
165
|
const IconButton = ({
|
|
157
166
|
onClick,
|
|
158
167
|
active,
|
|
@@ -176,7 +185,7 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
176
185
|
justifyContent: 'center',
|
|
177
186
|
transition: 'background-color 0.15s, color 0.15s',
|
|
178
187
|
backgroundColor: active ? 'var(--fd-primary, #3b82f6)' : 'transparent',
|
|
179
|
-
color: active ? '#fff' : 'var(--fd-muted-foreground, #71717a)',
|
|
188
|
+
color: active ? 'var(--fd-primary-foreground, #fff)' : 'var(--fd-muted-foreground, #71717a)',
|
|
180
189
|
}}
|
|
181
190
|
title={btnTitle}
|
|
182
191
|
aria-label={btnTitle}
|
|
@@ -275,6 +284,14 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
275
284
|
</div>
|
|
276
285
|
)
|
|
277
286
|
|
|
287
|
+
// Register DevTools in toolbar when on detail page (showHeader mode)
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (showHeader && !isFullscreen) {
|
|
290
|
+
setDevToolsContent(<DevTools />)
|
|
291
|
+
return () => setDevToolsContent(null)
|
|
292
|
+
}
|
|
293
|
+
}, [showHeader, isFullscreen, deviceMode, customWidth, showSlider])
|
|
294
|
+
|
|
278
295
|
// Calculate iframe style
|
|
279
296
|
const getIframeContainerStyle = (): React.CSSProperties => {
|
|
280
297
|
if (currentWidth === null) {
|
|
@@ -346,10 +363,11 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
346
363
|
}
|
|
347
364
|
|
|
348
365
|
// Non-fullscreen with showHeader (individual preview page)
|
|
366
|
+
// DevTools are rendered in the toolbar via context, not in this header
|
|
349
367
|
if (showHeader) {
|
|
350
368
|
return (
|
|
351
369
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
352
|
-
{/* Header with back button and
|
|
370
|
+
{/* Header with back button and build status - DevTools are in toolbar */}
|
|
353
371
|
<div style={{
|
|
354
372
|
display: 'flex',
|
|
355
373
|
alignItems: 'center',
|
|
@@ -357,7 +375,6 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
357
375
|
padding: '12px 16px',
|
|
358
376
|
backgroundColor: 'var(--fd-card, #fafafa)',
|
|
359
377
|
borderBottom: '1px solid var(--fd-border, #e4e4e7)',
|
|
360
|
-
borderRadius: '8px 8px 0 0',
|
|
361
378
|
}}>
|
|
362
379
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
363
380
|
<Link
|
|
@@ -385,7 +402,6 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
385
402
|
<span style={{ fontSize: '12px', color: 'var(--fd-muted-foreground, #a1a1aa)' }}>{buildTime}ms</span>
|
|
386
403
|
)}
|
|
387
404
|
</div>
|
|
388
|
-
<DevTools />
|
|
389
405
|
</div>
|
|
390
406
|
|
|
391
407
|
{/* Build error display */}
|
package/src/theme/Toolbar.css
CHANGED
|
@@ -43,13 +43,53 @@
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
.toolbar-devtools-slot {
|
|
46
|
-
display:
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 0.125rem;
|
|
49
|
+
padding-left: 0.25rem;
|
|
50
|
+
margin-left: 0.25rem;
|
|
51
|
+
border-left: 1px solid var(--fd-border);
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
.toolbar-devtools-slot:empty {
|
|
50
55
|
display: none;
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
/* Style devtools buttons in toolbar */
|
|
59
|
+
.toolbar-devtools-slot button {
|
|
60
|
+
width: 32px;
|
|
61
|
+
height: 32px;
|
|
62
|
+
padding: 4px;
|
|
63
|
+
border-radius: 50%;
|
|
64
|
+
background: transparent;
|
|
65
|
+
border: none;
|
|
66
|
+
color: var(--fd-muted-foreground);
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
transition: all 0.15s ease;
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.toolbar-devtools-slot button:hover {
|
|
75
|
+
background: var(--fd-muted);
|
|
76
|
+
color: var(--fd-foreground);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Override active state for toolbar devtools buttons */
|
|
80
|
+
.toolbar-devtools-slot button[style*="background-color: var(--fd-primary"] {
|
|
81
|
+
background: var(--fd-accent) !important;
|
|
82
|
+
color: var(--fd-accent-foreground) !important;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Adjust slider dropdown position when in toolbar (opens upward) */
|
|
86
|
+
.toolbar-devtools-slot [style*="position: absolute"] {
|
|
87
|
+
bottom: 100% !important;
|
|
88
|
+
top: auto !important;
|
|
89
|
+
margin-bottom: 8px !important;
|
|
90
|
+
margin-top: 0 !important;
|
|
91
|
+
}
|
|
92
|
+
|
|
53
93
|
/* Hide width toggle on mobile */
|
|
54
94
|
@media (max-width: 768px) {
|
|
55
95
|
.toolbar-btn.desktop-only {
|
package/src/theme/Toolbar.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { Link, useLocation } from '@tanstack/react-router'
|
|
|
3
3
|
import type { PageTree } from 'fumadocs-core/server'
|
|
4
4
|
import { previews } from 'virtual:prev-previews'
|
|
5
5
|
import { Icon } from './icons'
|
|
6
|
+
import { useDevTools } from './DevToolsContext'
|
|
6
7
|
import './Toolbar.css'
|
|
7
8
|
|
|
8
9
|
interface ToolbarProps {
|
|
@@ -22,6 +23,7 @@ export function Toolbar({ tree, onThemeToggle, onWidthToggle, isDark, isFullWidt
|
|
|
22
23
|
const toolbarRef = useRef<HTMLDivElement>(null)
|
|
23
24
|
const location = useLocation()
|
|
24
25
|
const isOnPreviews = location.pathname.startsWith('/previews')
|
|
26
|
+
const { devToolsContent } = useDevTools()
|
|
25
27
|
|
|
26
28
|
const handleMouseDown = (e: React.MouseEvent) => {
|
|
27
29
|
if ((e.target as HTMLElement).closest('button, a')) return
|
|
@@ -70,8 +72,12 @@ export function Toolbar({ tree, onThemeToggle, onWidthToggle, isDark, isFullWidt
|
|
|
70
72
|
</Link>
|
|
71
73
|
)}
|
|
72
74
|
|
|
73
|
-
{/* Contextual devtools
|
|
74
|
-
|
|
75
|
+
{/* Contextual devtools - rendered from preview context */}
|
|
76
|
+
{devToolsContent && (
|
|
77
|
+
<div className="toolbar-devtools-slot">
|
|
78
|
+
{devToolsContent}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
75
81
|
|
|
76
82
|
<button
|
|
77
83
|
className="toolbar-btn desktop-only"
|
package/src/theme/entry.tsx
CHANGED
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
Outlet,
|
|
9
9
|
redirect,
|
|
10
10
|
Navigate,
|
|
11
|
+
useLocation,
|
|
12
|
+
useParams,
|
|
13
|
+
Link,
|
|
11
14
|
} from '@tanstack/react-router'
|
|
12
15
|
import { MDXProvider } from '@mdx-js/react'
|
|
13
16
|
import { pages, sidebar } from 'virtual:prev-pages'
|
|
@@ -17,6 +20,7 @@ import { useDiagrams } from './diagrams'
|
|
|
17
20
|
import { Layout } from './Layout'
|
|
18
21
|
import { MetadataBlock } from './MetadataBlock'
|
|
19
22
|
import { mdxComponents } from './mdx-components'
|
|
23
|
+
import { DevToolsProvider } from './DevToolsContext'
|
|
20
24
|
import './styles.css'
|
|
21
25
|
|
|
22
26
|
// PageTree types (simplified from fumadocs-core)
|
|
@@ -151,10 +155,61 @@ function PreviewsCatalog() {
|
|
|
151
155
|
)
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
// Individual preview card - clickable thumbnail
|
|
155
|
-
import {
|
|
158
|
+
// Individual preview card - clickable thumbnail with WASM preview communication
|
|
159
|
+
import type { PreviewConfig, PreviewMessage } from '../preview-runtime/types'
|
|
156
160
|
|
|
157
161
|
function PreviewCard({ name }: { name: string }) {
|
|
162
|
+
const iframeRef = React.useRef<HTMLIFrameElement>(null)
|
|
163
|
+
const [isLoaded, setIsLoaded] = React.useState(false)
|
|
164
|
+
|
|
165
|
+
// In production, use pre-built static files; in dev, use WASM runtime
|
|
166
|
+
const isDev = import.meta.env?.DEV ?? false
|
|
167
|
+
const previewUrl = isDev ? `/_preview-runtime?src=${name}` : `/_preview/${name}/`
|
|
168
|
+
|
|
169
|
+
// Set up WASM preview communication for thumbnail (dev mode only)
|
|
170
|
+
React.useEffect(() => {
|
|
171
|
+
if (!isDev) {
|
|
172
|
+
// In production, just mark as loaded when iframe loads
|
|
173
|
+
const iframe = iframeRef.current
|
|
174
|
+
if (iframe) {
|
|
175
|
+
iframe.onload = () => setIsLoaded(true)
|
|
176
|
+
}
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const iframe = iframeRef.current
|
|
181
|
+
if (!iframe) return
|
|
182
|
+
|
|
183
|
+
let configSent = false
|
|
184
|
+
|
|
185
|
+
const handleMessage = (event: MessageEvent) => {
|
|
186
|
+
const msg = event.data as PreviewMessage
|
|
187
|
+
|
|
188
|
+
if (msg.type === 'ready' && !configSent) {
|
|
189
|
+
configSent = true
|
|
190
|
+
|
|
191
|
+
fetch(`/_preview-config/${name}`)
|
|
192
|
+
.then(res => res.json())
|
|
193
|
+
.then((config: PreviewConfig) => {
|
|
194
|
+
iframe.contentWindow?.postMessage({ type: 'init', config } as PreviewMessage, '*')
|
|
195
|
+
})
|
|
196
|
+
.catch(() => {
|
|
197
|
+
// Silently fail for thumbnails
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (msg.type === 'built') {
|
|
202
|
+
setIsLoaded(true)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
window.addEventListener('message', handleMessage)
|
|
207
|
+
|
|
208
|
+
return () => {
|
|
209
|
+
window.removeEventListener('message', handleMessage)
|
|
210
|
+
}
|
|
211
|
+
}, [name, isDev])
|
|
212
|
+
|
|
158
213
|
return (
|
|
159
214
|
<Link
|
|
160
215
|
to={`/previews/${name}`}
|
|
@@ -185,19 +240,40 @@ function PreviewCard({ name }: { name: string }) {
|
|
|
185
240
|
backgroundColor: 'var(--fd-muted)',
|
|
186
241
|
pointerEvents: 'none',
|
|
187
242
|
}}>
|
|
243
|
+
{/* Loading spinner */}
|
|
244
|
+
{!isLoaded && (
|
|
245
|
+
<div style={{
|
|
246
|
+
position: 'absolute',
|
|
247
|
+
inset: 0,
|
|
248
|
+
display: 'flex',
|
|
249
|
+
alignItems: 'center',
|
|
250
|
+
justifyContent: 'center',
|
|
251
|
+
backgroundColor: 'var(--fd-muted)',
|
|
252
|
+
zIndex: 1,
|
|
253
|
+
}}>
|
|
254
|
+
<div style={{
|
|
255
|
+
width: '24px',
|
|
256
|
+
height: '24px',
|
|
257
|
+
border: '2px solid var(--fd-border)',
|
|
258
|
+
borderTopColor: 'var(--fd-primary)',
|
|
259
|
+
borderRadius: '50%',
|
|
260
|
+
animation: 'spin 1s linear infinite',
|
|
261
|
+
}} />
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
188
264
|
<iframe
|
|
189
|
-
|
|
265
|
+
ref={iframeRef}
|
|
266
|
+
src={previewUrl}
|
|
190
267
|
style={{
|
|
191
|
-
width: '100%',
|
|
192
|
-
height: '100%',
|
|
193
268
|
border: 'none',
|
|
194
269
|
transform: 'scale(0.5)',
|
|
195
270
|
transformOrigin: 'top left',
|
|
196
271
|
width: '200%',
|
|
197
272
|
height: '200%',
|
|
273
|
+
opacity: isLoaded ? 1 : 0,
|
|
274
|
+
transition: 'opacity 0.3s',
|
|
198
275
|
}}
|
|
199
276
|
title={name}
|
|
200
|
-
loading="lazy"
|
|
201
277
|
/>
|
|
202
278
|
</div>
|
|
203
279
|
{/* Card footer */}
|
|
@@ -221,15 +297,37 @@ function PreviewCard({ name }: { name: string }) {
|
|
|
221
297
|
)
|
|
222
298
|
}
|
|
223
299
|
|
|
224
|
-
// Individual preview page - full view with devtools in
|
|
300
|
+
// Individual preview page - full view with devtools in toolbar
|
|
225
301
|
function PreviewPage() {
|
|
226
|
-
const
|
|
227
|
-
|
|
302
|
+
const params = useParams({ strict: false })
|
|
303
|
+
// Splat param captures the full path after /previews/
|
|
304
|
+
const name = (params as any)['_splat'] || (params as any)['*'] || params.name as string
|
|
305
|
+
|
|
306
|
+
if (!name) {
|
|
307
|
+
return <Navigate to="/previews" />
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<div className="preview-detail-page">
|
|
312
|
+
<Preview src={name} height="100%" showHeader />
|
|
313
|
+
</div>
|
|
314
|
+
)
|
|
228
315
|
}
|
|
229
316
|
|
|
230
317
|
// Root layout with custom lightweight Layout
|
|
231
318
|
function RootLayout() {
|
|
232
319
|
const pageTree = convertToPageTree(sidebar)
|
|
320
|
+
const location = useLocation()
|
|
321
|
+
const isPreviewDetail = location.pathname.startsWith('/previews/') && location.pathname !== '/previews'
|
|
322
|
+
|
|
323
|
+
// Preview detail page gets full viewport layout
|
|
324
|
+
if (isPreviewDetail) {
|
|
325
|
+
return (
|
|
326
|
+
<Layout tree={pageTree}>
|
|
327
|
+
<Outlet />
|
|
328
|
+
</Layout>
|
|
329
|
+
)
|
|
330
|
+
}
|
|
233
331
|
|
|
234
332
|
return (
|
|
235
333
|
<Layout tree={pageTree}>
|
|
@@ -252,10 +350,10 @@ const previewsRoute = createRoute({
|
|
|
252
350
|
component: PreviewsCatalog,
|
|
253
351
|
})
|
|
254
352
|
|
|
255
|
-
// Individual preview route
|
|
353
|
+
// Individual preview route (uses splat to capture nested paths like buttons/primary)
|
|
256
354
|
const previewDetailRoute = createRoute({
|
|
257
355
|
getParentRoute: () => rootRoute,
|
|
258
|
-
path: '/previews/$
|
|
356
|
+
path: '/previews/$',
|
|
259
357
|
component: PreviewPage,
|
|
260
358
|
})
|
|
261
359
|
|
|
@@ -307,5 +405,9 @@ const router = createRouter({
|
|
|
307
405
|
const container = document.getElementById('root')
|
|
308
406
|
if (container) {
|
|
309
407
|
const root = createRoot(container)
|
|
310
|
-
root.render(
|
|
408
|
+
root.render(
|
|
409
|
+
<DevToolsProvider>
|
|
410
|
+
<RouterProvider router={router} />
|
|
411
|
+
</DevToolsProvider>
|
|
412
|
+
)
|
|
311
413
|
}
|
package/src/theme/styles.css
CHANGED
|
@@ -523,6 +523,13 @@ body {
|
|
|
523
523
|
max-width: 100%;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
+
/* Mobile: add bottom padding for floating toolbar */
|
|
527
|
+
@media (max-width: 768px) {
|
|
528
|
+
.prev-main-floating {
|
|
529
|
+
padding-bottom: 5rem;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
526
533
|
.prev-main-floating .prev-content {
|
|
527
534
|
max-width: 72ch;
|
|
528
535
|
margin: 0 auto;
|
|
@@ -531,3 +538,32 @@ body {
|
|
|
531
538
|
.full-width .prev-main-floating .prev-content {
|
|
532
539
|
max-width: none;
|
|
533
540
|
}
|
|
541
|
+
|
|
542
|
+
/* Preview detail page - full available space */
|
|
543
|
+
.preview-detail-page {
|
|
544
|
+
height: calc(100vh - 4rem);
|
|
545
|
+
padding: 0;
|
|
546
|
+
margin: -2rem; /* Negate parent padding */
|
|
547
|
+
margin-bottom: 3rem; /* Space for toolbar */
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.preview-detail-page > div {
|
|
551
|
+
height: 100%;
|
|
552
|
+
border-radius: 0;
|
|
553
|
+
overflow: hidden;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
@media (min-width: 769px) {
|
|
557
|
+
.preview-detail-page {
|
|
558
|
+
margin: -2rem;
|
|
559
|
+
margin-bottom: 0;
|
|
560
|
+
height: calc(100vh - 2rem);
|
|
561
|
+
padding: 1rem;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.preview-detail-page > div {
|
|
565
|
+
border-radius: 12px;
|
|
566
|
+
border: 1px solid var(--fd-border);
|
|
567
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
|
568
|
+
}
|
|
569
|
+
}
|