@fragments-sdk/cli 0.5.2 → 0.7.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 +996 -79
- 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-7OPWMLOE.js +1625 -0
- package/dist/chunk-7OPWMLOE.js.map +1 -0
- package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
- package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
- package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
- package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
- package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
- package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
- package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
- package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
- package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.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-7CHRKQ7P.js} +5 -5
- package/dist/mcp-bin.js +8 -220
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-WY23TJCP.js +12 -0
- package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
- package/dist/static-viewer-GBR7YNF3.js +12 -0
- package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
- package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
- package/dist/viewer-SUFOISZM.js +1822 -0
- package/dist/viewer-SUFOISZM.js.map +1 -0
- package/package.json +6 -5
- package/src/bin.ts +31 -0
- package/src/build.ts +147 -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/commands/graph.ts +274 -0
- package/src/core/auto-props.ts +464 -0
- package/src/core/composition.ts +64 -1
- package/src/core/graph-extractor.test.ts +542 -0
- package/src/core/graph-extractor.ts +601 -0
- package/src/core/importAnalyzer.ts +5 -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 +276 -183
- 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 +151 -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 +12 -10
- package/src/viewer/components/LeftSidebar.tsx +103 -155
- package/src/viewer/components/MultiViewportPreview.tsx +254 -63
- package/src/viewer/components/PreviewArea.tsx +113 -44
- package/src/viewer/components/PreviewFrameHost.tsx +36 -6
- package/src/viewer/components/PreviewPane.tsx +2 -3
- package/src/viewer/components/PreviewToolbar.tsx +109 -105
- 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 +121 -114
- package/src/viewer/constants/ui.ts +23 -22
- package/src/viewer/entry.tsx +8 -3
- package/src/viewer/index.ts +3 -6
- package/src/viewer/preview-frame.html +43 -18
- package/src/viewer/server.ts +7 -16
- package/src/viewer/styles/globals.css +46 -85
- package/src/viewer/utils/a11y-fixes.ts +53 -30
- package/dist/chunk-ICAIQ57V.js.map +0 -1
- package/dist/chunk-U4GQ2JTD.js +0 -832
- 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-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
- /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
- /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
- /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
- /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
- /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
- /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
- /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
- /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
- /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
- /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* Refactored for better performance and maintainability.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
|
7
|
-
import type
|
|
8
|
-
import clsx from "clsx";
|
|
6
|
+
import { useState, useMemo, useEffect, useCallback, useRef, type RefObject } from "react";
|
|
7
|
+
import { BRAND, type SegmentDefinition } from "../../core/index.js";
|
|
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,8 +27,11 @@ 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 { Header, Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert, ScrollArea, Input } from "@fragments/ui";
|
|
32
|
+
|
|
31
33
|
// Icons
|
|
32
|
-
import { EmptyIcon, ExternalLinkIcon,
|
|
34
|
+
import { EmptyIcon, ExternalLinkIcon, FigmaIcon, CompareIcon, CheckIcon, LinkIcon, GridIcon, DevicesIcon } from "./Icons.js";
|
|
33
35
|
|
|
34
36
|
// Hooks
|
|
35
37
|
import { useAppState } from "../hooks/useAppState.js";
|
|
@@ -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 { info, success } = useToast();
|
|
90
85
|
|
|
91
86
|
// Navigation state
|
|
92
87
|
const [activeSegmentPath, setActiveSegmentPath] = useState<string | null>(() => {
|
|
@@ -104,6 +99,8 @@ export function App({ segments }: AppProps) {
|
|
|
104
99
|
}
|
|
105
100
|
return 0;
|
|
106
101
|
});
|
|
102
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
103
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
107
104
|
|
|
108
105
|
// Derived values
|
|
109
106
|
const activeSegment = useMemo(
|
|
@@ -161,13 +158,13 @@ export function App({ segments }: AppProps) {
|
|
|
161
158
|
const handleUpdate = (data: any) => {
|
|
162
159
|
if (data?.updates?.length > 0) {
|
|
163
160
|
const paths = data.updates.map((u: any) => u.path.split('/').pop()).join(', ');
|
|
164
|
-
|
|
161
|
+
info('HMR Update', `Updated: ${paths}`);
|
|
165
162
|
}
|
|
166
163
|
};
|
|
167
164
|
|
|
168
165
|
hot.on('vite:beforeUpdate', handleUpdate);
|
|
169
166
|
return () => hot.off?.('vite:beforeUpdate', handleUpdate);
|
|
170
|
-
}, [
|
|
167
|
+
}, [info]);
|
|
171
168
|
|
|
172
169
|
// Navigation handlers
|
|
173
170
|
const handleSelectSegment = useCallback((path: string) => {
|
|
@@ -189,13 +186,18 @@ export function App({ segments }: AppProps) {
|
|
|
189
186
|
|
|
190
187
|
// Copy link handler
|
|
191
188
|
const handleCopyLink = useCallback(async () => {
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
189
|
+
const copied = await copyUrl();
|
|
190
|
+
if (copied) {
|
|
194
191
|
uiActions.setLinkCopied(true);
|
|
195
|
-
|
|
192
|
+
success('Copied', 'Link copied to clipboard');
|
|
196
193
|
setTimeout(() => uiActions.setLinkCopied(false), 2000);
|
|
197
194
|
}
|
|
198
|
-
}, [copyUrl,
|
|
195
|
+
}, [copyUrl, success, uiActions]);
|
|
196
|
+
|
|
197
|
+
const focusSearchInput = useCallback(() => {
|
|
198
|
+
searchInputRef.current?.focus();
|
|
199
|
+
searchInputRef.current?.select();
|
|
200
|
+
}, []);
|
|
199
201
|
|
|
200
202
|
// Sorted segment paths for keyboard navigation
|
|
201
203
|
const sortedSegmentPaths = useMemo(() => {
|
|
@@ -226,8 +228,18 @@ export function App({ segments }: AppProps) {
|
|
|
226
228
|
togglePanel: uiActions.togglePanel,
|
|
227
229
|
copyLink: handleCopyLink,
|
|
228
230
|
showHelp: uiActions.toggleShortcutsHelp,
|
|
229
|
-
openSearch:
|
|
230
|
-
escape:
|
|
231
|
+
openSearch: focusSearchInput,
|
|
232
|
+
escape: () => {
|
|
233
|
+
if (document.activeElement === searchInputRef.current) {
|
|
234
|
+
if (searchQuery) {
|
|
235
|
+
setSearchQuery('');
|
|
236
|
+
} else {
|
|
237
|
+
searchInputRef.current.blur();
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
uiActions.closeAllModals();
|
|
242
|
+
},
|
|
231
243
|
},
|
|
232
244
|
{ enabled: !uiState.showShortcutsHelp, variantCount }
|
|
233
245
|
);
|
|
@@ -240,7 +252,7 @@ export function App({ segments }: AppProps) {
|
|
|
240
252
|
<ActionCapture onAction={useActionsRef.current.logAction}>
|
|
241
253
|
<StoryRenderer variant={activeVariant}>
|
|
242
254
|
{(content, isLoading, error) => {
|
|
243
|
-
if (isLoading) return <div
|
|
255
|
+
if (isLoading) return <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}><LoaderIndicator /></div>;
|
|
244
256
|
if (error) return <EmptyVariantMessage reason={`Error: ${error.message}`} variantName={activeVariant.name} hint="Check the console for the full error stack trace." />;
|
|
245
257
|
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
258
|
return content;
|
|
@@ -262,7 +274,6 @@ export function App({ segments }: AppProps) {
|
|
|
262
274
|
|
|
263
275
|
return (
|
|
264
276
|
<>
|
|
265
|
-
<Toast messages={toasts} onDismiss={dismissToast} />
|
|
266
277
|
<KeyboardShortcutsHelp isOpen={uiState.showShortcutsHelp} onClose={() => uiActions.setShortcutsHelp(false)} />
|
|
267
278
|
<CommandPalette
|
|
268
279
|
isOpen={uiState.showCommandPalette}
|
|
@@ -276,10 +287,35 @@ export function App({ segments }: AppProps) {
|
|
|
276
287
|
/>
|
|
277
288
|
|
|
278
289
|
<Layout
|
|
290
|
+
header={
|
|
291
|
+
activeSegment && !uiState.showHealthDashboard ? (
|
|
292
|
+
<TopToolbar
|
|
293
|
+
segment={activeSegment}
|
|
294
|
+
variant={activeVariant}
|
|
295
|
+
viewSettings={viewSettings}
|
|
296
|
+
uiState={uiState}
|
|
297
|
+
uiActions={uiActions}
|
|
298
|
+
figmaUrl={figmaUrl}
|
|
299
|
+
linkCopied={uiState.linkCopied}
|
|
300
|
+
onCopyLink={handleCopyLink}
|
|
301
|
+
searchQuery={searchQuery}
|
|
302
|
+
onSearchChange={setSearchQuery}
|
|
303
|
+
searchInputRef={searchInputRef}
|
|
304
|
+
/>
|
|
305
|
+
) : (
|
|
306
|
+
<ViewerHeader
|
|
307
|
+
showHealth={uiState.showHealthDashboard}
|
|
308
|
+
searchQuery={searchQuery}
|
|
309
|
+
onSearchChange={setSearchQuery}
|
|
310
|
+
searchInputRef={searchInputRef}
|
|
311
|
+
/>
|
|
312
|
+
)
|
|
313
|
+
}
|
|
279
314
|
leftSidebar={
|
|
280
315
|
<LeftSidebar
|
|
281
316
|
segments={segments}
|
|
282
317
|
activeSegment={uiState.showHealthDashboard ? null : activeSegmentPath}
|
|
318
|
+
searchQuery={searchQuery}
|
|
283
319
|
onSelect={handleSelectSegment}
|
|
284
320
|
showHealth={uiState.showHealthDashboard}
|
|
285
321
|
onHealthClick={() => {
|
|
@@ -290,8 +326,8 @@ export function App({ segments }: AppProps) {
|
|
|
290
326
|
}
|
|
291
327
|
>
|
|
292
328
|
{uiState.showHealthDashboard ? (
|
|
293
|
-
<div
|
|
294
|
-
<
|
|
329
|
+
<div style={{ height: '100%', overflow: 'auto', backgroundColor: 'var(--bg-primary)' }}>
|
|
330
|
+
<Box padding="lg" style={{ maxWidth: '896px', margin: '0 auto' }}>
|
|
295
331
|
<HealthDashboard
|
|
296
332
|
segments={segments}
|
|
297
333
|
onNavigate={(componentName) => {
|
|
@@ -302,24 +338,12 @@ export function App({ segments }: AppProps) {
|
|
|
302
338
|
}
|
|
303
339
|
}}
|
|
304
340
|
/>
|
|
305
|
-
</
|
|
341
|
+
</Box>
|
|
306
342
|
</div>
|
|
307
343
|
) : activeSegment ? (
|
|
308
|
-
<div
|
|
344
|
+
<div style={{ display: 'flex', height: '100%', flexDirection: panelDock === "bottom" ? 'column' : 'row' }}>
|
|
309
345
|
{/* Main Content Area */}
|
|
310
|
-
<div
|
|
311
|
-
{/* Top Toolbar */}
|
|
312
|
-
<TopToolbar
|
|
313
|
-
segment={activeSegment}
|
|
314
|
-
variant={activeVariant}
|
|
315
|
-
viewSettings={viewSettings}
|
|
316
|
-
uiState={uiState}
|
|
317
|
-
uiActions={uiActions}
|
|
318
|
-
figmaUrl={figmaUrl}
|
|
319
|
-
linkCopied={uiState.linkCopied}
|
|
320
|
-
onCopyLink={handleCopyLink}
|
|
321
|
-
/>
|
|
322
|
-
|
|
346
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, minHeight: 0 }}>
|
|
323
347
|
{/* Variant Tabs */}
|
|
324
348
|
{activeSegment.segment.variants && activeSegment.segment.variants.length > 0 && (
|
|
325
349
|
<VariantTabsBar
|
|
@@ -335,8 +359,12 @@ export function App({ segments }: AppProps) {
|
|
|
335
359
|
|
|
336
360
|
{/* Preview Area */}
|
|
337
361
|
<div
|
|
338
|
-
|
|
339
|
-
|
|
362
|
+
style={{
|
|
363
|
+
flex: 1,
|
|
364
|
+
overflow: 'auto',
|
|
365
|
+
position: 'relative',
|
|
366
|
+
...(uiState.showMatrixView ? {} : getBackgroundStyle(viewSettings.background)),
|
|
367
|
+
}}
|
|
340
368
|
>
|
|
341
369
|
{activeVariant ? (
|
|
342
370
|
<PreviewArea
|
|
@@ -395,11 +423,13 @@ export function App({ segments }: AppProps) {
|
|
|
395
423
|
)}
|
|
396
424
|
</div>
|
|
397
425
|
) : (
|
|
398
|
-
<
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
426
|
+
<EmptyState style={{ height: '100%' }}>
|
|
427
|
+
<EmptyState.Icon>
|
|
428
|
+
<EmptyIcon style={{ width: '48px', height: '48px' }} />
|
|
429
|
+
</EmptyState.Icon>
|
|
430
|
+
<EmptyState.Title>No component selected</EmptyState.Title>
|
|
431
|
+
<EmptyState.Description>Select a component from the sidebar</EmptyState.Description>
|
|
432
|
+
</EmptyState>
|
|
403
433
|
)}
|
|
404
434
|
</Layout>
|
|
405
435
|
</>
|
|
@@ -416,92 +446,157 @@ interface TopToolbarProps {
|
|
|
416
446
|
figmaUrl?: string;
|
|
417
447
|
linkCopied: boolean;
|
|
418
448
|
onCopyLink: () => void;
|
|
449
|
+
searchQuery: string;
|
|
450
|
+
onSearchChange: (value: string) => void;
|
|
451
|
+
searchInputRef: RefObject<HTMLInputElement>;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
interface ViewerHeaderProps {
|
|
455
|
+
showHealth: boolean;
|
|
456
|
+
searchQuery: string;
|
|
457
|
+
onSearchChange: (value: string) => void;
|
|
458
|
+
searchInputRef: RefObject<HTMLInputElement>;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
interface HeaderSearchProps {
|
|
462
|
+
value: string;
|
|
463
|
+
onChange: (value: string) => void;
|
|
464
|
+
inputRef: RefObject<HTMLInputElement>;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function HeaderSearch({ value, onChange, inputRef }: HeaderSearchProps) {
|
|
468
|
+
return (
|
|
469
|
+
<Header.Search expandable>
|
|
470
|
+
<Input
|
|
471
|
+
ref={inputRef}
|
|
472
|
+
value={value}
|
|
473
|
+
onChange={onChange}
|
|
474
|
+
placeholder="Search components"
|
|
475
|
+
aria-label="Search components"
|
|
476
|
+
size="sm"
|
|
477
|
+
shortcut="⌘K"
|
|
478
|
+
style={{ width: '220px' }}
|
|
479
|
+
/>
|
|
480
|
+
</Header.Search>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function ViewerHeader({ showHealth, searchQuery, onSearchChange, searchInputRef }: ViewerHeaderProps) {
|
|
485
|
+
return (
|
|
486
|
+
<Header aria-label="Fragments viewer header">
|
|
487
|
+
<Header.Trigger />
|
|
488
|
+
<Header.Brand>
|
|
489
|
+
<Stack direction="row" gap="sm" align="center">
|
|
490
|
+
<Text weight="medium" size="sm">{BRAND.name}</Text>
|
|
491
|
+
<Text size="xs" color="tertiary">{showHealth ? 'health dashboard' : 'preview'}</Text>
|
|
492
|
+
</Stack>
|
|
493
|
+
</Header.Brand>
|
|
494
|
+
<HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
|
|
495
|
+
<Header.Spacer />
|
|
496
|
+
</Header>
|
|
497
|
+
);
|
|
419
498
|
}
|
|
420
499
|
|
|
421
|
-
function TopToolbar({
|
|
500
|
+
function TopToolbar({
|
|
501
|
+
segment,
|
|
502
|
+
variant,
|
|
503
|
+
viewSettings,
|
|
504
|
+
uiState,
|
|
505
|
+
uiActions,
|
|
506
|
+
figmaUrl,
|
|
507
|
+
linkCopied,
|
|
508
|
+
onCopyLink,
|
|
509
|
+
searchQuery,
|
|
510
|
+
onSearchChange,
|
|
511
|
+
searchInputRef,
|
|
512
|
+
}: TopToolbarProps) {
|
|
422
513
|
return (
|
|
423
|
-
<
|
|
424
|
-
<
|
|
425
|
-
|
|
426
|
-
<
|
|
427
|
-
|
|
428
|
-
|
|
514
|
+
<Header aria-label="Component preview toolbar">
|
|
515
|
+
<Header.Trigger />
|
|
516
|
+
<Header.Brand>
|
|
517
|
+
<Stack direction="row" align="center" gap="sm">
|
|
518
|
+
<Text weight="medium" size="sm">{segment.segment.meta.name}</Text>
|
|
519
|
+
<Text size="xs" color="tertiary">{segment.segment.meta.category}</Text>
|
|
520
|
+
</Stack>
|
|
521
|
+
</Header.Brand>
|
|
522
|
+
<HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
|
|
523
|
+
<Header.Spacer />
|
|
524
|
+
<Header.Actions>
|
|
429
525
|
<PreviewToolbar
|
|
430
526
|
zoom={viewSettings.zoom}
|
|
431
527
|
background={viewSettings.background}
|
|
432
528
|
onZoomChange={viewSettings.setZoom}
|
|
433
529
|
onBackgroundChange={viewSettings.setBackground}
|
|
434
530
|
/>
|
|
435
|
-
<
|
|
531
|
+
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
436
532
|
<ViewportSelector
|
|
437
533
|
viewport={viewSettings.viewport}
|
|
438
534
|
customSize={viewSettings.customSize}
|
|
439
535
|
onViewportChange={viewSettings.setViewport}
|
|
440
536
|
onCustomSizeChange={viewSettings.setCustomSize}
|
|
441
537
|
/>
|
|
442
|
-
<
|
|
538
|
+
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
443
539
|
|
|
444
540
|
{figmaUrl && (
|
|
445
541
|
<>
|
|
446
|
-
<
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
"
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
>
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
</
|
|
465
|
-
<
|
|
542
|
+
<Tooltip content={uiState.showComparison ? "Hide Figma comparison" : "Compare with Figma design"}>
|
|
543
|
+
<Button
|
|
544
|
+
onClick={uiActions.toggleComparison}
|
|
545
|
+
variant="ghost"
|
|
546
|
+
size="sm"
|
|
547
|
+
style={uiState.showComparison ? { color: 'var(--color-accent)', backgroundColor: 'var(--bg-hover)' } : {}}
|
|
548
|
+
>
|
|
549
|
+
<CompareIcon style={{ width: '16px', height: '16px' }} />
|
|
550
|
+
</Button>
|
|
551
|
+
</Tooltip>
|
|
552
|
+
<Tooltip content="View in Figma">
|
|
553
|
+
<Button
|
|
554
|
+
onClick={() => window.open(figmaUrl, '_blank', 'noopener,noreferrer')}
|
|
555
|
+
variant="ghost"
|
|
556
|
+
size="sm"
|
|
557
|
+
>
|
|
558
|
+
<FigmaIcon style={{ width: '16px', height: '16px' }} />
|
|
559
|
+
</Button>
|
|
560
|
+
</Tooltip>
|
|
561
|
+
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
466
562
|
</>
|
|
467
563
|
)}
|
|
468
564
|
|
|
469
565
|
{variant && (
|
|
470
566
|
<>
|
|
471
|
-
<
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
567
|
+
<Tooltip content="Open in new window">
|
|
568
|
+
<Button
|
|
569
|
+
onClick={() => {
|
|
570
|
+
const url = new URL(window.location.href);
|
|
571
|
+
url.hash = '';
|
|
572
|
+
url.searchParams.set('isolated', 'true');
|
|
573
|
+
url.searchParams.set('component', segment.segment.meta.name);
|
|
574
|
+
url.searchParams.set('variant', variant.name);
|
|
575
|
+
if (viewSettings.zoom !== 100) url.searchParams.set('zoom', String(viewSettings.zoom));
|
|
576
|
+
if (viewSettings.background !== 'transparent') url.searchParams.set('bg', viewSettings.background);
|
|
577
|
+
window.open(url.toString(), '_blank', 'noopener,noreferrer');
|
|
578
|
+
}}
|
|
579
|
+
variant="ghost"
|
|
580
|
+
size="sm"
|
|
581
|
+
>
|
|
582
|
+
<ExternalLinkIcon style={{ width: '16px', height: '16px' }} />
|
|
583
|
+
</Button>
|
|
584
|
+
</Tooltip>
|
|
488
585
|
<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>
|
|
586
|
+
<Tooltip content="Copy link to share">
|
|
587
|
+
<Button
|
|
588
|
+
onClick={onCopyLink}
|
|
589
|
+
variant="ghost"
|
|
590
|
+
size="sm"
|
|
591
|
+
style={linkCopied ? { color: '#16a34a', backgroundColor: 'rgba(22, 163, 74, 0.1)' } : {}}
|
|
592
|
+
>
|
|
593
|
+
{linkCopied ? <CheckIcon style={{ width: '16px', height: '16px' }} /> : <LinkIcon style={{ width: '16px', height: '16px' }} />}
|
|
594
|
+
</Button>
|
|
595
|
+
</Tooltip>
|
|
501
596
|
</>
|
|
502
597
|
)}
|
|
503
|
-
</
|
|
504
|
-
</
|
|
598
|
+
</Header.Actions>
|
|
599
|
+
</Header>
|
|
505
600
|
);
|
|
506
601
|
}
|
|
507
602
|
|
|
@@ -518,43 +613,39 @@ interface VariantTabsBarProps {
|
|
|
518
613
|
|
|
519
614
|
function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView, showMultiViewport, onToggleMatrix, onToggleMultiViewport }: VariantTabsBarProps) {
|
|
520
615
|
return (
|
|
521
|
-
<
|
|
616
|
+
<Stack direction="row" align="center" justify="between" style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', backgroundColor: 'var(--bg-primary)', flexShrink: 0 }}>
|
|
522
617
|
{!showMatrixView ? (
|
|
523
|
-
<
|
|
618
|
+
<ScrollArea orientation="horizontal" showFades style={{ flex: 1, minWidth: 0 }}>
|
|
619
|
+
<VariantTabs variants={variants} activeIndex={activeIndex} onSelect={onSelect} />
|
|
620
|
+
</ScrollArea>
|
|
524
621
|
) : (
|
|
525
|
-
<
|
|
622
|
+
<Text size="sm" color="secondary">Showing all {variants.length} variants</Text>
|
|
526
623
|
)}
|
|
527
|
-
<
|
|
624
|
+
<Stack direction="row" align="center" gap="sm" style={{ marginLeft: '16px', flexShrink: 0 }}>
|
|
528
625
|
{variants.length > 1 && (
|
|
529
|
-
<
|
|
626
|
+
<Button
|
|
530
627
|
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
|
-
)}
|
|
628
|
+
variant="ghost"
|
|
629
|
+
size="sm"
|
|
537
630
|
title={showMatrixView ? "Show single variant" : "Show all variants in grid"}
|
|
631
|
+
style={showMatrixView ? { backgroundColor: 'rgba(59, 130, 246, 0.1)', color: 'var(--color-accent)' } : {}}
|
|
538
632
|
>
|
|
539
|
-
<GridIcon
|
|
633
|
+
<GridIcon style={{ width: '16px', height: '16px' }} />
|
|
540
634
|
{showMatrixView ? "Exit Matrix" : "Matrix"}
|
|
541
|
-
</
|
|
635
|
+
</Button>
|
|
542
636
|
)}
|
|
543
|
-
<
|
|
637
|
+
<Button
|
|
544
638
|
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
|
-
)}
|
|
639
|
+
variant="ghost"
|
|
640
|
+
size="sm"
|
|
551
641
|
title={showMultiViewport ? "Exit multi-viewport" : "Show at multiple screen sizes"}
|
|
642
|
+
style={showMultiViewport ? { backgroundColor: 'rgba(34, 197, 94, 0.1)', color: '#16a34a' } : {}}
|
|
552
643
|
>
|
|
553
|
-
<DevicesIcon
|
|
644
|
+
<DevicesIcon style={{ width: '16px', height: '16px' }} />
|
|
554
645
|
{showMultiViewport ? "Exit Responsive" : "Responsive"}
|
|
555
|
-
</
|
|
556
|
-
</
|
|
557
|
-
</
|
|
646
|
+
</Button>
|
|
647
|
+
</Stack>
|
|
648
|
+
</Stack>
|
|
558
649
|
);
|
|
559
650
|
}
|
|
560
651
|
|
|
@@ -567,34 +658,39 @@ function NoVariantsMessage({ segment }: NoVariantsMessageProps) {
|
|
|
567
658
|
const skippedVariants = (segment?._generated as any)?.skippedVariants;
|
|
568
659
|
|
|
569
660
|
if (!skippedVariants || skippedVariants.length === 0) {
|
|
570
|
-
return
|
|
661
|
+
return (
|
|
662
|
+
<EmptyState style={{ height: '100%' }}>
|
|
663
|
+
<EmptyState.Description>No variants defined</EmptyState.Description>
|
|
664
|
+
</EmptyState>
|
|
665
|
+
);
|
|
571
666
|
}
|
|
572
667
|
|
|
573
668
|
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
|
-
|
|
669
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', padding: '24px' }}>
|
|
670
|
+
<Alert variant="info">
|
|
671
|
+
<Alert.Body>
|
|
672
|
+
<Alert.Title>
|
|
673
|
+
{skippedVariants.length} variant{skippedVariants.length === 1 ? '' : 's'} skipped
|
|
674
|
+
</Alert.Title>
|
|
675
|
+
<Alert.Content>
|
|
676
|
+
<Stack direction="column" gap="sm">
|
|
677
|
+
<Text size="xs" color="secondary">
|
|
678
|
+
These variants couldn't be rendered because they use syntax the parser doesn't support yet:
|
|
679
|
+
</Text>
|
|
680
|
+
<ul style={{ marginTop: '4px', marginLeft: '16px', listStyleType: 'disc' }}>
|
|
681
|
+
{skippedVariants.map((sv: any, i: number) => (
|
|
682
|
+
<li key={i}>
|
|
683
|
+
<Text size="xs" color="secondary">
|
|
684
|
+
<Text as="span" size="xs" weight="semibold">{sv.name}:</Text>{' '}
|
|
685
|
+
<Text as="span" size="xs" color="tertiary">{sv.reason}</Text>
|
|
686
|
+
</Text>
|
|
687
|
+
</li>
|
|
688
|
+
))}
|
|
689
|
+
</ul>
|
|
690
|
+
</Stack>
|
|
691
|
+
</Alert.Content>
|
|
692
|
+
</Alert.Body>
|
|
693
|
+
</Alert>
|
|
598
694
|
</div>
|
|
599
695
|
);
|
|
600
696
|
}
|
|
@@ -608,31 +704,28 @@ interface EmptyVariantMessageProps {
|
|
|
608
704
|
|
|
609
705
|
function EmptyVariantMessage({ reason, variantName, hint }: EmptyVariantMessageProps) {
|
|
610
706
|
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>
|
|
707
|
+
<Alert variant="warning">
|
|
708
|
+
<Alert.Body>
|
|
709
|
+
<Alert.Title>Variant "{variantName}" rendered empty</Alert.Title>
|
|
710
|
+
<Alert.Content>
|
|
711
|
+
<Stack direction="column" gap="sm">
|
|
712
|
+
<Text size="xs" color="secondary">{reason}</Text>
|
|
713
|
+
{hint && (
|
|
714
|
+
<Text size="xs" color="tertiary">
|
|
715
|
+
<Text as="span" size="xs" weight="semibold">Tip:</Text> {hint}
|
|
716
|
+
</Text>
|
|
717
|
+
)}
|
|
718
|
+
<div>
|
|
719
|
+
<Text size="xs" color="tertiary" weight="semibold">Common causes:</Text>
|
|
720
|
+
<ul style={{ marginTop: '4px', marginLeft: '16px', listStyleType: 'disc' }}>
|
|
721
|
+
<li><Text size="xs" color="secondary">Component requires props that weren't provided</Text></li>
|
|
722
|
+
<li><Text size="xs" color="secondary">Component renders conditionally and conditions aren't met</Text></li>
|
|
723
|
+
<li><Text size="xs" color="secondary">Story args reference variables that don't exist in this context</Text></li>
|
|
724
|
+
</ul>
|
|
725
|
+
</div>
|
|
726
|
+
</Stack>
|
|
727
|
+
</Alert.Content>
|
|
728
|
+
</Alert.Body>
|
|
729
|
+
</Alert>
|
|
637
730
|
);
|
|
638
731
|
}
|