@fragments-sdk/cli 0.7.0 → 0.7.2
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/LICENSE +77 -14
- package/dist/bin.js +247 -247
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-CVXKXVOY.js → chunk-3T6QL7IY.js} +47 -29
- package/dist/chunk-3T6QL7IY.js.map +1 -0
- package/dist/{chunk-7OPWMLOE.js → chunk-7KUSBMI4.js} +114 -112
- package/dist/chunk-7KUSBMI4.js.map +1 -0
- package/dist/{chunk-XHUDJNN3.js → chunk-DH4ETVSM.js} +18 -18
- package/dist/chunk-DH4ETVSM.js.map +1 -0
- package/dist/{chunk-RVRTRESS.js → chunk-DQHWLAUV.js} +29 -29
- package/dist/chunk-DQHWLAUV.js.map +1 -0
- package/dist/{chunk-6JBGU74P.js → chunk-GHYYFAQN.js} +23 -23
- package/dist/chunk-GHYYFAQN.js.map +1 -0
- package/dist/{chunk-NWQ4CJOQ.js → chunk-GKX2HPZ6.js} +40 -40
- package/dist/chunk-GKX2HPZ6.js.map +1 -0
- package/dist/{chunk-TJ34N7C7.js → chunk-OOGTG5FM.js} +34 -33
- package/dist/chunk-OOGTG5FM.js.map +1 -0
- package/dist/{core-W2HYIQW6.js → core-UQXZTBFZ.js} +24 -26
- package/dist/{generate-LMTISDIJ.js → generate-GP6ZLAQB.js} +5 -5
- package/dist/generate-GP6ZLAQB.js.map +1 -0
- package/dist/index.d.ts +23 -27
- package/dist/index.js +10 -10
- package/dist/{init-7CHRKQ7P.js → init-W72WBSU2.js} +5 -5
- package/dist/{init-7CHRKQ7P.js.map → init-W72WBSU2.js.map} +1 -1
- package/dist/mcp-bin.js +73 -73
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-V54HWRDY.js +12 -0
- package/dist/{service-T2L7VLTE.js → service-PVGTYUKX.js} +6 -6
- package/dist/{static-viewer-GBR7YNF3.js → static-viewer-KILKIVN7.js} +4 -4
- package/dist/{test-OJRXNDO2.js → test-3YRYQRGV.js} +19 -19
- package/dist/test-3YRYQRGV.js.map +1 -0
- package/dist/{tokens-3BWDESVM.js → tokens-IXSQHPQK.js} +5 -5
- package/dist/{viewer-SUFOISZM.js → viewer-K42REJU2.js} +199 -199
- package/dist/viewer-K42REJU2.js.map +1 -0
- package/package.json +13 -2
- package/src/ai.ts +5 -5
- package/src/analyze.ts +11 -11
- package/src/bin.ts +1 -1
- package/src/build.ts +37 -35
- package/src/commands/a11y.ts +6 -6
- package/src/commands/add.ts +11 -11
- package/src/commands/audit.ts +4 -4
- package/src/commands/baseline.ts +3 -3
- package/src/commands/build.ts +8 -8
- package/src/commands/compare.ts +20 -20
- package/src/commands/context.ts +16 -16
- package/src/commands/enhance.ts +36 -36
- package/src/commands/generate.ts +1 -1
- package/src/commands/graph.ts +5 -5
- package/src/commands/init.ts +1 -1
- package/src/commands/link/figma.ts +82 -82
- package/src/commands/link/index.ts +3 -3
- package/src/commands/link/storybook.ts +9 -9
- package/src/commands/list.ts +2 -2
- package/src/commands/reset.ts +15 -15
- package/src/commands/scan.ts +27 -27
- package/src/commands/storygen.ts +24 -24
- package/src/commands/validate.ts +2 -2
- package/src/commands/verify.ts +8 -8
- package/src/core/auto-props.ts +4 -4
- package/src/core/composition.test.ts +36 -36
- package/src/core/composition.ts +19 -19
- package/src/core/config.ts +6 -6
- package/src/core/{defineSegment.ts → defineFragment.ts} +16 -22
- package/src/core/discovery.ts +6 -6
- package/src/core/figma.ts +2 -2
- package/src/core/graph-extractor.test.ts +77 -77
- package/src/core/graph-extractor.ts +32 -32
- package/src/core/importAnalyzer.ts +1 -1
- package/src/core/index.ts +22 -23
- package/src/core/loader.ts +21 -24
- package/src/core/node.ts +5 -5
- package/src/core/parser.ts +71 -31
- package/src/core/previewLoader.ts +1 -1
- package/src/core/schema.ts +16 -16
- package/src/core/storyAdapter.test.ts +87 -87
- package/src/core/storyAdapter.ts +16 -16
- package/src/core/token-parser.ts +9 -1
- package/src/core/types.ts +21 -26
- package/src/diff.ts +22 -22
- package/src/index.ts +2 -2
- package/src/mcp/server.ts +80 -80
- package/src/migrate/__tests__/utils/utils.test.ts +3 -3
- package/src/migrate/bin.ts +4 -4
- package/src/migrate/converter.ts +16 -16
- package/src/migrate/index.ts +3 -3
- package/src/migrate/migrate.ts +3 -3
- package/src/migrate/parser.ts +8 -8
- package/src/migrate/report.ts +2 -2
- package/src/migrate/types.ts +4 -4
- package/src/screenshot.ts +22 -22
- package/src/service/__tests__/props-extractor.test.ts +15 -15
- package/src/service/analytics.ts +39 -39
- package/src/service/enhance/codebase-scanner.ts +1 -1
- package/src/service/enhance/index.ts +1 -1
- package/src/service/enhance/props-extractor.ts +2 -2
- package/src/service/enhance/types.ts +2 -2
- package/src/service/index.ts +2 -2
- package/src/service/metrics-store.ts +1 -1
- package/src/service/patch-generator.ts +1 -1
- package/src/setup.ts +52 -52
- package/src/shared/dev-server-client.ts +7 -7
- package/src/shared/fragment-loader.ts +59 -0
- package/src/shared/index.ts +1 -1
- package/src/shared/types.ts +4 -4
- package/src/static-viewer.ts +35 -35
- package/src/test/discovery.ts +6 -6
- package/src/test/index.ts +5 -5
- package/src/test/reporters/console.ts +1 -1
- package/src/test/reporters/junit.ts +1 -1
- package/src/test/runner.ts +7 -7
- package/src/test/types.ts +3 -3
- package/src/test/watch.ts +9 -9
- package/src/validators.ts +26 -26
- package/src/viewer/__tests__/render-utils.test.ts +28 -28
- package/src/viewer/__tests__/viewer-integration.test.ts +4 -4
- package/src/viewer/cli/health.ts +26 -26
- package/src/viewer/components/App.tsx +79 -79
- package/src/viewer/components/BottomPanel.tsx +17 -17
- package/src/viewer/components/CodePanel.tsx +3 -3
- package/src/viewer/components/CommandPalette.tsx +11 -11
- package/src/viewer/components/ComponentGraph.tsx +28 -28
- package/src/viewer/components/ComponentHeader.tsx +2 -2
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/FigmaEmbed.tsx +9 -9
- package/src/viewer/components/HealthDashboard.tsx +17 -17
- package/src/viewer/components/InteractionsPanel.tsx +2 -2
- package/src/viewer/components/IsolatedPreviewFrame.tsx +6 -6
- package/src/viewer/components/IsolatedRender.tsx +10 -10
- package/src/viewer/components/LeftSidebar.tsx +28 -28
- package/src/viewer/components/MultiViewportPreview.tsx +14 -14
- package/src/viewer/components/PreviewArea.tsx +11 -11
- package/src/viewer/components/PreviewFrameHost.tsx +51 -51
- package/src/viewer/components/RightSidebar.tsx +9 -9
- package/src/viewer/components/Sidebar.tsx +17 -17
- package/src/viewer/components/StoryRenderer.tsx +2 -2
- package/src/viewer/components/TokenStylePanel.tsx +1 -1
- package/src/viewer/components/UsageSection.tsx +2 -2
- package/src/viewer/components/VariantMatrix.tsx +11 -11
- package/src/viewer/components/VariantRenderer.tsx +3 -3
- package/src/viewer/components/VariantTabs.tsx +2 -2
- package/src/viewer/components/_future/CreatePage.tsx +6 -6
- package/src/viewer/composition-renderer.ts +11 -11
- package/src/viewer/entry.tsx +40 -40
- package/src/viewer/hooks/useFigmaIntegration.ts +1 -1
- package/src/viewer/hooks/usePreviewBridge.ts +5 -5
- package/src/viewer/hooks/useUrlState.ts +6 -6
- package/src/viewer/index.ts +2 -2
- package/src/viewer/intelligence/healthReport.ts +17 -17
- package/src/viewer/intelligence/styleDrift.ts +1 -1
- package/src/viewer/intelligence/usageScanner.ts +1 -1
- package/src/viewer/render-template.html +1 -1
- package/src/viewer/render-utils.ts +21 -21
- package/src/viewer/server.ts +18 -18
- package/src/viewer/utils/detectRelationships.ts +22 -22
- package/src/viewer/vite-plugin.ts +213 -213
- package/dist/chunk-6JBGU74P.js.map +0 -1
- package/dist/chunk-7OPWMLOE.js.map +0 -1
- package/dist/chunk-CVXKXVOY.js.map +0 -1
- package/dist/chunk-NWQ4CJOQ.js.map +0 -1
- package/dist/chunk-RVRTRESS.js.map +0 -1
- package/dist/chunk-TJ34N7C7.js.map +0 -1
- package/dist/chunk-XHUDJNN3.js.map +0 -1
- package/dist/generate-LMTISDIJ.js.map +0 -1
- package/dist/scan-WY23TJCP.js +0 -12
- package/dist/test-OJRXNDO2.js.map +0 -1
- package/dist/viewer-SUFOISZM.js.map +0 -1
- package/src/shared/segment-loader.ts +0 -59
- /package/dist/{core-W2HYIQW6.js.map → core-UQXZTBFZ.js.map} +0 -0
- /package/dist/{scan-WY23TJCP.js.map → scan-V54HWRDY.js.map} +0 -0
- /package/dist/{service-T2L7VLTE.js.map → service-PVGTYUKX.js.map} +0 -0
- /package/dist/{static-viewer-GBR7YNF3.js.map → static-viewer-KILKIVN7.js.map} +0 -0
- /package/dist/{tokens-3BWDESVM.js.map → tokens-IXSQHPQK.js.map} +0 -0
|
@@ -42,8 +42,8 @@ const DEFAULT_VIEWPORTS: ViewportConfig[] = [
|
|
|
42
42
|
interface MultiViewportPreviewProps {
|
|
43
43
|
/** Component name for error boundary */
|
|
44
44
|
componentName: string;
|
|
45
|
-
/**
|
|
46
|
-
|
|
45
|
+
/** Fragment path for iframe rendering */
|
|
46
|
+
fragmentPath: string;
|
|
47
47
|
/** Variant name for iframe rendering */
|
|
48
48
|
variantName: string;
|
|
49
49
|
/** Render function that returns the component (fallback) */
|
|
@@ -60,7 +60,7 @@ interface MultiViewportPreviewProps {
|
|
|
60
60
|
|
|
61
61
|
export function MultiViewportPreview({
|
|
62
62
|
componentName,
|
|
63
|
-
|
|
63
|
+
fragmentPath,
|
|
64
64
|
variantName,
|
|
65
65
|
renderContent,
|
|
66
66
|
previewTheme,
|
|
@@ -203,7 +203,7 @@ export function MultiViewportPreview({
|
|
|
203
203
|
key={`${vp.name}-${vp.width}`}
|
|
204
204
|
viewport={vp}
|
|
205
205
|
componentName={componentName}
|
|
206
|
-
|
|
206
|
+
fragmentPath={fragmentPath}
|
|
207
207
|
variantName={variantName}
|
|
208
208
|
renderContent={renderContent}
|
|
209
209
|
previewTheme={previewTheme}
|
|
@@ -220,7 +220,7 @@ export function MultiViewportPreview({
|
|
|
220
220
|
interface ViewportPanelProps {
|
|
221
221
|
viewport: ViewportConfig;
|
|
222
222
|
componentName: string;
|
|
223
|
-
|
|
223
|
+
fragmentPath: string;
|
|
224
224
|
variantName: string;
|
|
225
225
|
renderContent: () => ReactNode;
|
|
226
226
|
previewTheme: "light" | "dark";
|
|
@@ -231,7 +231,7 @@ interface ViewportPanelProps {
|
|
|
231
231
|
function ViewportPanel({
|
|
232
232
|
viewport,
|
|
233
233
|
componentName,
|
|
234
|
-
|
|
234
|
+
fragmentPath,
|
|
235
235
|
variantName,
|
|
236
236
|
renderContent,
|
|
237
237
|
previewTheme,
|
|
@@ -247,7 +247,7 @@ function ViewportPanel({
|
|
|
247
247
|
previewTheme={previewTheme}
|
|
248
248
|
background={background}
|
|
249
249
|
componentName={componentName}
|
|
250
|
-
|
|
250
|
+
fragmentPath={fragmentPath}
|
|
251
251
|
variantName={variantName}
|
|
252
252
|
renderContent={renderContent}
|
|
253
253
|
useIframeIsolation={useIframeIsolation}
|
|
@@ -264,7 +264,7 @@ function ViewportPanel({
|
|
|
264
264
|
previewTheme={previewTheme}
|
|
265
265
|
background={background}
|
|
266
266
|
componentName={componentName}
|
|
267
|
-
|
|
267
|
+
fragmentPath={fragmentPath}
|
|
268
268
|
variantName={variantName}
|
|
269
269
|
renderContent={renderContent}
|
|
270
270
|
useIframeIsolation={useIframeIsolation}
|
|
@@ -280,7 +280,7 @@ interface DeviceMockupProps {
|
|
|
280
280
|
previewTheme: "light" | "dark";
|
|
281
281
|
background: BackgroundOption;
|
|
282
282
|
componentName: string;
|
|
283
|
-
|
|
283
|
+
fragmentPath: string;
|
|
284
284
|
variantName: string;
|
|
285
285
|
renderContent: () => ReactNode;
|
|
286
286
|
useIframeIsolation: boolean;
|
|
@@ -294,7 +294,7 @@ function DeviceMockup({
|
|
|
294
294
|
previewTheme,
|
|
295
295
|
background,
|
|
296
296
|
componentName,
|
|
297
|
-
|
|
297
|
+
fragmentPath,
|
|
298
298
|
variantName,
|
|
299
299
|
renderContent,
|
|
300
300
|
useIframeIsolation,
|
|
@@ -427,7 +427,7 @@ function DeviceMockup({
|
|
|
427
427
|
>
|
|
428
428
|
{useIframeIsolation ? (
|
|
429
429
|
<IsolatedPreviewFrame
|
|
430
|
-
|
|
430
|
+
fragmentPath={fragmentPath}
|
|
431
431
|
variantName={variantName}
|
|
432
432
|
theme={previewTheme}
|
|
433
433
|
width="100%"
|
|
@@ -481,7 +481,7 @@ interface DesktopMockupProps {
|
|
|
481
481
|
previewTheme: "light" | "dark";
|
|
482
482
|
background: BackgroundOption;
|
|
483
483
|
componentName: string;
|
|
484
|
-
|
|
484
|
+
fragmentPath: string;
|
|
485
485
|
variantName: string;
|
|
486
486
|
renderContent: () => ReactNode;
|
|
487
487
|
useIframeIsolation: boolean;
|
|
@@ -494,7 +494,7 @@ function DesktopMockup({
|
|
|
494
494
|
previewTheme,
|
|
495
495
|
background,
|
|
496
496
|
componentName,
|
|
497
|
-
|
|
497
|
+
fragmentPath,
|
|
498
498
|
variantName,
|
|
499
499
|
renderContent,
|
|
500
500
|
useIframeIsolation,
|
|
@@ -574,7 +574,7 @@ function DesktopMockup({
|
|
|
574
574
|
>
|
|
575
575
|
{useIframeIsolation ? (
|
|
576
576
|
<IsolatedPreviewFrame
|
|
577
|
-
|
|
577
|
+
fragmentPath={fragmentPath}
|
|
578
578
|
variantName={variantName}
|
|
579
579
|
theme={previewTheme}
|
|
580
580
|
width="100%"
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { memo, type ReactNode } from 'react';
|
|
10
|
-
import type {
|
|
10
|
+
import type { FragmentVariant } from '../../core/index.js';
|
|
11
11
|
import { ErrorBoundary } from './ErrorBoundary.js';
|
|
12
12
|
import { FigmaEmbed } from './FigmaEmbed.js';
|
|
13
13
|
import { VariantMatrix } from './VariantMatrix.js';
|
|
@@ -20,9 +20,9 @@ import { getViewportWidth, type ViewportPreset, type ViewportSize } from './View
|
|
|
20
20
|
interface PreviewAreaProps {
|
|
21
21
|
// Component data
|
|
22
22
|
componentName: string;
|
|
23
|
-
|
|
24
|
-
variant:
|
|
25
|
-
variants:
|
|
23
|
+
fragmentPath: string;
|
|
24
|
+
variant: FragmentVariant | undefined;
|
|
25
|
+
variants: FragmentVariant[] | undefined;
|
|
26
26
|
|
|
27
27
|
// View settings
|
|
28
28
|
zoom: ZoomLevel;
|
|
@@ -185,7 +185,7 @@ const PreviewContent = memo(function PreviewContent({ zoom, previewTheme, backgr
|
|
|
185
185
|
|
|
186
186
|
export function PreviewArea({
|
|
187
187
|
componentName,
|
|
188
|
-
|
|
188
|
+
fragmentPath,
|
|
189
189
|
variant,
|
|
190
190
|
variants,
|
|
191
191
|
zoom,
|
|
@@ -210,7 +210,7 @@ export function PreviewArea({
|
|
|
210
210
|
<VariantMatrix
|
|
211
211
|
variants={variants}
|
|
212
212
|
componentName={componentName}
|
|
213
|
-
|
|
213
|
+
fragmentPath={fragmentPath}
|
|
214
214
|
zoom={zoom}
|
|
215
215
|
previewTheme={previewTheme}
|
|
216
216
|
background={background}
|
|
@@ -227,7 +227,7 @@ export function PreviewArea({
|
|
|
227
227
|
return (
|
|
228
228
|
<MultiViewportPreview
|
|
229
229
|
componentName={componentName}
|
|
230
|
-
|
|
230
|
+
fragmentPath={fragmentPath}
|
|
231
231
|
variantName={variant.name}
|
|
232
232
|
renderContent={renderContent}
|
|
233
233
|
previewTheme={previewTheme}
|
|
@@ -257,7 +257,7 @@ export function PreviewArea({
|
|
|
257
257
|
<DeviceMockup type={viewport as 'tablet' | 'mobile'} width={viewportWidth}>
|
|
258
258
|
{useIframeIsolation ? (
|
|
259
259
|
<IsolatedPreviewFrame
|
|
260
|
-
|
|
260
|
+
fragmentPath={fragmentPath}
|
|
261
261
|
variantName={variant.name}
|
|
262
262
|
theme={previewTheme}
|
|
263
263
|
width={viewportWidth}
|
|
@@ -300,7 +300,7 @@ export function PreviewArea({
|
|
|
300
300
|
<DeviceMockup type={viewport as 'tablet' | 'mobile'} width={viewportWidth}>
|
|
301
301
|
{useIframeIsolation ? (
|
|
302
302
|
<IsolatedPreviewFrame
|
|
303
|
-
|
|
303
|
+
fragmentPath={fragmentPath}
|
|
304
304
|
variantName={variant.name}
|
|
305
305
|
theme={previewTheme}
|
|
306
306
|
width={viewportWidth}
|
|
@@ -337,7 +337,7 @@ export function PreviewArea({
|
|
|
337
337
|
>
|
|
338
338
|
{useIframeIsolation ? (
|
|
339
339
|
<IsolatedPreviewFrame
|
|
340
|
-
|
|
340
|
+
fragmentPath={fragmentPath}
|
|
341
341
|
variantName={variant.name}
|
|
342
342
|
theme={previewTheme}
|
|
343
343
|
width="100%"
|
|
@@ -420,7 +420,7 @@ export function PreviewArea({
|
|
|
420
420
|
}}
|
|
421
421
|
>
|
|
422
422
|
<IsolatedPreviewFrame
|
|
423
|
-
|
|
423
|
+
fragmentPath={fragmentPath}
|
|
424
424
|
variantName={variant.name}
|
|
425
425
|
theme={previewTheme}
|
|
426
426
|
width="100%"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This component runs inside the preview iframe and:
|
|
5
5
|
* 1. Listens for render requests from the parent window
|
|
6
|
-
* 2. Loads and renders the requested
|
|
6
|
+
* 2. Loads and renders the requested fragment variant
|
|
7
7
|
* 3. Applies theme styling
|
|
8
8
|
* 4. Reports render status back to parent
|
|
9
9
|
*/
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
import { useState, useEffect, useRef, type ReactNode } from 'react';
|
|
12
12
|
import { useFrameBridge } from '../hooks/usePreviewBridge.js';
|
|
13
13
|
|
|
14
|
-
// Types for
|
|
15
|
-
interface
|
|
14
|
+
// Types for fragment data
|
|
15
|
+
interface FragmentVariant {
|
|
16
16
|
name: string;
|
|
17
17
|
render: (options?: { loadedData?: Record<string, unknown> }) => ReactNode;
|
|
18
18
|
loaders?: Array<() => Promise<Record<string, unknown>>>;
|
|
@@ -20,74 +20,74 @@ interface SegmentVariant {
|
|
|
20
20
|
hasPlayFunction?: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
interface
|
|
23
|
+
interface FragmentDefinition {
|
|
24
24
|
meta: {
|
|
25
25
|
name: string;
|
|
26
26
|
description?: string;
|
|
27
27
|
category?: string;
|
|
28
28
|
};
|
|
29
|
-
variants?:
|
|
29
|
+
variants?: FragmentVariant[];
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
interface
|
|
32
|
+
interface FragmentItem {
|
|
33
33
|
path: string;
|
|
34
|
-
|
|
34
|
+
fragment: FragmentDefinition;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// Cached
|
|
38
|
-
let
|
|
39
|
-
let
|
|
37
|
+
// Cached fragments
|
|
38
|
+
let cachedFragments: FragmentItem[] | null = null;
|
|
39
|
+
let fragmentsPromise: Promise<FragmentItem[]> | null = null;
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Load
|
|
42
|
+
* Load fragments from the virtual module
|
|
43
43
|
*/
|
|
44
|
-
async function
|
|
45
|
-
if (
|
|
46
|
-
return
|
|
44
|
+
async function loadFragments(): Promise<FragmentItem[]> {
|
|
45
|
+
if (cachedFragments) {
|
|
46
|
+
return cachedFragments;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
if (
|
|
50
|
-
return
|
|
49
|
+
if (fragmentsPromise) {
|
|
50
|
+
return fragmentsPromise;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
fragmentsPromise = (async () => {
|
|
54
54
|
try {
|
|
55
55
|
// @ts-expect-error Virtual module
|
|
56
56
|
const module = await import('virtual:fragments');
|
|
57
|
-
if (module.
|
|
58
|
-
|
|
57
|
+
if (module.fragmentsPromise) {
|
|
58
|
+
cachedFragments = await module.fragmentsPromise;
|
|
59
59
|
} else {
|
|
60
|
-
|
|
60
|
+
cachedFragments = module.fragments || [];
|
|
61
61
|
}
|
|
62
|
-
return
|
|
62
|
+
return cachedFragments!;
|
|
63
63
|
} catch (error) {
|
|
64
|
-
console.error('[PreviewFrameHost] Failed to load
|
|
64
|
+
console.error('[PreviewFrameHost] Failed to load fragments:', error);
|
|
65
65
|
throw error;
|
|
66
66
|
}
|
|
67
67
|
})();
|
|
68
68
|
|
|
69
|
-
return
|
|
69
|
+
return fragmentsPromise;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* Find a
|
|
73
|
+
* Find a fragment by its path
|
|
74
74
|
*/
|
|
75
|
-
function
|
|
76
|
-
return
|
|
75
|
+
function findFragmentByPath(fragments: FragmentItem[], path: string): FragmentItem | undefined {
|
|
76
|
+
return fragments.find(s => s.path === path);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
* Find a variant by name within a
|
|
80
|
+
* Find a variant by name within a fragment
|
|
81
81
|
*/
|
|
82
|
-
function findVariant(
|
|
83
|
-
return
|
|
82
|
+
function findVariant(fragment: FragmentDefinition, variantName: string): FragmentVariant | undefined {
|
|
83
|
+
return fragment.variants?.find(v => v.name === variantName);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
type PreviewMode = 'centered' | 'full-bleed';
|
|
87
87
|
|
|
88
|
-
function resolvePreviewMode(
|
|
89
|
-
const name =
|
|
90
|
-
const category = (
|
|
88
|
+
function resolvePreviewMode(fragment: FragmentDefinition): PreviewMode {
|
|
89
|
+
const name = fragment.meta.name.toLowerCase();
|
|
90
|
+
const category = (fragment.meta.category || '').toLowerCase();
|
|
91
91
|
|
|
92
92
|
if (category === 'layout' || category === 'navigation') {
|
|
93
93
|
return 'full-bleed';
|
|
@@ -140,7 +140,7 @@ function VariantRenderer({
|
|
|
140
140
|
onRendered,
|
|
141
141
|
onError,
|
|
142
142
|
}: {
|
|
143
|
-
variant:
|
|
143
|
+
variant: FragmentVariant;
|
|
144
144
|
props?: Record<string, unknown>;
|
|
145
145
|
mode: PreviewMode;
|
|
146
146
|
onRendered: (width: number, height: number) => void;
|
|
@@ -245,9 +245,9 @@ function VariantRenderer({
|
|
|
245
245
|
*/
|
|
246
246
|
export function PreviewFrameHost() {
|
|
247
247
|
const { renderRequest, theme, notifyReady, notifyRendered, notifyError } = useFrameBridge();
|
|
248
|
-
const [
|
|
248
|
+
const [fragments, setFragments] = useState<FragmentItem[] | null>(null);
|
|
249
249
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
250
|
-
const [currentVariant, setCurrentVariant] = useState<
|
|
250
|
+
const [currentVariant, setCurrentVariant] = useState<FragmentVariant | null>(null);
|
|
251
251
|
const [currentProps, setCurrentProps] = useState<Record<string, unknown> | undefined>(undefined);
|
|
252
252
|
const [previewMode, setPreviewMode] = useState<PreviewMode>('centered');
|
|
253
253
|
|
|
@@ -264,15 +264,15 @@ export function PreviewFrameHost() {
|
|
|
264
264
|
document.body.setAttribute('data-preview-mode', previewMode);
|
|
265
265
|
}, [previewMode]);
|
|
266
266
|
|
|
267
|
-
// Load
|
|
267
|
+
// Load fragments on mount
|
|
268
268
|
useEffect(() => {
|
|
269
|
-
|
|
269
|
+
loadFragments()
|
|
270
270
|
.then(segs => {
|
|
271
|
-
|
|
271
|
+
setFragments(segs);
|
|
272
272
|
notifyReady();
|
|
273
273
|
})
|
|
274
274
|
.catch(err => {
|
|
275
|
-
const message = err instanceof Error ? err.message : 'Failed to load
|
|
275
|
+
const message = err instanceof Error ? err.message : 'Failed to load fragments';
|
|
276
276
|
setLoadError(message);
|
|
277
277
|
notifyError(message);
|
|
278
278
|
});
|
|
@@ -280,33 +280,33 @@ export function PreviewFrameHost() {
|
|
|
280
280
|
|
|
281
281
|
// Handle render requests
|
|
282
282
|
useEffect(() => {
|
|
283
|
-
if (!renderRequest || !
|
|
283
|
+
if (!renderRequest || !fragments) return;
|
|
284
284
|
|
|
285
|
-
const {
|
|
285
|
+
const { fragmentPath, variantName, props } = renderRequest;
|
|
286
286
|
|
|
287
|
-
// Find
|
|
288
|
-
const
|
|
289
|
-
if (!
|
|
290
|
-
notifyError(`
|
|
287
|
+
// Find fragment
|
|
288
|
+
const fragmentItem = findFragmentByPath(fragments, fragmentPath);
|
|
289
|
+
if (!fragmentItem) {
|
|
290
|
+
notifyError(`Fragment not found: ${fragmentPath}`);
|
|
291
291
|
setCurrentVariant(null);
|
|
292
292
|
return;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
// Find variant
|
|
296
|
-
const variant = findVariant(
|
|
296
|
+
const variant = findVariant(fragmentItem.fragment, variantName);
|
|
297
297
|
if (!variant) {
|
|
298
|
-
notifyError(`Variant not found: ${variantName} in ${
|
|
298
|
+
notifyError(`Variant not found: ${variantName} in ${fragmentPath}`);
|
|
299
299
|
setCurrentVariant(null);
|
|
300
300
|
return;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
setPreviewMode(resolvePreviewMode(
|
|
303
|
+
setPreviewMode(resolvePreviewMode(fragmentItem.fragment));
|
|
304
304
|
setCurrentVariant(variant);
|
|
305
305
|
setCurrentProps(props);
|
|
306
|
-
}, [renderRequest,
|
|
306
|
+
}, [renderRequest, fragments, notifyError]);
|
|
307
307
|
|
|
308
308
|
// Show loading state
|
|
309
|
-
if (!
|
|
309
|
+
if (!fragments && !loadError) {
|
|
310
310
|
return <LoadingIndicator />;
|
|
311
311
|
}
|
|
312
312
|
|
|
@@ -327,7 +327,7 @@ export function PreviewFrameHost() {
|
|
|
327
327
|
// Render the variant
|
|
328
328
|
return (
|
|
329
329
|
<VariantRenderer
|
|
330
|
-
key={`${renderRequest?.
|
|
330
|
+
key={`${renderRequest?.fragmentPath}-${renderRequest?.variantName}`}
|
|
331
331
|
variant={currentVariant}
|
|
332
332
|
props={currentProps}
|
|
333
333
|
mode={previewMode}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FragmentDefinition } from '../../core/index.js';
|
|
2
2
|
import { useScrollSpy } from '../hooks/useScrollSpy.js';
|
|
3
3
|
import { Sidebar } from '@fragments/ui';
|
|
4
4
|
|
|
5
5
|
interface RightSidebarProps {
|
|
6
|
-
|
|
6
|
+
fragment: FragmentDefinition;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
interface TocItem {
|
|
@@ -12,19 +12,19 @@ interface TocItem {
|
|
|
12
12
|
children?: TocItem[];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function RightSidebar({
|
|
16
|
-
// Build table of contents from
|
|
15
|
+
export function RightSidebar({ fragment }: RightSidebarProps) {
|
|
16
|
+
// Build table of contents from fragment
|
|
17
17
|
const tocItems: TocItem[] = [];
|
|
18
18
|
|
|
19
19
|
// Overview section
|
|
20
20
|
tocItems.push({ id: 'overview', label: 'Overview' });
|
|
21
21
|
|
|
22
22
|
// Variants section with nested items
|
|
23
|
-
if (
|
|
23
|
+
if (fragment.variants && fragment.variants.length > 0) {
|
|
24
24
|
tocItems.push({
|
|
25
25
|
id: 'variants',
|
|
26
26
|
label: 'Variants',
|
|
27
|
-
children:
|
|
27
|
+
children: fragment.variants.map((variant, index) => ({
|
|
28
28
|
id: `variant-${index}`,
|
|
29
29
|
label: variant.name,
|
|
30
30
|
})),
|
|
@@ -32,17 +32,17 @@ export function RightSidebar({ segment }: RightSidebarProps) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// Usage section
|
|
35
|
-
if (
|
|
35
|
+
if (fragment.usage) {
|
|
36
36
|
tocItems.push({ id: 'usage', label: 'Usage' });
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// Props section
|
|
40
|
-
if (
|
|
40
|
+
if (fragment.props && Object.keys(fragment.props).length > 0) {
|
|
41
41
|
tocItems.push({ id: 'props', label: 'Props' });
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// Relations section
|
|
45
|
-
if (
|
|
45
|
+
if (fragment.relations && fragment.relations.length > 0) {
|
|
46
46
|
tocItems.push({ id: 'relations', label: 'Relations' });
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import React, { useState, useMemo } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FragmentDefinition } from '../../core/index.js';
|
|
3
3
|
|
|
4
4
|
interface SidebarProps {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
6
|
+
activeFragment: string | null;
|
|
7
7
|
onSelect: (path: string) => void;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function Sidebar({
|
|
10
|
+
export function Sidebar({ fragments, activeFragment, onSelect }: SidebarProps): React.ReactElement {
|
|
11
11
|
const [search, setSearch] = useState('');
|
|
12
12
|
|
|
13
|
-
// Group
|
|
13
|
+
// Group fragments by category
|
|
14
14
|
const grouped = useMemo(() => {
|
|
15
|
-
const groups: Record<string, typeof
|
|
15
|
+
const groups: Record<string, typeof fragments> = {};
|
|
16
16
|
|
|
17
|
-
for (const item of
|
|
18
|
-
const category = item.
|
|
17
|
+
for (const item of fragments) {
|
|
18
|
+
const category = item.fragment.meta.category || 'uncategorized';
|
|
19
19
|
if (!groups[category]) {
|
|
20
20
|
groups[category] = [];
|
|
21
21
|
}
|
|
@@ -24,12 +24,12 @@ export function Sidebar({ segments, activeSegment, onSelect }: SidebarProps): Re
|
|
|
24
24
|
|
|
25
25
|
// Filter by search
|
|
26
26
|
if (search) {
|
|
27
|
-
const filtered: Record<string, typeof
|
|
27
|
+
const filtered: Record<string, typeof fragments> = {};
|
|
28
28
|
for (const [category, items] of Object.entries(groups)) {
|
|
29
29
|
const matchingItems = items.filter(
|
|
30
30
|
(item) =>
|
|
31
|
-
item.
|
|
32
|
-
item.
|
|
31
|
+
item.fragment.meta.name.toLowerCase().includes(search.toLowerCase()) ||
|
|
32
|
+
item.fragment.meta.description.toLowerCase().includes(search.toLowerCase())
|
|
33
33
|
);
|
|
34
34
|
if (matchingItems.length > 0) {
|
|
35
35
|
filtered[category] = matchingItems;
|
|
@@ -39,7 +39,7 @@ export function Sidebar({ segments, activeSegment, onSelect }: SidebarProps): Re
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
return groups;
|
|
42
|
-
}, [
|
|
42
|
+
}, [fragments, search]);
|
|
43
43
|
|
|
44
44
|
return (
|
|
45
45
|
<div
|
|
@@ -68,7 +68,7 @@ export function Sidebar({ segments, activeSegment, onSelect }: SidebarProps): Re
|
|
|
68
68
|
color: '#111827',
|
|
69
69
|
}}
|
|
70
70
|
>
|
|
71
|
-
|
|
71
|
+
Fragments
|
|
72
72
|
</h1>
|
|
73
73
|
<p
|
|
74
74
|
style={{
|
|
@@ -77,7 +77,7 @@ export function Sidebar({ segments, activeSegment, onSelect }: SidebarProps): Re
|
|
|
77
77
|
color: '#6b7280',
|
|
78
78
|
}}
|
|
79
79
|
>
|
|
80
|
-
{
|
|
80
|
+
{fragments.length} component{fragments.length !== 1 ? 's' : ''}
|
|
81
81
|
</p>
|
|
82
82
|
</div>
|
|
83
83
|
|
|
@@ -132,7 +132,7 @@ export function Sidebar({ segments, activeSegment, onSelect }: SidebarProps): Re
|
|
|
132
132
|
padding: '8px 12px',
|
|
133
133
|
border: 'none',
|
|
134
134
|
borderRadius: '6px',
|
|
135
|
-
background:
|
|
135
|
+
background: activeFragment === item.path ? '#e5e7eb' : 'transparent',
|
|
136
136
|
textAlign: 'left',
|
|
137
137
|
cursor: 'pointer',
|
|
138
138
|
transition: 'background 0.15s',
|
|
@@ -145,7 +145,7 @@ export function Sidebar({ segments, activeSegment, onSelect }: SidebarProps): Re
|
|
|
145
145
|
color: '#111827',
|
|
146
146
|
}}
|
|
147
147
|
>
|
|
148
|
-
{item.
|
|
148
|
+
{item.fragment.meta.name}
|
|
149
149
|
</div>
|
|
150
150
|
<div
|
|
151
151
|
style={{
|
|
@@ -157,7 +157,7 @@ export function Sidebar({ segments, activeSegment, onSelect }: SidebarProps): Re
|
|
|
157
157
|
whiteSpace: 'nowrap',
|
|
158
158
|
}}
|
|
159
159
|
>
|
|
160
|
-
{item.
|
|
160
|
+
{item.fragment.meta.description}
|
|
161
161
|
</div>
|
|
162
162
|
</button>
|
|
163
163
|
))}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useState, useEffect, useMemo, type ReactNode } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { FragmentVariant } from "../../core/index.js";
|
|
3
3
|
|
|
4
4
|
interface StoryRendererProps {
|
|
5
5
|
/** The variant to render */
|
|
6
|
-
variant:
|
|
6
|
+
variant: FragmentVariant;
|
|
7
7
|
/** Children render function - receives rendered content */
|
|
8
8
|
children: (content: ReactNode | null, isLoading: boolean, error: Error | null) => ReactNode;
|
|
9
9
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FragmentUsage } from '../../core/index.js';
|
|
2
2
|
import { CheckIcon, XIcon, AccessibilityIcon } from './Icons.js';
|
|
3
3
|
|
|
4
4
|
interface UsageSectionProps {
|
|
5
|
-
usage:
|
|
5
|
+
usage: FragmentUsage;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function UsageSection({ usage }: UsageSectionProps) {
|