prev-cli 0.23.0 → 0.24.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/package.json +1 -1
- package/src/theme/Preview.tsx +139 -95
- package/src/theme/mdx-components.tsx +32 -0
- package/src/theme/styles.css +67 -2
package/package.json
CHANGED
package/src/theme/Preview.tsx
CHANGED
|
@@ -22,7 +22,11 @@ const DEVICE_WIDTHS: Record<DeviceMode, number | '100%'> = {
|
|
|
22
22
|
|
|
23
23
|
export function Preview({ src, height = 400, title, mode = 'wasm', showHeader = false }: PreviewProps) {
|
|
24
24
|
const [isFullscreen, setIsFullscreen] = useState(false)
|
|
25
|
-
|
|
25
|
+
// Default to 'mobile' device mode on mobile viewports to match user's actual environment
|
|
26
|
+
const [deviceMode, setDeviceMode] = useState<DeviceMode>(() => {
|
|
27
|
+
if (typeof window === 'undefined') return 'desktop'
|
|
28
|
+
return window.innerWidth < 768 ? 'mobile' : 'desktop'
|
|
29
|
+
})
|
|
26
30
|
const [customWidth, setCustomWidth] = useState<number | null>(null)
|
|
27
31
|
const [showSlider, setShowSlider] = useState(false)
|
|
28
32
|
|
|
@@ -197,95 +201,124 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
197
201
|
</button>
|
|
198
202
|
)
|
|
199
203
|
|
|
204
|
+
// Track viewport size for responsive layout
|
|
205
|
+
const [isMobileViewport, setIsMobileViewport] = useState(typeof window !== 'undefined' ? window.innerWidth < 480 : false)
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
const handleResize = () => setIsMobileViewport(window.innerWidth < 480)
|
|
209
|
+
window.addEventListener('resize', handleResize)
|
|
210
|
+
return () => window.removeEventListener('resize', handleResize)
|
|
211
|
+
}, [])
|
|
212
|
+
|
|
200
213
|
// DevTools in header - device modes, width slider, fullscreen
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
title="Desktop (100%)"
|
|
223
|
-
>
|
|
224
|
-
<Icon name="desktop" size={16} />
|
|
225
|
-
</IconButton>
|
|
226
|
-
|
|
227
|
-
<div style={{ width: '1px', height: '16px', backgroundColor: 'var(--fd-border, #e4e4e7)', margin: '0 4px' }} />
|
|
228
|
-
|
|
229
|
-
{/* Width slider toggle */}
|
|
230
|
-
<div style={{ position: 'relative' }}>
|
|
214
|
+
// On mobile: simplified controls with just fullscreen
|
|
215
|
+
// On larger screens: full device controls
|
|
216
|
+
const DevTools = ({ compact = false }: { compact?: boolean }) => {
|
|
217
|
+
// Mobile viewport or compact mode: show minimal controls
|
|
218
|
+
if (isMobileViewport || compact) {
|
|
219
|
+
return (
|
|
220
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '2px' }}>
|
|
221
|
+
<IconButton
|
|
222
|
+
onClick={() => setIsFullscreen(!isFullscreen)}
|
|
223
|
+
active={isFullscreen}
|
|
224
|
+
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
|
225
|
+
>
|
|
226
|
+
<Icon name={isFullscreen ? 'minimize' : 'maximize'} size={16} />
|
|
227
|
+
</IconButton>
|
|
228
|
+
</div>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Full controls for larger screens
|
|
233
|
+
return (
|
|
234
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '2px' }}>
|
|
231
235
|
<IconButton
|
|
232
|
-
onClick={() =>
|
|
233
|
-
active={
|
|
234
|
-
title="
|
|
236
|
+
onClick={() => handleDeviceChange('mobile')}
|
|
237
|
+
active={deviceMode === 'mobile' && customWidth === null}
|
|
238
|
+
title="Mobile (375px)"
|
|
235
239
|
>
|
|
236
|
-
<Icon name="
|
|
240
|
+
<Icon name="mobile" size={16} />
|
|
237
241
|
</IconButton>
|
|
238
242
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
243
|
+
<IconButton
|
|
244
|
+
onClick={() => handleDeviceChange('tablet')}
|
|
245
|
+
active={deviceMode === 'tablet' && customWidth === null}
|
|
246
|
+
title="Tablet (768px)"
|
|
247
|
+
>
|
|
248
|
+
<Icon name="tablet" size={16} />
|
|
249
|
+
</IconButton>
|
|
250
|
+
|
|
251
|
+
<IconButton
|
|
252
|
+
onClick={() => handleDeviceChange('desktop')}
|
|
253
|
+
active={deviceMode === 'desktop' && customWidth === null}
|
|
254
|
+
title="Desktop (100%)"
|
|
255
|
+
>
|
|
256
|
+
<Icon name="desktop" size={16} />
|
|
257
|
+
</IconButton>
|
|
258
|
+
|
|
259
|
+
<div style={{ width: '1px', height: '16px', backgroundColor: 'var(--fd-border, #e4e4e7)', margin: '0 4px' }} />
|
|
260
|
+
|
|
261
|
+
{/* Width slider toggle */}
|
|
262
|
+
<div style={{ position: 'relative' }}>
|
|
263
|
+
<IconButton
|
|
264
|
+
onClick={() => setShowSlider(!showSlider)}
|
|
265
|
+
active={showSlider || customWidth !== null}
|
|
266
|
+
title="Custom width"
|
|
267
|
+
>
|
|
268
|
+
<Icon name="sliders" size={16} />
|
|
269
|
+
</IconButton>
|
|
270
|
+
|
|
271
|
+
{showSlider && (
|
|
272
|
+
<div style={{
|
|
273
|
+
position: 'absolute',
|
|
274
|
+
top: '100%',
|
|
275
|
+
right: 0,
|
|
276
|
+
marginTop: '8px',
|
|
277
|
+
padding: '12px',
|
|
278
|
+
backgroundColor: 'var(--fd-background, #fff)',
|
|
279
|
+
borderRadius: '8px',
|
|
280
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
|
281
|
+
border: '1px solid var(--fd-border, #e4e4e7)',
|
|
282
|
+
minWidth: '192px',
|
|
283
|
+
zIndex: 100,
|
|
284
|
+
}}>
|
|
285
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
|
|
286
|
+
<span style={{ fontSize: '12px', color: 'var(--fd-muted-foreground, #71717a)' }}>
|
|
287
|
+
Width: {customWidth ?? currentWidth ?? '100%'}px
|
|
288
|
+
</span>
|
|
289
|
+
<button
|
|
290
|
+
onClick={() => setShowSlider(false)}
|
|
291
|
+
style={{ padding: '2px', background: 'none', border: 'none', cursor: 'pointer', color: 'var(--fd-muted-foreground, #a1a1aa)' }}
|
|
292
|
+
>
|
|
293
|
+
<Icon name="x" size={12} />
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
<input
|
|
297
|
+
type="range"
|
|
298
|
+
min={320}
|
|
299
|
+
max={1920}
|
|
300
|
+
value={customWidth ?? (typeof currentWidth === 'number' ? currentWidth : 1920)}
|
|
301
|
+
onChange={(e) => handleSliderChange(parseInt(e.target.value))}
|
|
302
|
+
style={{ width: '100%', accentColor: 'var(--fd-primary, #3b82f6)' }}
|
|
303
|
+
/>
|
|
304
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '12px', color: 'var(--fd-muted-foreground, #a1a1aa)', marginTop: '4px' }}>
|
|
305
|
+
<span>320px</span>
|
|
306
|
+
<span>1920px</span>
|
|
307
|
+
</div>
|
|
275
308
|
</div>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
</div>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
279
311
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
312
|
+
<IconButton
|
|
313
|
+
onClick={() => setIsFullscreen(!isFullscreen)}
|
|
314
|
+
active={isFullscreen}
|
|
315
|
+
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
|
316
|
+
>
|
|
317
|
+
<Icon name={isFullscreen ? 'minimize' : 'maximize'} size={16} />
|
|
318
|
+
</IconButton>
|
|
319
|
+
</div>
|
|
320
|
+
)
|
|
321
|
+
}
|
|
289
322
|
|
|
290
323
|
// Register DevTools in toolbar when on detail page (showHeader mode)
|
|
291
324
|
useEffect(() => {
|
|
@@ -469,34 +502,45 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
|
|
|
469
502
|
overflow: 'hidden',
|
|
470
503
|
position: 'relative',
|
|
471
504
|
}}>
|
|
472
|
-
{/* Compact header */}
|
|
505
|
+
{/* Compact header - responsive for mobile */}
|
|
473
506
|
<div style={{
|
|
474
507
|
display: 'flex',
|
|
475
508
|
alignItems: 'center',
|
|
476
509
|
justifyContent: 'space-between',
|
|
477
|
-
padding: '8px 12px',
|
|
510
|
+
padding: isMobileViewport ? '6px 10px' : '8px 12px',
|
|
478
511
|
backgroundColor: 'var(--fd-card, #fafafa)',
|
|
479
512
|
borderBottom: '1px solid var(--fd-border, #e4e4e7)',
|
|
513
|
+
gap: '8px',
|
|
480
514
|
}}>
|
|
481
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '
|
|
482
|
-
<span style={{
|
|
515
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0, flex: 1 }}>
|
|
516
|
+
<span style={{
|
|
517
|
+
fontSize: isMobileViewport ? '13px' : '14px',
|
|
518
|
+
fontWeight: 500,
|
|
519
|
+
color: 'var(--fd-foreground, #52525b)',
|
|
520
|
+
overflow: 'hidden',
|
|
521
|
+
textOverflow: 'ellipsis',
|
|
522
|
+
whiteSpace: 'nowrap',
|
|
523
|
+
}}>
|
|
483
524
|
{displayTitle}
|
|
484
525
|
</span>
|
|
485
526
|
{mode === 'wasm' && buildStatus === 'building' && (
|
|
486
|
-
<Icon name="loader" size={14} style={{ color: 'var(--fd-primary, #3b82f6)', animation: 'spin 1s linear infinite' }} />
|
|
527
|
+
<Icon name="loader" size={14} style={{ color: 'var(--fd-primary, #3b82f6)', animation: 'spin 1s linear infinite', flexShrink: 0 }} />
|
|
487
528
|
)}
|
|
488
529
|
{mode === 'wasm' && buildStatus === 'error' && (
|
|
489
|
-
<span style={{ fontSize: '12px', color: '#ef4444' }}>Error</span>
|
|
530
|
+
<span style={{ fontSize: '12px', color: '#ef4444', flexShrink: 0 }}>Error</span>
|
|
490
531
|
)}
|
|
491
532
|
</div>
|
|
492
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '
|
|
493
|
-
{
|
|
533
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexShrink: 0 }}>
|
|
534
|
+
{/* Hide build time and width on mobile to save space */}
|
|
535
|
+
{!isMobileViewport && mode === 'wasm' && buildTime && (
|
|
494
536
|
<span style={{ fontSize: '12px', color: 'var(--fd-muted-foreground, #a1a1aa)' }}>{buildTime}ms</span>
|
|
495
537
|
)}
|
|
496
|
-
|
|
497
|
-
{
|
|
498
|
-
|
|
499
|
-
|
|
538
|
+
{!isMobileViewport && (
|
|
539
|
+
<span style={{ fontSize: '12px', color: 'var(--fd-muted-foreground, #a1a1aa)' }}>
|
|
540
|
+
{currentWidth ? `${currentWidth}px` : '100%'}
|
|
541
|
+
</span>
|
|
542
|
+
)}
|
|
543
|
+
<DevTools compact={isMobileViewport} />
|
|
500
544
|
</div>
|
|
501
545
|
</div>
|
|
502
546
|
|
|
@@ -73,7 +73,39 @@ function MdxLink({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAn
|
|
|
73
73
|
)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Responsive table wrapper for horizontal scrolling on mobile
|
|
77
|
+
function MdxTable({ children, ...props }: React.TableHTMLAttributes<HTMLTableElement>) {
|
|
78
|
+
const wrapperRef = React.useRef<HTMLDivElement>(null)
|
|
79
|
+
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
const wrapper = wrapperRef.current
|
|
82
|
+
if (!wrapper) return
|
|
83
|
+
|
|
84
|
+
// Check if table overflows and add class for scroll indicator
|
|
85
|
+
const checkOverflow = () => {
|
|
86
|
+
const hasOverflow = wrapper.scrollWidth > wrapper.clientWidth
|
|
87
|
+
wrapper.classList.toggle('has-scroll', hasOverflow && wrapper.scrollLeft < wrapper.scrollWidth - wrapper.clientWidth - 1)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
checkOverflow()
|
|
91
|
+
wrapper.addEventListener('scroll', checkOverflow)
|
|
92
|
+
window.addEventListener('resize', checkOverflow)
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
wrapper.removeEventListener('scroll', checkOverflow)
|
|
96
|
+
window.removeEventListener('resize', checkOverflow)
|
|
97
|
+
}
|
|
98
|
+
}, [])
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div ref={wrapperRef} className="table-wrapper">
|
|
102
|
+
<table {...props}>{children}</table>
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
76
107
|
export const mdxComponents = {
|
|
77
108
|
Preview,
|
|
78
109
|
a: MdxLink,
|
|
110
|
+
table: MdxTable,
|
|
79
111
|
}
|
package/src/theme/styles.css
CHANGED
|
@@ -214,6 +214,27 @@ body {
|
|
|
214
214
|
padding-bottom: 3rem !important;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/* Mobile: reduce content padding for better use of screen space */
|
|
218
|
+
@media (max-width: 640px) {
|
|
219
|
+
.prev-content {
|
|
220
|
+
padding-left: 1rem !important;
|
|
221
|
+
padding-right: 1rem !important;
|
|
222
|
+
font-size: 0.9375rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.prev-content h1 {
|
|
226
|
+
font-size: 1.75rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.prev-content h2 {
|
|
230
|
+
font-size: 1.25rem;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.prev-content h3 {
|
|
234
|
+
font-size: 1.125rem;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
217
238
|
/* Heading styles with better spacing */
|
|
218
239
|
.prev-content h1 {
|
|
219
240
|
font-size: 2.25rem;
|
|
@@ -340,14 +361,57 @@ body {
|
|
|
340
361
|
color: var(--fd-foreground);
|
|
341
362
|
}
|
|
342
363
|
|
|
343
|
-
/* Table styling */
|
|
344
|
-
.prev-content table {
|
|
364
|
+
/* Table styling - responsive with horizontal scroll on mobile */
|
|
365
|
+
.prev-content .table-wrapper {
|
|
345
366
|
width: 100%;
|
|
346
367
|
margin: 1.75rem 0;
|
|
368
|
+
overflow-x: auto;
|
|
369
|
+
-webkit-overflow-scrolling: touch;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Scroll indicator gradient for tables on mobile */
|
|
373
|
+
@media (max-width: 640px) {
|
|
374
|
+
.prev-content .table-wrapper {
|
|
375
|
+
position: relative;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.prev-content .table-wrapper::after {
|
|
379
|
+
content: '';
|
|
380
|
+
position: absolute;
|
|
381
|
+
top: 0;
|
|
382
|
+
right: 0;
|
|
383
|
+
bottom: 0;
|
|
384
|
+
width: 24px;
|
|
385
|
+
background: linear-gradient(to right, transparent, var(--fd-background));
|
|
386
|
+
pointer-events: none;
|
|
387
|
+
opacity: 0;
|
|
388
|
+
transition: opacity 0.2s ease;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.prev-content .table-wrapper.has-scroll::after {
|
|
392
|
+
opacity: 1;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.prev-content table {
|
|
397
|
+
width: 100%;
|
|
398
|
+
min-width: max-content;
|
|
347
399
|
border-collapse: collapse;
|
|
348
400
|
font-size: 0.925rem;
|
|
349
401
|
}
|
|
350
402
|
|
|
403
|
+
/* Mobile: smaller table font and padding */
|
|
404
|
+
@media (max-width: 640px) {
|
|
405
|
+
.prev-content table {
|
|
406
|
+
font-size: 0.85rem;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.prev-content th,
|
|
410
|
+
.prev-content td {
|
|
411
|
+
padding: 0.625rem 0.75rem;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
351
415
|
.prev-content thead {
|
|
352
416
|
border-bottom: 2px solid var(--fd-border);
|
|
353
417
|
}
|
|
@@ -357,6 +421,7 @@ body {
|
|
|
357
421
|
text-align: left;
|
|
358
422
|
font-weight: 600;
|
|
359
423
|
background: var(--fd-muted);
|
|
424
|
+
white-space: nowrap;
|
|
360
425
|
}
|
|
361
426
|
|
|
362
427
|
.prev-content th:first-child {
|