@fragments-sdk/cli 0.6.0 → 0.7.1
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 +529 -285
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-F7ITZPDJ.js → chunk-32VIEOQY.js} +18 -18
- package/dist/chunk-32VIEOQY.js.map +1 -0
- package/dist/{chunk-SSLQXHNX.js → chunk-5ITIP3ES.js} +27 -27
- package/dist/chunk-5ITIP3ES.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-Q7GOHVOK.js → chunk-GCZMFLDI.js} +67 -32
- package/dist/chunk-GCZMFLDI.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-D35RGPAG.js → chunk-U6VTHBNI.js} +499 -83
- package/dist/chunk-U6VTHBNI.js.map +1 -0
- package/dist/{core-SKRPJQZG.js → core-SFHPYR5H.js} +24 -26
- package/dist/{generate-7AF7WRVK.js → generate-54GJAWUY.js} +5 -5
- package/dist/generate-54GJAWUY.js.map +1 -0
- package/dist/index.d.ts +23 -27
- package/dist/index.js +10 -10
- package/dist/{init-WKGDPYI4.js → init-EIM5WNMP.js} +5 -5
- package/dist/{init-WKGDPYI4.js.map → init-EIM5WNMP.js.map} +1 -1
- package/dist/mcp-bin.js +73 -73
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-KQBKUS64.js +12 -0
- package/dist/{service-F3E4JJM7.js → service-ED2LNCTU.js} +6 -6
- package/dist/{static-viewer-4LQZ5AGA.js → static-viewer-Q4F4QP5M.js} +4 -4
- package/dist/{test-CJDNJTPZ.js → test-6VN2DA3S.js} +19 -19
- package/dist/test-6VN2DA3S.js.map +1 -0
- package/dist/{tokens-JAJABYXP.js → tokens-P2B7ZAM3.js} +5 -5
- package/dist/{viewer-R3Q6WAMJ.js → viewer-GM7IQPPB.js} +199 -199
- package/dist/viewer-GM7IQPPB.js.map +1 -0
- package/package.json +2 -2
- package/src/ai.ts +5 -5
- package/src/analyze.ts +11 -11
- package/src/bin.ts +24 -1
- package/src/build.ts +64 -21
- 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 +274 -0
- 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 +83 -20
- 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 +542 -0
- package/src/core/graph-extractor.ts +601 -0
- package/src/core/importAnalyzer.ts +6 -1
- package/src/core/index.ts +22 -23
- package/src/core/loader.ts +22 -22
- package/src/core/node.ts +5 -5
- package/src/core/parser.ts +31 -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/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 +201 -103
- 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/Icons.tsx +53 -1
- 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/Layout.tsx +7 -3
- package/src/viewer/components/LeftSidebar.tsx +92 -114
- package/src/viewer/components/MultiViewportPreview.tsx +14 -14
- package/src/viewer/components/PreviewArea.tsx +11 -11
- package/src/viewer/components/PreviewFrameHost.tsx +77 -48
- package/src/viewer/components/PreviewToolbar.tsx +57 -10
- 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/ViewportSelector.tsx +56 -45
- package/src/viewer/components/_future/CreatePage.tsx +6 -6
- package/src/viewer/composition-renderer.ts +11 -11
- package/src/viewer/constants/ui.ts +4 -4
- 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/preview-frame.html +22 -13
- 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/styles/globals.css +42 -81
- 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-D35RGPAG.js.map +0 -1
- package/dist/chunk-F7ITZPDJ.js.map +0 -1
- package/dist/chunk-NWQ4CJOQ.js.map +0 -1
- package/dist/chunk-Q7GOHVOK.js.map +0 -1
- package/dist/chunk-RVRTRESS.js.map +0 -1
- package/dist/chunk-SSLQXHNX.js.map +0 -1
- package/dist/generate-7AF7WRVK.js.map +0 -1
- package/dist/scan-K6JNMCGM.js +0 -12
- package/dist/test-CJDNJTPZ.js.map +0 -1
- package/dist/viewer-R3Q6WAMJ.js.map +0 -1
- package/src/shared/segment-loader.ts +0 -59
- /package/dist/{core-SKRPJQZG.js.map → core-SFHPYR5H.js.map} +0 -0
- /package/dist/{scan-K6JNMCGM.js.map → scan-KQBKUS64.js.map} +0 -0
- /package/dist/{service-F3E4JJM7.js.map → service-ED2LNCTU.js.map} +0 -0
- /package/dist/{static-viewer-4LQZ5AGA.js.map → static-viewer-Q4F4QP5M.js.map} +0 -0
- /package/dist/{tokens-JAJABYXP.js.map → tokens-P2B7ZAM3.js.map} +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit tests for
|
|
2
|
+
* Unit tests for storyModuleToFragment and related utilities.
|
|
3
3
|
* Tests both CSF2 (Template.bind) and CSF3 (object stories) patterns.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { describe, it, expect, beforeEach } from "vitest";
|
|
7
7
|
import { createElement, type ComponentType } from "react";
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
storyModuleToFragment,
|
|
10
10
|
setPreviewConfig,
|
|
11
11
|
toId,
|
|
12
12
|
storyNameFromExport,
|
|
@@ -23,14 +23,14 @@ const MockComponent = (({ label = "Test" }: { label?: string }) =>
|
|
|
23
23
|
createElement("button", null, label)) as ComponentType<unknown>;
|
|
24
24
|
(MockComponent as unknown as { displayName: string }).displayName = "MockComponent";
|
|
25
25
|
|
|
26
|
-
describe("
|
|
26
|
+
describe("storyModuleToFragment", () => {
|
|
27
27
|
beforeEach(() => {
|
|
28
28
|
// Reset preview config before each test
|
|
29
29
|
setPreviewConfig({});
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
describe("basic conversion", () => {
|
|
33
|
-
it("should convert a basic story module to a
|
|
33
|
+
it("should convert a basic story module to a fragment definition", () => {
|
|
34
34
|
const storyModule: StoryModule = {
|
|
35
35
|
default: {
|
|
36
36
|
title: "Components/Button",
|
|
@@ -41,14 +41,14 @@ describe("storyModuleToSegment", () => {
|
|
|
41
41
|
} as Story,
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const
|
|
44
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
45
45
|
|
|
46
|
-
expect(
|
|
47
|
-
expect(
|
|
48
|
-
expect(
|
|
49
|
-
expect(
|
|
50
|
-
expect(
|
|
51
|
-
expect(
|
|
46
|
+
expect(fragment).not.toBeNull();
|
|
47
|
+
expect(fragment!.meta.name).toBe("Button");
|
|
48
|
+
expect(fragment!.meta.category).toBe("general");
|
|
49
|
+
expect(fragment!.component).toBe(MockComponent);
|
|
50
|
+
expect(fragment!.variants).toHaveLength(1);
|
|
51
|
+
expect(fragment!.variants[0].name).toBe("Primary");
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
it("should return null for stories without a component", () => {
|
|
@@ -58,8 +58,8 @@ describe("storyModuleToSegment", () => {
|
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
const
|
|
62
|
-
expect(
|
|
61
|
+
const fragment = storyModuleToFragment(storyModule, "Intro.stories.tsx");
|
|
62
|
+
expect(fragment).toBeNull();
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
it("should extract component name from title path", () => {
|
|
@@ -71,10 +71,10 @@ describe("storyModuleToSegment", () => {
|
|
|
71
71
|
Default: { args: {} } as Story,
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
const
|
|
74
|
+
const fragment = storyModuleToFragment(storyModule, "TextField.stories.tsx");
|
|
75
75
|
|
|
76
|
-
expect(
|
|
77
|
-
expect(
|
|
76
|
+
expect(fragment!.meta.name).toBe("TextField");
|
|
77
|
+
expect(fragment!.meta.category).toBe("forms");
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
it("should use component displayName when title is not provided", () => {
|
|
@@ -85,9 +85,9 @@ describe("storyModuleToSegment", () => {
|
|
|
85
85
|
Default: { args: {} } as Story,
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
const
|
|
88
|
+
const fragment = storyModuleToFragment(storyModule, "path/to/Component.stories.tsx");
|
|
89
89
|
|
|
90
|
-
expect(
|
|
90
|
+
expect(fragment!.meta.name).toBe("MockComponent");
|
|
91
91
|
});
|
|
92
92
|
});
|
|
93
93
|
|
|
@@ -112,11 +112,11 @@ describe("storyModuleToSegment", () => {
|
|
|
112
112
|
Secondary,
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
const
|
|
115
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
116
116
|
|
|
117
|
-
expect(
|
|
118
|
-
expect(
|
|
119
|
-
expect(
|
|
117
|
+
expect(fragment!.variants).toHaveLength(2);
|
|
118
|
+
expect(fragment!.variants[0].name).toBe("Primary");
|
|
119
|
+
expect(fragment!.variants[1].name).toBe("Secondary");
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
it("should use storyName when provided on CSF2 story", () => {
|
|
@@ -135,9 +135,9 @@ describe("storyModuleToSegment", () => {
|
|
|
135
135
|
MyStory,
|
|
136
136
|
};
|
|
137
137
|
|
|
138
|
-
const
|
|
138
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
139
139
|
|
|
140
|
-
expect(
|
|
140
|
+
expect(fragment!.variants[0].name).toBe("Custom Story Name");
|
|
141
141
|
});
|
|
142
142
|
});
|
|
143
143
|
|
|
@@ -157,11 +157,11 @@ describe("storyModuleToSegment", () => {
|
|
|
157
157
|
} as Story,
|
|
158
158
|
};
|
|
159
159
|
|
|
160
|
-
const
|
|
160
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
161
161
|
|
|
162
|
-
expect(
|
|
163
|
-
expect(
|
|
164
|
-
expect(
|
|
162
|
+
expect(fragment!.variants).toHaveLength(2);
|
|
163
|
+
expect(fragment!.variants[0].name).toBe("Primary");
|
|
164
|
+
expect(fragment!.variants[1].name).toBe("With Custom Render");
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
it("should use story name property when provided", () => {
|
|
@@ -176,9 +176,9 @@ describe("storyModuleToSegment", () => {
|
|
|
176
176
|
} as Story,
|
|
177
177
|
};
|
|
178
178
|
|
|
179
|
-
const
|
|
179
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
180
180
|
|
|
181
|
-
expect(
|
|
181
|
+
expect(fragment!.variants[0].name).toBe("Explicit Name");
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
it("should detect play functions", () => {
|
|
@@ -198,10 +198,10 @@ describe("storyModuleToSegment", () => {
|
|
|
198
198
|
} as Story,
|
|
199
199
|
};
|
|
200
200
|
|
|
201
|
-
const
|
|
201
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
202
202
|
|
|
203
|
-
expect(
|
|
204
|
-
expect(
|
|
203
|
+
expect(fragment!.variants[0].hasPlayFunction).toBe(true);
|
|
204
|
+
expect(fragment!.variants[1].hasPlayFunction).toBeUndefined();
|
|
205
205
|
});
|
|
206
206
|
});
|
|
207
207
|
|
|
@@ -230,13 +230,13 @@ describe("storyModuleToSegment", () => {
|
|
|
230
230
|
Default: { args: {} } as Story,
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
-
const
|
|
233
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
234
234
|
|
|
235
|
-
expect(
|
|
236
|
-
expect(
|
|
237
|
-
expect(
|
|
238
|
-
expect(
|
|
239
|
-
expect(
|
|
235
|
+
expect(fragment!.props.label.type).toBe("string");
|
|
236
|
+
expect(fragment!.props.label.description).toBe("Button label text");
|
|
237
|
+
expect(fragment!.props.disabled.type).toBe("boolean");
|
|
238
|
+
expect(fragment!.props.size.type).toBe("enum");
|
|
239
|
+
expect(fragment!.props.size.values).toEqual(["small", "medium", "large"]);
|
|
240
240
|
});
|
|
241
241
|
|
|
242
242
|
it("should handle all control types", () => {
|
|
@@ -261,23 +261,23 @@ describe("storyModuleToSegment", () => {
|
|
|
261
261
|
Default: { args: {} } as Story,
|
|
262
262
|
};
|
|
263
263
|
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
expect(
|
|
267
|
-
expect(
|
|
268
|
-
expect(
|
|
269
|
-
expect(
|
|
270
|
-
expect(
|
|
271
|
-
expect(
|
|
272
|
-
expect(
|
|
273
|
-
expect(
|
|
274
|
-
expect(
|
|
275
|
-
expect(
|
|
276
|
-
expect(
|
|
277
|
-
expect(
|
|
278
|
-
expect(
|
|
279
|
-
expect(
|
|
280
|
-
expect(
|
|
264
|
+
const fragment = storyModuleToFragment(storyModule, "Test.stories.tsx");
|
|
265
|
+
|
|
266
|
+
expect(fragment!.props.text.type).toBe("string");
|
|
267
|
+
expect(fragment!.props.number.type).toBe("number");
|
|
268
|
+
expect(fragment!.props.range.type).toBe("number");
|
|
269
|
+
expect(fragment!.props.range.controlType).toBe("range");
|
|
270
|
+
expect(fragment!.props.range.controlOptions).toEqual({ min: 0, max: 100 });
|
|
271
|
+
expect(fragment!.props.boolean.type).toBe("boolean");
|
|
272
|
+
expect(fragment!.props.color.type).toBe("string");
|
|
273
|
+
expect(fragment!.props.color.controlType).toBe("color");
|
|
274
|
+
expect(fragment!.props.date.type).toBe("string");
|
|
275
|
+
expect(fragment!.props.date.controlType).toBe("date");
|
|
276
|
+
expect(fragment!.props.object.type).toBe("object");
|
|
277
|
+
expect(fragment!.props.radio.type).toBe("enum");
|
|
278
|
+
expect(fragment!.props.inlineRadio.type).toBe("enum");
|
|
279
|
+
expect(fragment!.props.select.type).toBe("enum");
|
|
280
|
+
expect(fragment!.props.multiSelect.type).toBe("enum");
|
|
281
281
|
});
|
|
282
282
|
|
|
283
283
|
it("should handle action argTypes as functions", () => {
|
|
@@ -294,9 +294,9 @@ describe("storyModuleToSegment", () => {
|
|
|
294
294
|
Default: { args: {} } as Story,
|
|
295
295
|
};
|
|
296
296
|
|
|
297
|
-
const
|
|
297
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
298
298
|
|
|
299
|
-
expect(
|
|
299
|
+
expect(fragment!.props.onClick.type).toBe("function");
|
|
300
300
|
});
|
|
301
301
|
|
|
302
302
|
it("should skip disabled argTypes", () => {
|
|
@@ -312,10 +312,10 @@ describe("storyModuleToSegment", () => {
|
|
|
312
312
|
Default: { args: {} } as Story,
|
|
313
313
|
};
|
|
314
314
|
|
|
315
|
-
const
|
|
315
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
316
316
|
|
|
317
|
-
expect(
|
|
318
|
-
expect(
|
|
317
|
+
expect(fragment!.props.visible).toBeDefined();
|
|
318
|
+
expect(fragment!.props.hidden).toBeUndefined();
|
|
319
319
|
});
|
|
320
320
|
});
|
|
321
321
|
|
|
@@ -339,8 +339,8 @@ describe("storyModuleToSegment", () => {
|
|
|
339
339
|
} as Story,
|
|
340
340
|
};
|
|
341
341
|
|
|
342
|
-
const
|
|
343
|
-
|
|
342
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
343
|
+
fragment!.variants[0].render();
|
|
344
344
|
|
|
345
345
|
expect(decoratorCalled).toBe(true);
|
|
346
346
|
});
|
|
@@ -363,9 +363,9 @@ describe("storyModuleToSegment", () => {
|
|
|
363
363
|
Story2: { args: { label: "2" } } as Story,
|
|
364
364
|
};
|
|
365
365
|
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
366
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
367
|
+
fragment!.variants[0].render();
|
|
368
|
+
fragment!.variants[1].render();
|
|
369
369
|
|
|
370
370
|
expect(metaDecoratorCount).toBe(2);
|
|
371
371
|
});
|
|
@@ -390,8 +390,8 @@ describe("storyModuleToSegment", () => {
|
|
|
390
390
|
Default: { args: { label: "Test" } } as Story,
|
|
391
391
|
};
|
|
392
392
|
|
|
393
|
-
const
|
|
394
|
-
|
|
393
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
394
|
+
fragment!.variants[0].render();
|
|
395
395
|
|
|
396
396
|
expect(globalDecoratorCalled).toBe(true);
|
|
397
397
|
});
|
|
@@ -410,12 +410,12 @@ describe("storyModuleToSegment", () => {
|
|
|
410
410
|
Excluded: { args: { label: "Excluded" } } as Story,
|
|
411
411
|
};
|
|
412
412
|
|
|
413
|
-
const
|
|
413
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
414
414
|
|
|
415
|
-
expect(
|
|
416
|
-
expect(
|
|
417
|
-
expect(
|
|
418
|
-
expect(
|
|
415
|
+
expect(fragment!.variants).toHaveLength(2);
|
|
416
|
+
expect(fragment!.variants.map((v) => v.name)).toContain("Primary");
|
|
417
|
+
expect(fragment!.variants.map((v) => v.name)).toContain("Secondary");
|
|
418
|
+
expect(fragment!.variants.map((v) => v.name)).not.toContain("Excluded");
|
|
419
419
|
});
|
|
420
420
|
|
|
421
421
|
it("should filter stories using excludeStories array", () => {
|
|
@@ -429,10 +429,10 @@ describe("storyModuleToSegment", () => {
|
|
|
429
429
|
Internal: { args: { label: "Internal" } } as Story,
|
|
430
430
|
};
|
|
431
431
|
|
|
432
|
-
const
|
|
432
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
433
433
|
|
|
434
|
-
expect(
|
|
435
|
-
expect(
|
|
434
|
+
expect(fragment!.variants).toHaveLength(1);
|
|
435
|
+
expect(fragment!.variants[0].name).toBe("Primary");
|
|
436
436
|
});
|
|
437
437
|
|
|
438
438
|
it("should filter stories using excludeStories regex", () => {
|
|
@@ -447,10 +447,10 @@ describe("storyModuleToSegment", () => {
|
|
|
447
447
|
_Internal: { args: { label: "Internal" } } as Story,
|
|
448
448
|
};
|
|
449
449
|
|
|
450
|
-
const
|
|
450
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
451
451
|
|
|
452
|
-
expect(
|
|
453
|
-
expect(
|
|
452
|
+
expect(fragment!.variants).toHaveLength(1);
|
|
453
|
+
expect(fragment!.variants[0].name).toBe("Primary");
|
|
454
454
|
});
|
|
455
455
|
});
|
|
456
456
|
|
|
@@ -467,10 +467,10 @@ describe("storyModuleToSegment", () => {
|
|
|
467
467
|
} as Story,
|
|
468
468
|
};
|
|
469
469
|
|
|
470
|
-
const
|
|
470
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
471
471
|
|
|
472
|
-
expect(
|
|
473
|
-
expect(
|
|
472
|
+
expect(fragment!.variants[0].loaders).toBeDefined();
|
|
473
|
+
expect(fragment!.variants[0].loaders).toHaveLength(1);
|
|
474
474
|
});
|
|
475
475
|
|
|
476
476
|
it("should collect loaders from meta and story", () => {
|
|
@@ -486,10 +486,10 @@ describe("storyModuleToSegment", () => {
|
|
|
486
486
|
} as Story,
|
|
487
487
|
};
|
|
488
488
|
|
|
489
|
-
const
|
|
489
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
490
490
|
|
|
491
491
|
// Should have both meta and story loaders
|
|
492
|
-
expect(
|
|
492
|
+
expect(fragment!.variants[0].loaders).toHaveLength(2);
|
|
493
493
|
});
|
|
494
494
|
});
|
|
495
495
|
|
|
@@ -504,11 +504,11 @@ describe("storyModuleToSegment", () => {
|
|
|
504
504
|
WithIcon: { args: { label: "With Icon" } } as Story,
|
|
505
505
|
};
|
|
506
506
|
|
|
507
|
-
const
|
|
507
|
+
const fragment = storyModuleToFragment(storyModule, "Button.stories.tsx");
|
|
508
508
|
|
|
509
509
|
// toId lowercases the export name without adding hyphens
|
|
510
|
-
expect(
|
|
511
|
-
expect(
|
|
510
|
+
expect(fragment!.variants[0].storyId).toBe("components-forms-button--primary");
|
|
511
|
+
expect(fragment!.variants[1].storyId).toBe("components-forms-button--withicon");
|
|
512
512
|
});
|
|
513
513
|
});
|
|
514
514
|
});
|
package/src/core/storyAdapter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime adapter for converting Storybook CSF modules to
|
|
2
|
+
* Runtime adapter for converting Storybook CSF modules to Fragment definitions.
|
|
3
3
|
*
|
|
4
4
|
* This operates on IMPORTED modules at runtime, not source code parsing.
|
|
5
5
|
* By leveraging Vite's module system, we get 100% accurate render functions
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
import { createElement, type ComponentType, type ReactNode } from "react";
|
|
12
12
|
import { toId, storyNameFromExport, isExportStory } from "./storybook-csf.js";
|
|
13
13
|
import type {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
FragmentDefinition,
|
|
15
|
+
FragmentMeta,
|
|
16
|
+
FragmentUsage,
|
|
17
17
|
PropDefinition,
|
|
18
|
-
|
|
18
|
+
FragmentVariant,
|
|
19
19
|
ControlType,
|
|
20
20
|
VariantLoader,
|
|
21
21
|
PlayFunction,
|
|
@@ -194,16 +194,16 @@ export function getPreviewConfig(): PreviewConfig {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
/**
|
|
197
|
-
* Convert a Storybook module to a
|
|
197
|
+
* Convert a Storybook module to a Fragment definition at runtime.
|
|
198
198
|
*
|
|
199
199
|
* @param storyModule - The imported Storybook module
|
|
200
200
|
* @param filePath - File path for metadata extraction
|
|
201
|
-
* @returns A complete
|
|
201
|
+
* @returns A complete FragmentDefinition ready for the viewer
|
|
202
202
|
*/
|
|
203
|
-
export function
|
|
203
|
+
export function storyModuleToFragment(
|
|
204
204
|
storyModule: StoryModule,
|
|
205
205
|
filePath: string
|
|
206
|
-
):
|
|
206
|
+
): FragmentDefinition | null {
|
|
207
207
|
const meta = storyModule.default;
|
|
208
208
|
const component = meta.component;
|
|
209
209
|
|
|
@@ -220,7 +220,7 @@ export function storyModuleToSegment(
|
|
|
220
220
|
// Extract Figma URL from parameters.design.url (storybook-addon-designs) or parameters.figma
|
|
221
221
|
const figmaUrl = extractFigmaUrl(meta.parameters);
|
|
222
222
|
|
|
223
|
-
const
|
|
223
|
+
const fragmentMeta: FragmentMeta = {
|
|
224
224
|
name: componentName,
|
|
225
225
|
description:
|
|
226
226
|
meta.parameters?.docs?.description?.component ??
|
|
@@ -231,14 +231,14 @@ export function storyModuleToSegment(
|
|
|
231
231
|
figma: figmaUrl,
|
|
232
232
|
};
|
|
233
233
|
|
|
234
|
-
const usage:
|
|
234
|
+
const usage: FragmentUsage = {
|
|
235
235
|
when: [`Use ${componentName} for its intended purpose`],
|
|
236
236
|
whenNot: ["When a more specific component is available"],
|
|
237
237
|
};
|
|
238
238
|
|
|
239
239
|
return {
|
|
240
240
|
component,
|
|
241
|
-
meta:
|
|
241
|
+
meta: fragmentMeta,
|
|
242
242
|
usage,
|
|
243
243
|
props,
|
|
244
244
|
variants,
|
|
@@ -249,7 +249,7 @@ export function storyModuleToSegment(
|
|
|
249
249
|
* Extract component name from meta or file path
|
|
250
250
|
*/
|
|
251
251
|
function extractComponentName(meta: StoryMeta, filePath: string): string {
|
|
252
|
-
// Try title (last
|
|
252
|
+
// Try title (last fragment of path like "Components/Forms/Button" -> "Button")
|
|
253
253
|
if (meta.title) {
|
|
254
254
|
const parts = meta.title.split("/");
|
|
255
255
|
return parts[parts.length - 1];
|
|
@@ -307,7 +307,7 @@ function extractFigmaUrl(parameters?: Record<string, unknown>): string | undefin
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
/**
|
|
310
|
-
* Convert Storybook argTypes to
|
|
310
|
+
* Convert Storybook argTypes to Fragment props
|
|
311
311
|
* Merges global argTypes from preview config with meta argTypes
|
|
312
312
|
*/
|
|
313
313
|
function convertArgTypes(
|
|
@@ -482,8 +482,8 @@ function extractVariants(
|
|
|
482
482
|
storyModule: StoryModule,
|
|
483
483
|
component: ComponentType<unknown>,
|
|
484
484
|
meta: StoryMeta
|
|
485
|
-
):
|
|
486
|
-
const variants:
|
|
485
|
+
): FragmentVariant[] {
|
|
486
|
+
const variants: FragmentVariant[] = [];
|
|
487
487
|
|
|
488
488
|
for (const [exportName, exportValue] of Object.entries(storyModule)) {
|
|
489
489
|
// Skip default export
|
package/src/core/types.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode, JSX } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A React component that can be used in a
|
|
4
|
+
* A React component that can be used in a fragment definition.
|
|
5
5
|
* This type is intentionally broad to support various React component patterns
|
|
6
6
|
* including FC, forwardRef, memo, and class components across different React versions.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export type
|
|
9
|
+
export type FragmentComponent<TProps = any> =
|
|
10
10
|
| ComponentType<TProps>
|
|
11
11
|
| ((props: TProps) => ReactNode | JSX.Element | null);
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Metadata about the component
|
|
15
15
|
*/
|
|
16
|
-
export interface
|
|
16
|
+
export interface FragmentMeta {
|
|
17
17
|
/** Component display name */
|
|
18
18
|
name: string;
|
|
19
19
|
|
|
@@ -91,7 +91,7 @@ export interface FigmaTextContentMapping {
|
|
|
91
91
|
/**
|
|
92
92
|
* Usage guidelines for AI agents and developers
|
|
93
93
|
*/
|
|
94
|
-
export interface
|
|
94
|
+
export interface FragmentUsage {
|
|
95
95
|
/** When to use this component */
|
|
96
96
|
when: string[];
|
|
97
97
|
|
|
@@ -238,7 +238,7 @@ export interface VariantRenderOptions {
|
|
|
238
238
|
/**
|
|
239
239
|
* A single variant/example of the component
|
|
240
240
|
*/
|
|
241
|
-
export interface
|
|
241
|
+
export interface FragmentVariant {
|
|
242
242
|
/** Variant name */
|
|
243
243
|
name: string;
|
|
244
244
|
|
|
@@ -279,7 +279,7 @@ export interface SegmentVariant {
|
|
|
279
279
|
* Agent-optimized contract metadata
|
|
280
280
|
* Provides compact, structured data for AI code generation
|
|
281
281
|
*/
|
|
282
|
-
export interface
|
|
282
|
+
export interface FragmentContract {
|
|
283
283
|
/** Short prop descriptions for agents (e.g., "variant: primary|secondary (required)") */
|
|
284
284
|
propsSummary?: string[];
|
|
285
285
|
|
|
@@ -299,11 +299,11 @@ export interface SegmentContract {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
/**
|
|
302
|
-
* Provenance tracking for generated
|
|
302
|
+
* Provenance tracking for generated fragments
|
|
303
303
|
* Helps distinguish human-authored from machine-generated content
|
|
304
304
|
*/
|
|
305
|
-
export interface
|
|
306
|
-
/** Source of this
|
|
305
|
+
export interface FragmentGenerated {
|
|
306
|
+
/** Source of this fragment definition */
|
|
307
307
|
source: "storybook" | "manual" | "ai";
|
|
308
308
|
|
|
309
309
|
/** Original source file (e.g., "Button.stories.tsx") */
|
|
@@ -335,17 +335,17 @@ export interface AIMetadata {
|
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
/**
|
|
338
|
-
* Complete
|
|
338
|
+
* Complete fragment definition
|
|
339
339
|
*/
|
|
340
|
-
export interface
|
|
340
|
+
export interface FragmentDefinition<TProps = unknown> {
|
|
341
341
|
/** The component being documented */
|
|
342
|
-
component:
|
|
342
|
+
component: FragmentComponent<TProps>;
|
|
343
343
|
|
|
344
344
|
/** Component metadata */
|
|
345
|
-
meta:
|
|
345
|
+
meta: FragmentMeta;
|
|
346
346
|
|
|
347
347
|
/** Usage guidelines */
|
|
348
|
-
usage:
|
|
348
|
+
usage: FragmentUsage;
|
|
349
349
|
|
|
350
350
|
/** Props documentation */
|
|
351
351
|
props: Record<string, PropDefinition>;
|
|
@@ -354,16 +354,16 @@ export interface SegmentDefinition<TProps = unknown> {
|
|
|
354
354
|
relations?: ComponentRelation[];
|
|
355
355
|
|
|
356
356
|
/** Component variants/examples */
|
|
357
|
-
variants:
|
|
357
|
+
variants: FragmentVariant[];
|
|
358
358
|
|
|
359
359
|
/** Agent-optimized contract metadata */
|
|
360
|
-
contract?:
|
|
360
|
+
contract?: FragmentContract;
|
|
361
361
|
|
|
362
362
|
/** AI-specific metadata for playground context generation */
|
|
363
363
|
ai?: AIMetadata;
|
|
364
364
|
|
|
365
|
-
/** Provenance tracking (for generated
|
|
366
|
-
_generated?:
|
|
365
|
+
/** Provenance tracking (for generated fragments) */
|
|
366
|
+
_generated?: FragmentGenerated;
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
/**
|
|
@@ -430,7 +430,7 @@ export interface CIConfig {
|
|
|
430
430
|
* Config file structure
|
|
431
431
|
*/
|
|
432
432
|
export interface FragmentsConfig {
|
|
433
|
-
/** Glob patterns for finding
|
|
433
|
+
/** Glob patterns for finding fragment/fragment files */
|
|
434
434
|
include: string[];
|
|
435
435
|
|
|
436
436
|
/** Glob patterns to exclude */
|
|
@@ -467,11 +467,6 @@ export interface FragmentsConfig {
|
|
|
467
467
|
ci?: CIConfig;
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
-
/**
|
|
471
|
-
* @deprecated Use FragmentsConfig instead
|
|
472
|
-
*/
|
|
473
|
-
export type SegmentsConfig = FragmentsConfig;
|
|
474
|
-
|
|
475
470
|
/**
|
|
476
471
|
* Screenshot capture configuration
|
|
477
472
|
*/
|
|
@@ -699,11 +694,11 @@ export interface VerifyResult {
|
|
|
699
694
|
|
|
700
695
|
// Compiled types — re-exported from @fragments-sdk/context
|
|
701
696
|
export type {
|
|
702
|
-
|
|
697
|
+
CompiledFragment,
|
|
703
698
|
CompiledBlock,
|
|
704
699
|
CompiledTokenEntry,
|
|
705
700
|
CompiledTokenData,
|
|
706
|
-
|
|
701
|
+
CompiledFragmentsFile,
|
|
707
702
|
} from '@fragments-sdk/context/types';
|
|
708
703
|
|
|
709
704
|
// Re-export CompiledBlock under deprecated alias
|