@fragments-sdk/cli 0.5.2 → 0.6.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/bin.js +712 -39
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
- package/dist/chunk-6JBGU74P.js.map +1 -0
- package/dist/{chunk-U4GQ2JTD.js → chunk-D35RGPAG.js} +412 -35
- package/dist/chunk-D35RGPAG.js.map +1 -0
- package/dist/{chunk-XNWDI6UT.js → chunk-F7ITZPDJ.js} +5 -5
- package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
- package/dist/{chunk-V7YLRR4C.js → chunk-Q7GOHVOK.js} +3 -3
- package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
- package/dist/{chunk-2H2JAA3U.js → chunk-SSLQXHNX.js} +3 -3
- package/dist/{core-DKHB7FYV.js → core-SKRPJQZG.js} +4 -4
- package/dist/{generate-KL24VZVD.js → generate-7AF7WRVK.js} +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/{init-NION5S3M.js → init-WKGDPYI4.js} +5 -5
- package/dist/mcp-bin.js +8 -220
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-K6JNMCGM.js +12 -0
- package/dist/{service-RWUMZ3EW.js → service-F3E4JJM7.js} +5 -5
- package/dist/static-viewer-4LQZ5AGA.js +12 -0
- package/dist/{test-ECPEXFDN.js → test-CJDNJTPZ.js} +4 -4
- package/dist/{tokens-ITADYVPF.js → tokens-JAJABYXP.js} +6 -6
- package/dist/viewer-R3Q6WAMJ.js +1822 -0
- package/dist/viewer-R3Q6WAMJ.js.map +1 -0
- package/package.json +5 -4
- package/src/bin.ts +8 -0
- package/src/build.ts +104 -13
- package/src/cli-commands.ts +18 -0
- package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
- package/src/commands/a11y-report.ts +625 -0
- package/src/commands/a11y.ts +168 -14
- package/src/commands/build.ts +16 -0
- package/src/core/auto-props.ts +464 -0
- package/src/core/schema.ts +2 -0
- package/src/core/types.ts +3 -1
- package/src/index.ts +4 -0
- package/src/mcp/server.ts +13 -220
- package/src/theme/__tests__/component-contrast.test.ts +338 -0
- package/src/theme/__tests__/contrast-validation.test.ts +326 -0
- package/src/theme/contrast.test.ts +331 -0
- package/src/theme/contrast.ts +246 -0
- package/src/theme/generator.ts +213 -1
- package/src/theme/index.ts +16 -0
- package/src/theme/types.ts +51 -0
- package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
- package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
- package/src/viewer/components/AccessibilityPanel.tsx +493 -433
- package/src/viewer/components/ActionCapture.tsx +1 -1
- package/src/viewer/components/ActionsPanel.tsx +142 -183
- package/src/viewer/components/App.tsx +159 -164
- package/src/viewer/components/BottomPanel.tsx +40 -80
- package/src/viewer/components/CodePanel.tsx +9 -87
- package/src/viewer/components/CommandPalette.tsx +117 -74
- package/src/viewer/components/ComponentGraph.tsx +143 -126
- package/src/viewer/components/ComponentHeader.tsx +46 -43
- package/src/viewer/components/ContractPanel.tsx +124 -117
- package/src/viewer/components/ErrorBoundary.tsx +47 -35
- package/src/viewer/components/FigmaEmbed.tsx +18 -13
- package/src/viewer/components/FragmentEditor.tsx +126 -63
- package/src/viewer/components/HealthDashboard.tsx +146 -171
- package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
- package/src/viewer/components/Icons.tsx +99 -98
- package/src/viewer/components/InteractionsPanel.tsx +317 -264
- package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
- package/src/viewer/components/IsolatedRender.tsx +12 -6
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
- package/src/viewer/components/LandingPage.tsx +285 -305
- package/src/viewer/components/Layout.tsx +7 -9
- package/src/viewer/components/LeftSidebar.tsx +78 -108
- package/src/viewer/components/MultiViewportPreview.tsx +254 -63
- package/src/viewer/components/PreviewArea.tsx +113 -44
- package/src/viewer/components/PreviewFrameHost.tsx +6 -5
- package/src/viewer/components/PreviewPane.tsx +2 -3
- package/src/viewer/components/PreviewToolbar.tsx +61 -104
- package/src/viewer/components/PropsEditor.tsx +154 -74
- package/src/viewer/components/PropsTable.tsx +95 -82
- package/src/viewer/components/RelationsSection.tsx +71 -40
- package/src/viewer/components/ResizablePanel.tsx +158 -55
- package/src/viewer/components/RightSidebar.tsx +46 -56
- package/src/viewer/components/ScreenshotButton.tsx +12 -12
- package/src/viewer/components/SkeletonLoader.tsx +99 -83
- package/src/viewer/components/StoryRenderer.tsx +4 -11
- package/src/viewer/components/Toast.tsx +3 -67
- package/src/viewer/components/TokenStylePanel.tsx +136 -118
- package/src/viewer/components/UsageSection.tsx +26 -26
- package/src/viewer/components/VariantMatrix.tsx +140 -47
- package/src/viewer/components/VariantTabs.tsx +24 -68
- package/src/viewer/components/ViewportSelector.tsx +106 -110
- package/src/viewer/constants/ui.ts +19 -18
- package/src/viewer/entry.tsx +8 -3
- package/src/viewer/index.ts +3 -6
- package/src/viewer/preview-frame.html +21 -5
- package/src/viewer/server.ts +7 -16
- package/src/viewer/styles/globals.css +4 -4
- package/src/viewer/utils/a11y-fixes.ts +53 -30
- package/dist/chunk-ICAIQ57V.js.map +0 -1
- package/dist/chunk-U4GQ2JTD.js.map +0 -1
- package/dist/scan-ESEXV7LF.js +0 -12
- package/dist/static-viewer-O37MJ5B6.js +0 -12
- package/dist/viewer-YDGFDTK5.js +0 -11104
- package/dist/viewer-YDGFDTK5.js.map +0 -1
- package/src/viewer/postcss.config.js +0 -6
- package/src/viewer/tailwind.config.js +0 -37
- /package/dist/{chunk-XNWDI6UT.js.map → chunk-F7ITZPDJ.js.map} +0 -0
- /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
- /package/dist/{chunk-V7YLRR4C.js.map → chunk-Q7GOHVOK.js.map} +0 -0
- /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
- /package/dist/{chunk-2H2JAA3U.js.map → chunk-SSLQXHNX.js.map} +0 -0
- /package/dist/{core-DKHB7FYV.js.map → core-SKRPJQZG.js.map} +0 -0
- /package/dist/{generate-KL24VZVD.js.map → generate-7AF7WRVK.js.map} +0 -0
- /package/dist/{init-NION5S3M.js.map → init-WKGDPYI4.js.map} +0 -0
- /package/dist/{scan-ESEXV7LF.js.map → scan-K6JNMCGM.js.map} +0 -0
- /package/dist/{service-RWUMZ3EW.js.map → service-F3E4JJM7.js.map} +0 -0
- /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-4LQZ5AGA.js.map} +0 -0
- /package/dist/{test-ECPEXFDN.js.map → test-CJDNJTPZ.js.map} +0 -0
- /package/dist/{tokens-ITADYVPF.js.map → tokens-JAJABYXP.js.map} +0 -0
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
|
7
7
|
import type { SegmentDefinition } from "../../core/index.js";
|
|
8
|
-
import clsx from "clsx";
|
|
9
8
|
|
|
10
9
|
// Layout & Navigation
|
|
11
10
|
import { Layout } from "./Layout.js";
|
|
@@ -13,7 +12,7 @@ import { LeftSidebar } from "./LeftSidebar.js";
|
|
|
13
12
|
import { VariantTabs } from "./VariantTabs.js";
|
|
14
13
|
import { CommandPalette } from "./CommandPalette.js";
|
|
15
14
|
import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp.js";
|
|
16
|
-
import {
|
|
15
|
+
import { useToast } from "./Toast.js";
|
|
17
16
|
|
|
18
17
|
// Toolbar
|
|
19
18
|
import { PreviewToolbar, getBackgroundStyle } from "./PreviewToolbar.js";
|
|
@@ -28,6 +27,9 @@ import { HealthDashboard } from "./HealthDashboard.js";
|
|
|
28
27
|
import { useAllFigmaUrls } from "./FigmaEmbed.js";
|
|
29
28
|
import { ActionCapture } from "./ActionCapture.js";
|
|
30
29
|
|
|
30
|
+
// Fragments UI
|
|
31
|
+
import { Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert } from "@fragments/ui";
|
|
32
|
+
|
|
31
33
|
// Icons
|
|
32
34
|
import { EmptyIcon, ExternalLinkIcon, CameraIcon, FigmaIcon, CompareIcon, CheckIcon, LinkIcon, GridIcon, DevicesIcon } from "./Icons.js";
|
|
33
35
|
|
|
@@ -78,15 +80,8 @@ export function App({ segments }: AppProps) {
|
|
|
78
80
|
// Get resolved theme from ThemeProvider for iframe preview
|
|
79
81
|
const { resolvedTheme } = useTheme();
|
|
80
82
|
|
|
81
|
-
// Toast notifications
|
|
82
|
-
const
|
|
83
|
-
const addToast = useCallback((type: ToastMessage['type'], message: string, duration?: number) => {
|
|
84
|
-
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
85
|
-
setToasts(prev => [...prev, { id, type, message, duration }]);
|
|
86
|
-
}, []);
|
|
87
|
-
const dismissToast = useCallback((id: string) => {
|
|
88
|
-
setToasts(prev => prev.filter(t => t.id !== id));
|
|
89
|
-
}, []);
|
|
83
|
+
// Toast notifications (via Fragments UI ToastProvider)
|
|
84
|
+
const { toast, info, success } = useToast();
|
|
90
85
|
|
|
91
86
|
// Navigation state
|
|
92
87
|
const [activeSegmentPath, setActiveSegmentPath] = useState<string | null>(() => {
|
|
@@ -161,13 +156,13 @@ export function App({ segments }: AppProps) {
|
|
|
161
156
|
const handleUpdate = (data: any) => {
|
|
162
157
|
if (data?.updates?.length > 0) {
|
|
163
158
|
const paths = data.updates.map((u: any) => u.path.split('/').pop()).join(', ');
|
|
164
|
-
|
|
159
|
+
info('HMR Update', `Updated: ${paths}`);
|
|
165
160
|
}
|
|
166
161
|
};
|
|
167
162
|
|
|
168
163
|
hot.on('vite:beforeUpdate', handleUpdate);
|
|
169
164
|
return () => hot.off?.('vite:beforeUpdate', handleUpdate);
|
|
170
|
-
}, [
|
|
165
|
+
}, [info]);
|
|
171
166
|
|
|
172
167
|
// Navigation handlers
|
|
173
168
|
const handleSelectSegment = useCallback((path: string) => {
|
|
@@ -189,13 +184,13 @@ export function App({ segments }: AppProps) {
|
|
|
189
184
|
|
|
190
185
|
// Copy link handler
|
|
191
186
|
const handleCopyLink = useCallback(async () => {
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
187
|
+
const copied = await copyUrl();
|
|
188
|
+
if (copied) {
|
|
194
189
|
uiActions.setLinkCopied(true);
|
|
195
|
-
|
|
190
|
+
success('Copied', 'Link copied to clipboard');
|
|
196
191
|
setTimeout(() => uiActions.setLinkCopied(false), 2000);
|
|
197
192
|
}
|
|
198
|
-
}, [copyUrl,
|
|
193
|
+
}, [copyUrl, success, uiActions]);
|
|
199
194
|
|
|
200
195
|
// Sorted segment paths for keyboard navigation
|
|
201
196
|
const sortedSegmentPaths = useMemo(() => {
|
|
@@ -240,7 +235,7 @@ export function App({ segments }: AppProps) {
|
|
|
240
235
|
<ActionCapture onAction={useActionsRef.current.logAction}>
|
|
241
236
|
<StoryRenderer variant={activeVariant}>
|
|
242
237
|
{(content, isLoading, error) => {
|
|
243
|
-
if (isLoading) return <div
|
|
238
|
+
if (isLoading) return <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}><LoaderIndicator /></div>;
|
|
244
239
|
if (error) return <EmptyVariantMessage reason={`Error: ${error.message}`} variantName={activeVariant.name} hint="Check the console for the full error stack trace." />;
|
|
245
240
|
if (content === null || content === undefined) return <EmptyVariantMessage reason="render() returned null or undefined" variantName={activeVariant.name} hint="The variant's render function didn't return any JSX." />;
|
|
246
241
|
return content;
|
|
@@ -262,7 +257,6 @@ export function App({ segments }: AppProps) {
|
|
|
262
257
|
|
|
263
258
|
return (
|
|
264
259
|
<>
|
|
265
|
-
<Toast messages={toasts} onDismiss={dismissToast} />
|
|
266
260
|
<KeyboardShortcutsHelp isOpen={uiState.showShortcutsHelp} onClose={() => uiActions.setShortcutsHelp(false)} />
|
|
267
261
|
<CommandPalette
|
|
268
262
|
isOpen={uiState.showCommandPalette}
|
|
@@ -290,8 +284,8 @@ export function App({ segments }: AppProps) {
|
|
|
290
284
|
}
|
|
291
285
|
>
|
|
292
286
|
{uiState.showHealthDashboard ? (
|
|
293
|
-
<div
|
|
294
|
-
<
|
|
287
|
+
<div style={{ height: '100%', overflow: 'auto', backgroundColor: 'var(--bg-primary)' }}>
|
|
288
|
+
<Box padding="lg" style={{ maxWidth: '896px', margin: '0 auto' }}>
|
|
295
289
|
<HealthDashboard
|
|
296
290
|
segments={segments}
|
|
297
291
|
onNavigate={(componentName) => {
|
|
@@ -302,12 +296,12 @@ export function App({ segments }: AppProps) {
|
|
|
302
296
|
}
|
|
303
297
|
}}
|
|
304
298
|
/>
|
|
305
|
-
</
|
|
299
|
+
</Box>
|
|
306
300
|
</div>
|
|
307
301
|
) : activeSegment ? (
|
|
308
|
-
<div
|
|
302
|
+
<div style={{ display: 'flex', height: '100%', flexDirection: panelDock === "bottom" ? 'column' : 'row' }}>
|
|
309
303
|
{/* Main Content Area */}
|
|
310
|
-
<div
|
|
304
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, minHeight: 0 }}>
|
|
311
305
|
{/* Top Toolbar */}
|
|
312
306
|
<TopToolbar
|
|
313
307
|
segment={activeSegment}
|
|
@@ -335,8 +329,12 @@ export function App({ segments }: AppProps) {
|
|
|
335
329
|
|
|
336
330
|
{/* Preview Area */}
|
|
337
331
|
<div
|
|
338
|
-
|
|
339
|
-
|
|
332
|
+
style={{
|
|
333
|
+
flex: 1,
|
|
334
|
+
overflow: 'auto',
|
|
335
|
+
position: 'relative',
|
|
336
|
+
...(uiState.showMatrixView ? {} : getBackgroundStyle(viewSettings.background)),
|
|
337
|
+
}}
|
|
340
338
|
>
|
|
341
339
|
{activeVariant ? (
|
|
342
340
|
<PreviewArea
|
|
@@ -395,11 +393,13 @@ export function App({ segments }: AppProps) {
|
|
|
395
393
|
)}
|
|
396
394
|
</div>
|
|
397
395
|
) : (
|
|
398
|
-
<
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
396
|
+
<EmptyState style={{ height: '100%' }}>
|
|
397
|
+
<EmptyState.Icon>
|
|
398
|
+
<EmptyIcon style={{ width: '48px', height: '48px' }} />
|
|
399
|
+
</EmptyState.Icon>
|
|
400
|
+
<EmptyState.Title>No component selected</EmptyState.Title>
|
|
401
|
+
<EmptyState.Description>Select a component from the sidebar</EmptyState.Description>
|
|
402
|
+
</EmptyState>
|
|
403
403
|
)}
|
|
404
404
|
</Layout>
|
|
405
405
|
</>
|
|
@@ -420,88 +420,87 @@ interface TopToolbarProps {
|
|
|
420
420
|
|
|
421
421
|
function TopToolbar({ segment, variant, viewSettings, uiState, uiActions, figmaUrl, linkCopied, onCopyLink }: TopToolbarProps) {
|
|
422
422
|
return (
|
|
423
|
-
<
|
|
424
|
-
<
|
|
425
|
-
<
|
|
426
|
-
<
|
|
427
|
-
</
|
|
428
|
-
<
|
|
423
|
+
<Stack direction="row" align="center" justify="between" style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', backgroundColor: 'var(--bg-secondary)', flexShrink: 0 }}>
|
|
424
|
+
<Stack direction="row" align="center" gap="sm">
|
|
425
|
+
<Text weight="medium" size="sm">{segment.segment.meta.name}</Text>
|
|
426
|
+
<Text size="xs" color="tertiary">{segment.segment.meta.category}</Text>
|
|
427
|
+
</Stack>
|
|
428
|
+
<Stack direction="row" align="center" gap="sm">
|
|
429
429
|
<PreviewToolbar
|
|
430
430
|
zoom={viewSettings.zoom}
|
|
431
431
|
background={viewSettings.background}
|
|
432
432
|
onZoomChange={viewSettings.setZoom}
|
|
433
433
|
onBackgroundChange={viewSettings.setBackground}
|
|
434
434
|
/>
|
|
435
|
-
<
|
|
435
|
+
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
436
436
|
<ViewportSelector
|
|
437
437
|
viewport={viewSettings.viewport}
|
|
438
438
|
customSize={viewSettings.customSize}
|
|
439
439
|
onViewportChange={viewSettings.setViewport}
|
|
440
440
|
onCustomSizeChange={viewSettings.setCustomSize}
|
|
441
441
|
/>
|
|
442
|
-
<
|
|
442
|
+
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
443
443
|
|
|
444
444
|
{figmaUrl && (
|
|
445
445
|
<>
|
|
446
|
-
<
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
"
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
>
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
</
|
|
465
|
-
<
|
|
446
|
+
<Tooltip content={uiState.showComparison ? "Hide Figma comparison" : "Compare with Figma design"}>
|
|
447
|
+
<Button
|
|
448
|
+
onClick={uiActions.toggleComparison}
|
|
449
|
+
variant="ghost"
|
|
450
|
+
size="sm"
|
|
451
|
+
style={uiState.showComparison ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}}
|
|
452
|
+
>
|
|
453
|
+
<CompareIcon style={{ width: '16px', height: '16px' }} />
|
|
454
|
+
</Button>
|
|
455
|
+
</Tooltip>
|
|
456
|
+
<Tooltip content="View in Figma">
|
|
457
|
+
<Button
|
|
458
|
+
onClick={() => window.open(figmaUrl, '_blank', 'noopener,noreferrer')}
|
|
459
|
+
variant="ghost"
|
|
460
|
+
size="sm"
|
|
461
|
+
>
|
|
462
|
+
<FigmaIcon style={{ width: '16px', height: '16px' }} />
|
|
463
|
+
</Button>
|
|
464
|
+
</Tooltip>
|
|
465
|
+
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
466
466
|
</>
|
|
467
467
|
)}
|
|
468
468
|
|
|
469
469
|
{variant && (
|
|
470
470
|
<>
|
|
471
|
-
<
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
471
|
+
<Tooltip content="Open in new window">
|
|
472
|
+
<Button
|
|
473
|
+
onClick={() => {
|
|
474
|
+
const url = new URL(window.location.href);
|
|
475
|
+
url.hash = '';
|
|
476
|
+
url.searchParams.set('isolated', 'true');
|
|
477
|
+
url.searchParams.set('component', segment.segment.meta.name);
|
|
478
|
+
url.searchParams.set('variant', variant.name);
|
|
479
|
+
if (viewSettings.zoom !== 100) url.searchParams.set('zoom', String(viewSettings.zoom));
|
|
480
|
+
if (viewSettings.background !== 'transparent') url.searchParams.set('bg', viewSettings.background);
|
|
481
|
+
window.open(url.toString(), '_blank', 'noopener,noreferrer');
|
|
482
|
+
}}
|
|
483
|
+
variant="ghost"
|
|
484
|
+
size="sm"
|
|
485
|
+
>
|
|
486
|
+
<ExternalLinkIcon style={{ width: '16px', height: '16px' }} />
|
|
487
|
+
</Button>
|
|
488
|
+
</Tooltip>
|
|
488
489
|
<ScreenshotButton componentName={segment.segment.meta.name} variantName={variant.name} />
|
|
489
|
-
<
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
"
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
>
|
|
499
|
-
{linkCopied ? <CheckIcon className="w-4 h-4" /> : <LinkIcon className="w-4 h-4" />}
|
|
500
|
-
</button>
|
|
490
|
+
<Tooltip content="Copy link to share">
|
|
491
|
+
<Button
|
|
492
|
+
onClick={onCopyLink}
|
|
493
|
+
variant="ghost"
|
|
494
|
+
size="sm"
|
|
495
|
+
style={linkCopied ? { color: '#16a34a', backgroundColor: 'rgba(22, 163, 74, 0.1)' } : {}}
|
|
496
|
+
>
|
|
497
|
+
{linkCopied ? <CheckIcon style={{ width: '16px', height: '16px' }} /> : <LinkIcon style={{ width: '16px', height: '16px' }} />}
|
|
498
|
+
</Button>
|
|
499
|
+
</Tooltip>
|
|
501
500
|
</>
|
|
502
501
|
)}
|
|
503
|
-
</
|
|
504
|
-
</
|
|
502
|
+
</Stack>
|
|
503
|
+
</Stack>
|
|
505
504
|
);
|
|
506
505
|
}
|
|
507
506
|
|
|
@@ -518,43 +517,37 @@ interface VariantTabsBarProps {
|
|
|
518
517
|
|
|
519
518
|
function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView, showMultiViewport, onToggleMatrix, onToggleMultiViewport }: VariantTabsBarProps) {
|
|
520
519
|
return (
|
|
521
|
-
<
|
|
520
|
+
<Stack direction="row" align="center" justify="between" style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', backgroundColor: 'var(--bg-primary)', flexShrink: 0 }}>
|
|
522
521
|
{!showMatrixView ? (
|
|
523
522
|
<VariantTabs variants={variants} activeIndex={activeIndex} onSelect={onSelect} />
|
|
524
523
|
) : (
|
|
525
|
-
<
|
|
524
|
+
<Text size="sm" color="secondary">Showing all {variants.length} variants</Text>
|
|
526
525
|
)}
|
|
527
|
-
<
|
|
526
|
+
<Stack direction="row" align="center" gap="sm" style={{ marginLeft: '16px' }}>
|
|
528
527
|
{variants.length > 1 && (
|
|
529
|
-
<
|
|
528
|
+
<Button
|
|
530
529
|
onClick={onToggleMatrix}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
showMatrixView
|
|
534
|
-
? "bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300"
|
|
535
|
-
: "text-tertiary hover:text-secondary hover:bg-[--bg-hover]"
|
|
536
|
-
)}
|
|
530
|
+
variant="ghost"
|
|
531
|
+
size="sm"
|
|
537
532
|
title={showMatrixView ? "Show single variant" : "Show all variants in grid"}
|
|
533
|
+
style={showMatrixView ? { backgroundColor: 'rgba(59, 130, 246, 0.1)', color: 'var(--color-accent)' } : {}}
|
|
538
534
|
>
|
|
539
|
-
<GridIcon
|
|
535
|
+
<GridIcon style={{ width: '16px', height: '16px' }} />
|
|
540
536
|
{showMatrixView ? "Exit Matrix" : "Matrix"}
|
|
541
|
-
</
|
|
537
|
+
</Button>
|
|
542
538
|
)}
|
|
543
|
-
<
|
|
539
|
+
<Button
|
|
544
540
|
onClick={onToggleMultiViewport}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
showMultiViewport
|
|
548
|
-
? "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300"
|
|
549
|
-
: "text-tertiary hover:text-secondary hover:bg-[--bg-hover]"
|
|
550
|
-
)}
|
|
541
|
+
variant="ghost"
|
|
542
|
+
size="sm"
|
|
551
543
|
title={showMultiViewport ? "Exit multi-viewport" : "Show at multiple screen sizes"}
|
|
544
|
+
style={showMultiViewport ? { backgroundColor: 'rgba(34, 197, 94, 0.1)', color: '#16a34a' } : {}}
|
|
552
545
|
>
|
|
553
|
-
<DevicesIcon
|
|
546
|
+
<DevicesIcon style={{ width: '16px', height: '16px' }} />
|
|
554
547
|
{showMultiViewport ? "Exit Responsive" : "Responsive"}
|
|
555
|
-
</
|
|
556
|
-
</
|
|
557
|
-
</
|
|
548
|
+
</Button>
|
|
549
|
+
</Stack>
|
|
550
|
+
</Stack>
|
|
558
551
|
);
|
|
559
552
|
}
|
|
560
553
|
|
|
@@ -567,34 +560,39 @@ function NoVariantsMessage({ segment }: NoVariantsMessageProps) {
|
|
|
567
560
|
const skippedVariants = (segment?._generated as any)?.skippedVariants;
|
|
568
561
|
|
|
569
562
|
if (!skippedVariants || skippedVariants.length === 0) {
|
|
570
|
-
return
|
|
563
|
+
return (
|
|
564
|
+
<EmptyState style={{ height: '100%' }}>
|
|
565
|
+
<EmptyState.Description>No variants defined</EmptyState.Description>
|
|
566
|
+
</EmptyState>
|
|
567
|
+
);
|
|
571
568
|
}
|
|
572
569
|
|
|
573
570
|
return (
|
|
574
|
-
<div
|
|
575
|
-
<
|
|
576
|
-
<
|
|
577
|
-
<
|
|
578
|
-
|
|
579
|
-
</
|
|
580
|
-
<
|
|
581
|
-
<
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
571
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', padding: '24px' }}>
|
|
572
|
+
<Alert variant="info">
|
|
573
|
+
<Alert.Body>
|
|
574
|
+
<Alert.Title>
|
|
575
|
+
{skippedVariants.length} variant{skippedVariants.length === 1 ? '' : 's'} skipped
|
|
576
|
+
</Alert.Title>
|
|
577
|
+
<Alert.Content>
|
|
578
|
+
<Stack direction="column" gap="sm">
|
|
579
|
+
<Text size="xs" color="secondary">
|
|
580
|
+
These variants couldn't be rendered because they use syntax the parser doesn't support yet:
|
|
581
|
+
</Text>
|
|
582
|
+
<ul style={{ marginTop: '4px', marginLeft: '16px', listStyleType: 'disc' }}>
|
|
583
|
+
{skippedVariants.map((sv: any, i: number) => (
|
|
584
|
+
<li key={i}>
|
|
585
|
+
<Text size="xs" color="secondary">
|
|
586
|
+
<Text as="span" size="xs" weight="semibold">{sv.name}:</Text>{' '}
|
|
587
|
+
<Text as="span" size="xs" color="tertiary">{sv.reason}</Text>
|
|
588
|
+
</Text>
|
|
589
|
+
</li>
|
|
590
|
+
))}
|
|
591
|
+
</ul>
|
|
592
|
+
</Stack>
|
|
593
|
+
</Alert.Content>
|
|
594
|
+
</Alert.Body>
|
|
595
|
+
</Alert>
|
|
598
596
|
</div>
|
|
599
597
|
);
|
|
600
598
|
}
|
|
@@ -608,31 +606,28 @@ interface EmptyVariantMessageProps {
|
|
|
608
606
|
|
|
609
607
|
function EmptyVariantMessage({ reason, variantName, hint }: EmptyVariantMessageProps) {
|
|
610
608
|
return (
|
|
611
|
-
<
|
|
612
|
-
<
|
|
613
|
-
<
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
<
|
|
623
|
-
<
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
</div>
|
|
635
|
-
</div>
|
|
636
|
-
</div>
|
|
609
|
+
<Alert variant="warning">
|
|
610
|
+
<Alert.Body>
|
|
611
|
+
<Alert.Title>Variant "{variantName}" rendered empty</Alert.Title>
|
|
612
|
+
<Alert.Content>
|
|
613
|
+
<Stack direction="column" gap="sm">
|
|
614
|
+
<Text size="xs" color="secondary">{reason}</Text>
|
|
615
|
+
{hint && (
|
|
616
|
+
<Text size="xs" color="tertiary">
|
|
617
|
+
<Text as="span" size="xs" weight="semibold">Tip:</Text> {hint}
|
|
618
|
+
</Text>
|
|
619
|
+
)}
|
|
620
|
+
<div>
|
|
621
|
+
<Text size="xs" color="tertiary" weight="semibold">Common causes:</Text>
|
|
622
|
+
<ul style={{ marginTop: '4px', marginLeft: '16px', listStyleType: 'disc' }}>
|
|
623
|
+
<li><Text size="xs" color="secondary">Component requires props that weren't provided</Text></li>
|
|
624
|
+
<li><Text size="xs" color="secondary">Component renders conditionally and conditions aren't met</Text></li>
|
|
625
|
+
<li><Text size="xs" color="secondary">Story args reference variables that don't exist in this context</Text></li>
|
|
626
|
+
</ul>
|
|
627
|
+
</div>
|
|
628
|
+
</Stack>
|
|
629
|
+
</Alert.Content>
|
|
630
|
+
</Alert.Body>
|
|
631
|
+
</Alert>
|
|
637
632
|
);
|
|
638
633
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { memo, useCallback } from 'react';
|
|
7
7
|
import type { SegmentDefinition, SegmentVariant } from '../../core/index.js';
|
|
8
|
-
import
|
|
8
|
+
import { Tabs, Badge } from '@fragments/ui';
|
|
9
9
|
import { ResizablePanel } from './ResizablePanel.js';
|
|
10
10
|
import { CodePanel } from './CodePanel.js';
|
|
11
11
|
import { TokenStylePanel } from './TokenStylePanel.js';
|
|
@@ -48,35 +48,6 @@ interface BottomPanelProps {
|
|
|
48
48
|
segmentKey: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// Tab button component
|
|
52
|
-
interface TabButtonProps {
|
|
53
|
-
active: boolean;
|
|
54
|
-
onClick: () => void;
|
|
55
|
-
children: React.ReactNode;
|
|
56
|
-
badge?: number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const TabButton = memo(function TabButton({ active, onClick, children, badge }: TabButtonProps) {
|
|
60
|
-
return (
|
|
61
|
-
<button
|
|
62
|
-
onClick={onClick}
|
|
63
|
-
className={clsx(
|
|
64
|
-
"px-3 py-1 text-xs font-medium rounded relative",
|
|
65
|
-
active
|
|
66
|
-
? "text-primary bg-[--bg-hover]"
|
|
67
|
-
: "text-tertiary hover:text-secondary"
|
|
68
|
-
)}
|
|
69
|
-
>
|
|
70
|
-
{children}
|
|
71
|
-
{badge !== undefined && badge > 0 && (
|
|
72
|
-
<span className="absolute -top-1 -right-1 w-4 h-4 text-[10px] bg-blue-500 text-white rounded-full flex items-center justify-center">
|
|
73
|
-
{badge > 99 ? '99+' : badge}
|
|
74
|
-
</span>
|
|
75
|
-
)}
|
|
76
|
-
</button>
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
51
|
export const BottomPanel = memo(function BottomPanel({
|
|
81
52
|
segment,
|
|
82
53
|
variant,
|
|
@@ -102,63 +73,52 @@ export const BottomPanel = memo(function BottomPanel({
|
|
|
102
73
|
setTimeout(onRefreshRendered, 100);
|
|
103
74
|
}, [onPanelChange, onFetchFigma, onRefreshRendered]);
|
|
104
75
|
|
|
76
|
+
// Build tab change handler that also triggers side effects
|
|
77
|
+
const handleTabChange = useCallback((value: string | number) => {
|
|
78
|
+
const panel = String(value);
|
|
79
|
+
if (panel === 'styles') {
|
|
80
|
+
handleStylesClick();
|
|
81
|
+
} else {
|
|
82
|
+
onPanelChange(panel as ActivePanel);
|
|
83
|
+
}
|
|
84
|
+
}, [handleStylesClick, onPanelChange]);
|
|
85
|
+
|
|
105
86
|
return (
|
|
106
87
|
<ResizablePanel
|
|
107
88
|
visible={true}
|
|
108
89
|
header={
|
|
109
|
-
<
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)}
|
|
138
|
-
<TabButton
|
|
139
|
-
active={activePanel === 'actions'}
|
|
140
|
-
onClick={() => onPanelChange('actions')}
|
|
141
|
-
badge={actionLogs.length}
|
|
142
|
-
>
|
|
143
|
-
Actions
|
|
144
|
-
</TabButton>
|
|
145
|
-
<TabButton
|
|
146
|
-
active={activePanel === 'graph'}
|
|
147
|
-
onClick={() => onPanelChange('graph')}
|
|
148
|
-
>
|
|
149
|
-
Graph
|
|
150
|
-
</TabButton>
|
|
151
|
-
<TabButton
|
|
152
|
-
active={activePanel === 'contract'}
|
|
153
|
-
onClick={() => onPanelChange('contract')}
|
|
154
|
-
>
|
|
155
|
-
Contract
|
|
156
|
-
</TabButton>
|
|
157
|
-
</div>
|
|
90
|
+
<Tabs value={activePanel} onValueChange={handleTabChange}>
|
|
91
|
+
<Tabs.List variant="pills">
|
|
92
|
+
<Tabs.Tab value="code">Code</Tabs.Tab>
|
|
93
|
+
{figmaUrl && <Tabs.Tab value="styles">Styles</Tabs.Tab>}
|
|
94
|
+
<Tabs.Tab value="accessibility">Accessibility</Tabs.Tab>
|
|
95
|
+
{variant?.hasPlayFunction && <Tabs.Tab value="interactions">Interactions</Tabs.Tab>}
|
|
96
|
+
<Tabs.Tab value="actions">
|
|
97
|
+
<span style={{ position: 'relative' }}>
|
|
98
|
+
Actions
|
|
99
|
+
{actionLogs.length > 0 && (
|
|
100
|
+
<Badge
|
|
101
|
+
variant="info"
|
|
102
|
+
size="sm"
|
|
103
|
+
style={{
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
top: '-8px',
|
|
106
|
+
right: '-16px',
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
{actionLogs.length > 99 ? '99+' : actionLogs.length}
|
|
110
|
+
</Badge>
|
|
111
|
+
)}
|
|
112
|
+
</span>
|
|
113
|
+
</Tabs.Tab>
|
|
114
|
+
<Tabs.Tab value="graph">Graph</Tabs.Tab>
|
|
115
|
+
<Tabs.Tab value="contract">Contract</Tabs.Tab>
|
|
116
|
+
</Tabs.List>
|
|
117
|
+
</Tabs>
|
|
158
118
|
}
|
|
159
119
|
>
|
|
160
120
|
{activePanel === 'code' && (
|
|
161
|
-
<div
|
|
121
|
+
<div style={{ padding: '16px' }}>
|
|
162
122
|
<CodePanel
|
|
163
123
|
variant={variant}
|
|
164
124
|
componentName={segment.meta.name}
|