@fragments-sdk/cli 0.9.0 → 0.10.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.
Files changed (166) hide show
  1. package/dist/bin.d.ts +1 -0
  2. package/dist/bin.js +502 -84
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-CJEGT3WD.js → chunk-566BNPQZ.js} +21 -6
  5. package/dist/chunk-566BNPQZ.js.map +1 -0
  6. package/dist/{chunk-WI6SLMSO.js → chunk-CAMXG5HJ.js} +5 -5
  7. package/dist/chunk-D2CDBRNU.js +2 -0
  8. package/dist/{chunk-YMPGYEWK.js → chunk-D5PYOXEI.js} +2 -2
  9. package/dist/{chunk-NGIMCIK2.js → chunk-OQO55NKV.js} +405 -34
  10. package/dist/chunk-OQO55NKV.js.map +1 -0
  11. package/dist/{chunk-TOIE7VXF.js → chunk-PW7QTQA6.js} +2 -2
  12. package/dist/{chunk-AWYCDRPG.js → chunk-WXSR2II7.js} +2 -2
  13. package/dist/chunk-WXSR2II7.js.map +1 -0
  14. package/dist/{chunk-2JIKCJX3.js → chunk-ZDA3PLQ6.js} +17 -14
  15. package/dist/chunk-ZDA3PLQ6.js.map +1 -0
  16. package/dist/core/index.d.ts +1 -2092
  17. package/dist/core/index.js +26 -21
  18. package/dist/{discovery-Z4RDDFVR.js → discovery-NEOY4MPN.js} +3 -3
  19. package/dist/generate-BGKTKO6E.js +459 -0
  20. package/dist/generate-BGKTKO6E.js.map +1 -0
  21. package/dist/index.d.ts +3 -5
  22. package/dist/index.js +7 -8
  23. package/dist/index.js.map +1 -1
  24. package/dist/{init-KSAAS7X3.js → init-Q53R5Q2T.js} +66 -76
  25. package/dist/init-Q53R5Q2T.js.map +1 -0
  26. package/dist/mcp-bin.js +5 -7
  27. package/dist/mcp-bin.js.map +1 -1
  28. package/dist/scan-OQU7M4GH.js +14 -0
  29. package/dist/scan-generate-T5QNUG7N.js +691 -0
  30. package/dist/scan-generate-T5QNUG7N.js.map +1 -0
  31. package/dist/{service-A5GIGGGK.js → service-TQYWY65E.js} +4 -5
  32. package/dist/{static-viewer-NSODM5VX.js → static-viewer-NUBFPKWH.js} +4 -5
  33. package/dist/static-viewer-NUBFPKWH.js.map +1 -0
  34. package/dist/{test-RPWZAYSJ.js → test-2CSOSS3B.js} +4 -5
  35. package/dist/{test-RPWZAYSJ.js.map → test-2CSOSS3B.js.map} +1 -1
  36. package/dist/{tokens-NIXSZRX7.js → tokens-DXEGYTOJ.js} +6 -7
  37. package/dist/{tokens-NIXSZRX7.js.map → tokens-DXEGYTOJ.js.map} +1 -1
  38. package/dist/{viewer-SBTJDMP7.js → viewer-DBEPYM3G.js} +245 -23
  39. package/dist/viewer-DBEPYM3G.js.map +1 -0
  40. package/package.json +2 -1
  41. package/src/bin.ts +33 -1
  42. package/src/build.ts +13 -3
  43. package/src/commands/__tests__/scan-generate.test.ts +308 -0
  44. package/src/commands/build.ts +16 -2
  45. package/src/commands/generate.ts +383 -68
  46. package/src/commands/init.ts +81 -56
  47. package/src/commands/perf.ts +1 -1
  48. package/src/commands/scan-generate.ts +1013 -0
  49. package/src/commands/setup.ts +499 -0
  50. package/src/core/auto-props.ts +1 -1
  51. package/src/core/bundle-measurer.ts +2 -2
  52. package/src/core/config.ts +16 -4
  53. package/src/core/discovery.ts +2 -2
  54. package/src/core/generators/context.ts +1 -1
  55. package/src/core/generators/registry.ts +3 -3
  56. package/src/core/generators/typescript-extractor.ts +11 -1
  57. package/src/core/graph-extractor.ts +1 -1
  58. package/src/core/index.ts +3 -190
  59. package/src/core/loader.ts +2 -2
  60. package/src/core/parser.ts +1 -1
  61. package/src/core/previewLoader.ts +1 -1
  62. package/src/index.ts +2 -2
  63. package/src/migrate/converter.ts +9 -1
  64. package/src/migrate/parser.ts +2 -0
  65. package/src/migrate/types.ts +2 -0
  66. package/src/service/snippet-validation.test.ts +1 -1
  67. package/src/service/snippet-validation.ts +2 -2
  68. package/src/setup.ts +69 -24
  69. package/src/viewer/__tests__/viewer-integration.test.ts +4 -10
  70. package/src/viewer/components/AccessibilityPanel.tsx +305 -312
  71. package/src/viewer/components/ActionsPanel.tsx +31 -29
  72. package/src/viewer/components/AllVariantsPreview.tsx +78 -0
  73. package/src/viewer/components/App.tsx +187 -740
  74. package/src/viewer/components/BottomPanel.tsx +228 -132
  75. package/src/viewer/components/CodePanel.tsx +1 -1
  76. package/src/viewer/components/CommandPalette.tsx +7 -10
  77. package/src/viewer/components/ComponentDocView.tsx +164 -0
  78. package/src/viewer/components/ComponentGraph.tsx +111 -142
  79. package/src/viewer/components/ContractPanel.tsx +6 -6
  80. package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
  81. package/src/viewer/components/FigmaEmbed.tsx +20 -18
  82. package/src/viewer/components/FragmentEditor.tsx +92 -115
  83. package/src/viewer/components/HeaderSearch.tsx +24 -0
  84. package/src/viewer/components/HealthDashboard.tsx +16 -2
  85. package/src/viewer/components/Icons.tsx +9 -0
  86. package/src/viewer/components/InteractionsPanel.tsx +101 -117
  87. package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
  88. package/src/viewer/components/LandingPage.tsx +3 -3
  89. package/src/viewer/components/LeftSidebar.tsx +141 -63
  90. package/src/viewer/components/LoadErrorMessage.tsx +102 -0
  91. package/src/viewer/components/MultiViewportPreview.tsx +61 -142
  92. package/src/viewer/components/NoVariantsMessage.tsx +59 -0
  93. package/src/viewer/components/PanelShell.tsx +161 -0
  94. package/src/viewer/components/PerformancePanel.tsx +31 -28
  95. package/src/viewer/components/PreviewArea.tsx +1 -1
  96. package/src/viewer/components/PreviewAside.tsx +168 -0
  97. package/src/viewer/components/PreviewFrameHost.tsx +3 -3
  98. package/src/viewer/components/PropsEditor.tsx +70 -156
  99. package/src/viewer/components/ResizablePanel.tsx +103 -263
  100. package/src/viewer/components/RightSidebar.tsx +3 -9
  101. package/src/viewer/components/SkeletonLoader.tsx +13 -13
  102. package/src/viewer/components/TokenStylePanel.tsx +182 -209
  103. package/src/viewer/components/TopToolbar.tsx +159 -0
  104. package/src/viewer/components/VariantMatrix.tsx +42 -86
  105. package/src/viewer/components/VariantTabs.tsx +3 -3
  106. package/src/viewer/components/ViewerHeader.tsx +69 -0
  107. package/src/viewer/components/WebMCPDevTools.tsx +17 -23
  108. package/src/viewer/components/viewer-utils.ts +16 -0
  109. package/src/viewer/entry.tsx +5 -0
  110. package/src/viewer/hooks/useAppState.ts +27 -4
  111. package/src/viewer/hooks/usePreviewBridge.ts +2 -2
  112. package/src/viewer/preview-frame.html +6 -12
  113. package/src/viewer/server.ts +169 -2
  114. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
  115. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
  116. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
  117. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +6 -18
  118. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
  119. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +5 -16
  120. package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
  121. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
  122. package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
  123. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +114 -0
  124. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
  125. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
  126. package/src/viewer/vendor/shared/src/index.ts +8 -0
  127. package/src/viewer/vendor/shared/src/types.ts +12 -0
  128. package/src/viewer/vite-plugin.ts +109 -4
  129. package/dist/chunk-2JIKCJX3.js.map +0 -1
  130. package/dist/chunk-AWYCDRPG.js.map +0 -1
  131. package/dist/chunk-CJEGT3WD.js.map +0 -1
  132. package/dist/chunk-EKLMXTWU.js +0 -80
  133. package/dist/chunk-EKLMXTWU.js.map +0 -1
  134. package/dist/chunk-GOVI6COW.js +0 -195
  135. package/dist/chunk-GOVI6COW.js.map +0 -1
  136. package/dist/chunk-NGIMCIK2.js.map +0 -1
  137. package/dist/defineFragment-D0UTve-I.d.ts +0 -665
  138. package/dist/generate-35OIMW4Y.js +0 -252
  139. package/dist/generate-35OIMW4Y.js.map +0 -1
  140. package/dist/init-KSAAS7X3.js.map +0 -1
  141. package/dist/scan-65RH3QMM.js +0 -15
  142. package/dist/viewer-SBTJDMP7.js.map +0 -1
  143. package/src/core/__tests__/preview-runtime.test.tsx +0 -111
  144. package/src/core/composition.test.ts +0 -262
  145. package/src/core/composition.ts +0 -318
  146. package/src/core/constants.ts +0 -114
  147. package/src/core/context.ts +0 -2
  148. package/src/core/defineFragment.ts +0 -141
  149. package/src/core/figma.ts +0 -263
  150. package/src/core/fragment-types.ts +0 -214
  151. package/src/core/performance-presets.ts +0 -142
  152. package/src/core/preview-runtime.tsx +0 -144
  153. package/src/core/schema.ts +0 -221
  154. package/src/core/storyAdapter.test.ts +0 -571
  155. package/src/core/storyAdapter.ts +0 -761
  156. package/src/core/storybook-csf.ts +0 -11
  157. package/src/core/token-parser.ts +0 -321
  158. package/src/core/token-types.ts +0 -287
  159. package/src/core/types.ts +0 -762
  160. /package/dist/{chunk-WI6SLMSO.js.map → chunk-CAMXG5HJ.js.map} +0 -0
  161. /package/dist/{discovery-Z4RDDFVR.js.map → chunk-D2CDBRNU.js.map} +0 -0
  162. /package/dist/{chunk-YMPGYEWK.js.map → chunk-D5PYOXEI.js.map} +0 -0
  163. /package/dist/{chunk-TOIE7VXF.js.map → chunk-PW7QTQA6.js.map} +0 -0
  164. /package/dist/{scan-65RH3QMM.js.map → discovery-NEOY4MPN.js.map} +0 -0
  165. /package/dist/{service-A5GIGGGK.js.map → scan-OQU7M4GH.js.map} +0 -0
  166. /package/dist/{static-viewer-NSODM5VX.js.map → service-TQYWY65E.js.map} +0 -0
@@ -10,9 +10,11 @@
10
10
 
11
11
  import { useMemo, useState } from "react";
12
12
  import type { FragmentDefinition, ComponentRelation, RelationshipType } from "../../core/index.js";
13
- import { ChevronRightIcon, EmptyIcon, WandIcon } from "./Icons.js";
13
+ import { ChevronRightIcon, WandIcon } from "./Icons.js";
14
14
  import { detectAllRelationships, mergeRelationships } from "../utils/detectRelationships.js";
15
- import { Stack, Text, Badge, Button, EmptyState, CodeBlock, Grid, Separator } from "@fragments-sdk/ui";
15
+ import { Stack, Text, Badge, Button, CodeBlock, Separator, Box } from "@fragments-sdk/ui";
16
+ import { TreeStructure } from "@phosphor-icons/react";
17
+ import { PanelShell } from "./PanelShell.js";
16
18
 
17
19
  interface ComponentGraphProps {
18
20
  /** Current fragment definition */
@@ -148,46 +150,24 @@ export function ComponentGraph({ fragment, allFragments, onNavigate }: Component
148
150
  }
149
151
  };
150
152
 
151
- // No fragment selected
152
- if (!fragment) {
153
- return (
154
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
155
- <div style={{ padding: '16px', borderBottom: '1px solid var(--border)' }}>
156
- <Text weight="medium">Component Graph</Text>
157
- </div>
158
- <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
159
- <EmptyState>
160
- <EmptyState.Icon>
161
- <EmptyIcon style={{ width: '24px', height: '24px' }} />
162
- </EmptyState.Icon>
163
- <EmptyState.Description>Select a component to view its relationships</EmptyState.Description>
164
- </EmptyState>
165
- </div>
166
- </div>
167
- );
168
- }
169
-
170
153
  const hasRelations = (groupedRelations && Object.values(groupedRelations).some(g => g.length > 0)) || reverseRelations.length > 0;
171
154
  const detectedCount = detectedRelationships.length;
172
155
 
173
- // No relations defined
174
- if (!hasRelations) {
175
- return (
176
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
177
- <div style={{ padding: '16px', borderBottom: '1px solid var(--border)' }}>
178
- <Text weight="medium">Component Graph</Text>
179
- </div>
180
- <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
181
- <EmptyState>
182
- <EmptyState.Icon>
183
- <EmptyIcon style={{ width: '24px', height: '24px' }} />
184
- </EmptyState.Icon>
185
- <EmptyState.Title>No relationships defined</EmptyState.Title>
186
- <EmptyState.Description>
187
- This component doesn't have any defined relationships. Add a <code style={{ padding: '2px 4px', background: 'var(--bg-secondary)', borderRadius: '4px', fontSize: '12px' }}>relations</code> array to your fragment definition to visualize component connections.
188
- </EmptyState.Description>
189
- <div style={{ marginTop: '16px', width: '100%' }}>
190
- <CodeBlock language="typescript">{`relations: [
156
+ // Determine empty config
157
+ const emptyConfig = !fragment ? {
158
+ icon: <TreeStructure size={24} weight="regular" style={{ color: 'var(--text-tertiary)' }} />,
159
+ title: "No component selected",
160
+ description: "Select a component to view its relationships",
161
+ } : !hasRelations ? {
162
+ icon: <TreeStructure size={24} weight="regular" style={{ color: 'var(--text-tertiary)' }} />,
163
+ title: "No relationships defined",
164
+ description: (
165
+ <>
166
+ Add a <Box as="code" padding="xs" background="secondary" rounded="sm" style={{ fontSize: '12px', display: 'inline' }}>relations</Box> array to your fragment definition to visualize component connections.
167
+ </>
168
+ ),
169
+ action: (
170
+ <CodeBlock language="typescript" code={`relations: [
191
171
  {
192
172
  component: "ButtonGroup",
193
173
  relationship: "parent",
@@ -198,116 +178,105 @@ export function ComponentGraph({ fragment, allFragments, onNavigate }: Component
198
178
  relationship: "alternative",
199
179
  note: "Use when only an icon is needed"
200
180
  }
201
- ]`}</CodeBlock>
202
- </div>
203
- </EmptyState>
204
- </div>
205
- </div>
206
- );
207
- }
181
+ ]`} />
182
+ ),
183
+ } : undefined;
184
+
185
+ // Toolbar with badges + auto-detect toggle (only when we have data)
186
+ const toolbar = fragment && hasRelations ? (
187
+ <Stack direction="row" align="center" justify="between" style={{ width: '100%' }}>
188
+ <Stack direction="row" align="center" gap="sm">
189
+ <Badge size="sm">{fragment.meta.name}</Badge>
190
+ <Badge size="sm" variant="default">
191
+ {allRelationships.length + reverseRelations.length} connection{(allRelationships.length + reverseRelations.length) !== 1 ? 's' : ''}
192
+ </Badge>
193
+ </Stack>
194
+ {detectedCount > 0 && (
195
+ <AutoDetectToggle
196
+ showAutoDetected={showAutoDetected}
197
+ onToggle={() => setShowAutoDetected(!showAutoDetected)}
198
+ />
199
+ )}
200
+ </Stack>
201
+ ) : undefined;
208
202
 
209
203
  return (
210
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
211
- {/* Header */}
212
- <div style={{ padding: '16px', borderBottom: '1px solid var(--border)' }}>
213
- <Stack direction="row" align="center" justify="between">
214
- <Text weight="medium">Component Graph</Text>
215
- {detectedCount > 0 && (
216
- <AutoDetectToggle
217
- showAutoDetected={showAutoDetected}
218
- onToggle={() => setShowAutoDetected(!showAutoDetected)}
219
- />
204
+ <PanelShell toolbar={toolbar} empty={emptyConfig}>
205
+ {/* Defined relations */}
206
+ {groupedRelations && (
207
+ <Stack direction="column" gap="md">
208
+ {(Object.entries(groupedRelations) as [RelationshipType, ComponentRelation[]][]).map(
209
+ ([type, relations]) => {
210
+ if (relations.length === 0) return null;
211
+ const config = RELATIONSHIP_CONFIG[type];
212
+
213
+ return (
214
+ <div key={type}>
215
+ <Stack direction="row" align="center" gap="sm" style={{ marginBottom: '8px' }}>
216
+ <Text size="xs" weight="medium" style={{ textTransform: 'uppercase', letterSpacing: '0.05em', color: config.color }}>
217
+ {config.label}
218
+ </Text>
219
+ <Separator style={{ flex: 1 }} />
220
+ </Stack>
221
+ <Stack direction="column" gap="sm">
222
+ {relations.map((relation, index) => (
223
+ <RelationCard
224
+ key={`${relation.component}-${index}`}
225
+ relation={relation}
226
+ config={config}
227
+ exists={componentExists(relation.component)}
228
+ onNavigate={handleNavigate}
229
+ />
230
+ ))}
231
+ </Stack>
232
+ </div>
233
+ );
234
+ }
220
235
  )}
221
236
  </Stack>
222
- <Text size="xs" color="secondary" style={{ marginTop: '4px' }}>
223
- Relationships for <Text as="span" weight="medium" size="xs">{fragment.meta.name}</Text>
224
- {showAutoDetected && detectedCount > 0 && (
225
- <span style={{ marginLeft: '4px' }}>
226
- ({detectedCount} from variants)
227
- </span>
228
- )}
229
- </Text>
230
- </div>
231
-
232
- {/* Graph visualization */}
233
- <div style={{ flex: 1, overflowY: 'auto', padding: '16px' }}>
234
- {/* Defined relations */}
235
- {groupedRelations && (
236
- <Stack direction="column" gap="md">
237
- {(Object.entries(groupedRelations) as [RelationshipType, ComponentRelation[]][]).map(
238
- ([type, relations]) => {
239
- if (relations.length === 0) return null;
240
- const config = RELATIONSHIP_CONFIG[type];
237
+ )}
241
238
 
242
- return (
243
- <div key={type}>
244
- <Stack direction="row" align="center" gap="sm" style={{ marginBottom: '8px' }}>
245
- <Text size="xs" weight="medium" style={{ textTransform: 'uppercase', letterSpacing: '0.05em', color: config.color }}>
246
- {config.label}
247
- </Text>
248
- <div style={{ flex: 1, height: '1px', background: 'var(--border)' }} />
249
- </Stack>
250
- <Stack direction="column" gap="sm">
251
- {relations.map((relation, index) => (
252
- <RelationCard
253
- key={`${relation.component}-${index}`}
254
- relation={relation}
255
- config={config}
256
- exists={componentExists(relation.component)}
257
- onNavigate={handleNavigate}
258
- />
259
- ))}
260
- </Stack>
261
- </div>
262
- );
263
- }
264
- )}
239
+ {/* Reverse relations (inbound references) */}
240
+ {reverseRelations.length > 0 && (
241
+ <Box style={{ marginTop: '24px' }}>
242
+ <Stack direction="row" align="center" gap="sm" style={{ marginBottom: '8px' }}>
243
+ <Text size="xs" weight="medium" color="secondary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
244
+ Referenced By
245
+ </Text>
246
+ <Separator style={{ flex: 1 }} />
265
247
  </Stack>
266
- )}
267
-
268
- {/* Reverse relations (inbound references) */}
269
- {reverseRelations.length > 0 && (
270
- <div style={{ marginTop: '24px' }}>
271
- <Stack direction="row" align="center" gap="sm" style={{ marginBottom: '8px' }}>
272
- <Text size="xs" weight="medium" color="secondary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
273
- Referenced By
274
- </Text>
275
- <div style={{ flex: 1, height: '1px', background: 'var(--border)' }} />
276
- </Stack>
277
- <Stack direction="column" gap="sm">
278
- {reverseRelations.map(({ relation }, index) => {
279
- const config = RELATIONSHIP_CONFIG[relation.relationship];
280
- return (
281
- <RelationCard
282
- key={`reverse-${relation.component}-${index}`}
283
- relation={relation}
284
- config={config}
285
- exists={componentExists(relation.component)}
286
- onNavigate={handleNavigate}
287
- isReverse
288
- />
289
- );
290
- })}
291
- </Stack>
292
- </div>
293
- )}
248
+ <Stack direction="column" gap="sm">
249
+ {reverseRelations.map(({ relation }, index) => {
250
+ const config = RELATIONSHIP_CONFIG[relation.relationship];
251
+ return (
252
+ <RelationCard
253
+ key={`reverse-${relation.component}-${index}`}
254
+ relation={relation}
255
+ config={config}
256
+ exists={componentExists(relation.component)}
257
+ onNavigate={handleNavigate}
258
+ isReverse
259
+ />
260
+ );
261
+ })}
262
+ </Stack>
263
+ </Box>
264
+ )}
294
265
 
295
- {/* Legend */}
296
- <div style={{ marginTop: '32px', paddingTop: '16px', borderTop: '1px solid var(--border)' }}>
297
- <Text size="xs" weight="medium" color="secondary" style={{ marginBottom: '8px' }}>Legend</Text>
298
- <Grid columns={2} gap="sm">
299
- {(Object.entries(RELATIONSHIP_CONFIG) as [RelationshipType, typeof RELATIONSHIP_CONFIG[RelationshipType]][]).map(
300
- ([type, config]) => (
301
- <Stack key={type} direction="row" align="center" gap="sm">
302
- <span style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: config.bgColor, border: `1px solid ${config.color}` }} />
303
- <Text size="xs" color="secondary">{config.label}</Text>
304
- </Stack>
305
- )
306
- )}
307
- </Grid>
308
- </div>
309
- </div>
310
- </div>
266
+ {/* Legend */}
267
+ <Box borderTop style={{ marginTop: '24px', paddingTop: '12px' }}>
268
+ <Stack direction="row" gap="md" style={{ flexWrap: 'wrap' }}>
269
+ {(Object.entries(RELATIONSHIP_CONFIG) as [RelationshipType, typeof RELATIONSHIP_CONFIG[RelationshipType]][]).map(
270
+ ([type, config]) => (
271
+ <Stack key={type} direction="row" align="center" gap="xs">
272
+ <span style={{ width: '6px', height: '6px', borderRadius: '50%', backgroundColor: config.color, flexShrink: 0 }} />
273
+ <Text size="xs" color="tertiary">{config.label}</Text>
274
+ </Stack>
275
+ )
276
+ )}
277
+ </Stack>
278
+ </Box>
279
+ </PanelShell>
311
280
  );
312
281
  }
313
282
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { memo } from 'react';
7
7
  import type { FragmentContract } from '../../core/index.js';
8
- import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge } from '@fragments-sdk/ui';
8
+ import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge, Box } from '@fragments-sdk/ui';
9
9
 
10
10
  interface ContractPanelProps {
11
11
  contract?: FragmentContract;
@@ -37,7 +37,7 @@ export const ContractPanel = memo(function ContractPanel({
37
37
  </EmptyState.Icon>
38
38
  <EmptyState.Title>No contract metadata</EmptyState.Title>
39
39
  <EmptyState.Description>
40
- Add a <code style={{ background: 'var(--bg-hover)', padding: '2px 4px', borderRadius: '4px' }}>contract</code> field to the fragment definition to enable AI agent features.
40
+ Add a <Box as="code" padding="xs" background="secondary" rounded="sm">contract</Box> field to the fragment definition to enable AI agent features.
41
41
  </EmptyState.Description>
42
42
  <EmptyState.Actions>
43
43
  <CodeBlock
@@ -55,7 +55,7 @@ export const ContractPanel = memo(function ContractPanel({
55
55
  }
56
56
 
57
57
  return (
58
- <div style={{ padding: '16px', fontSize: '14px' }}>
58
+ <Box padding="md" style={{ fontSize: '14px' }}>
59
59
  <Stack gap="lg">
60
60
  {/* Props Summary */}
61
61
  {contract.propsSummary && contract.propsSummary.length > 0 && (
@@ -110,12 +110,11 @@ export const ContractPanel = memo(function ContractPanel({
110
110
  <Section title="Accessibility Rules">
111
111
  <Stack gap="sm">
112
112
  {contract.a11yRules.map((rule, i) => (
113
+ <Box key={i} padding="sm" background="secondary" rounded="sm">
113
114
  <Stack
114
- key={i}
115
115
  direction="row"
116
116
  align="center"
117
117
  gap="sm"
118
- style={{ padding: '8px', background: 'var(--bg-hover)', borderRadius: '4px' }}
119
118
  >
120
119
  <span style={{ color: '#22c55e' }}>
121
120
  <svg style={{ width: '16px', height: '16px' }} fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -124,6 +123,7 @@ export const ContractPanel = memo(function ContractPanel({
124
123
  </span>
125
124
  <Text font="mono" size="xs" color="secondary">{rule}</Text>
126
125
  </Stack>
126
+ </Box>
127
127
  ))}
128
128
  </Stack>
129
129
  </Section>
@@ -160,7 +160,7 @@ export const ContractPanel = memo(function ContractPanel({
160
160
  </Alert>
161
161
  )}
162
162
  </Stack>
163
- </div>
163
+ </Box>
164
164
  );
165
165
  });
166
166
 
@@ -0,0 +1,54 @@
1
+ import { Stack, Text, Alert } from "@fragments-sdk/ui";
2
+
3
+ interface EmptyVariantMessageProps {
4
+ reason: string;
5
+ variantName: string;
6
+ hint?: string;
7
+ }
8
+
9
+ export function EmptyVariantMessage({ reason, variantName, hint }: EmptyVariantMessageProps) {
10
+ return (
11
+ <Alert variant="warning">
12
+ <Alert.Body>
13
+ <Alert.Title>Variant "{variantName}" rendered empty</Alert.Title>
14
+ <Alert.Content>
15
+ <Stack direction="column" gap="sm">
16
+ <Text size="xs" color="secondary">
17
+ {reason}
18
+ </Text>
19
+ {hint && (
20
+ <Text size="xs" color="tertiary">
21
+ <Text as="span" size="xs" weight="semibold">
22
+ Tip:
23
+ </Text>{" "}
24
+ {hint}
25
+ </Text>
26
+ )}
27
+ <div>
28
+ <Text size="xs" color="tertiary" weight="semibold">
29
+ Common causes:
30
+ </Text>
31
+ <ul style={{ marginTop: "4px", marginLeft: "16px", listStyleType: "disc" }}>
32
+ <li>
33
+ <Text size="xs" color="secondary">
34
+ Component requires props that weren't provided
35
+ </Text>
36
+ </li>
37
+ <li>
38
+ <Text size="xs" color="secondary">
39
+ Component renders conditionally and conditions aren't met
40
+ </Text>
41
+ </li>
42
+ <li>
43
+ <Text size="xs" color="secondary">
44
+ Story args reference variables that don't exist in this context
45
+ </Text>
46
+ </li>
47
+ </ul>
48
+ </div>
49
+ </Stack>
50
+ </Alert.Content>
51
+ </Alert.Body>
52
+ </Alert>
53
+ );
54
+ }
@@ -1,5 +1,6 @@
1
1
  import { useRef, useEffect, useState, useMemo, useCallback } from "react";
2
2
  import { FigmaIcon } from "./Icons.js";
3
+ import { Stack, Text, Button } from '@fragments-sdk/ui';
3
4
 
4
5
  interface FigmaEmbedProps {
5
6
  /** Current Figma URL to display */
@@ -124,18 +125,19 @@ export function FigmaEmbed({ figmaUrl, allFigmaUrls, zoom = 100, className, styl
124
125
  // If we can't parse the URL, show error
125
126
  if (!currentParsed) {
126
127
  return (
127
- <div className={className} style={{ ...style, display: "flex", alignItems: "center", justifyContent: "center" }}>
128
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px', color: 'var(--text-tertiary)', padding: '16px', textAlign: 'center' }}>
128
+ <Stack className={className} align="center" justify="center" style={style}>
129
+ <Stack direction="column" align="center" gap="sm" style={{ color: 'var(--text-tertiary)', padding: '16px', textAlign: 'center' }}>
129
130
  <FigmaIcon style={{ width: '24px', height: '24px' }} />
130
- <span style={{ fontSize: '12px' }}>Unable to embed Figma design</span>
131
- <button
131
+ <Text size="xs">Unable to embed Figma design</Text>
132
+ <Button
133
+ variant="ghost"
134
+ size="sm"
132
135
  onClick={() => window.open(figmaUrl, "_blank", "noopener,noreferrer")}
133
- style={{ fontSize: '12px', color: 'var(--color-accent)', background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'underline' }}
134
136
  >
135
137
  Open in Figma
136
- </button>
137
- </div>
138
- </div>
138
+ </Button>
139
+ </Stack>
140
+ </Stack>
139
141
  );
140
142
  }
141
143
 
@@ -143,22 +145,22 @@ export function FigmaEmbed({ figmaUrl, allFigmaUrls, zoom = 100, className, styl
143
145
  <div className={className} style={{ ...style, position: "relative", overflow: "hidden" }}>
144
146
  {/* Loading overlay - shows while current iframe is loading */}
145
147
  {!isCurrentLoaded && (
146
- <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'var(--bg-secondary)', zIndex: 20 }}>
147
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px' }}>
148
+ <Stack align="center" justify="center" style={{ position: 'absolute', inset: 0, backgroundColor: 'var(--bg-secondary)', zIndex: 20 }}>
149
+ <Stack direction="column" align="center" gap="sm">
148
150
  <FigmaIcon style={{ width: '20px', height: '20px', color: 'var(--text-tertiary)' }} />
149
- <span style={{ fontSize: '12px', color: 'var(--text-tertiary)' }}>Loading Figma...</span>
150
- </div>
151
- </div>
151
+ <Text size="xs" color="tertiary">Loading Figma...</Text>
152
+ </Stack>
153
+ </Stack>
152
154
  )}
153
155
 
154
156
  {/* Error overlay */}
155
157
  {error && (
156
- <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'var(--bg-secondary)', zIndex: 20 }}>
157
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px', color: 'var(--text-tertiary)' }}>
158
+ <Stack align="center" justify="center" style={{ position: 'absolute', inset: 0, backgroundColor: 'var(--bg-secondary)', zIndex: 20 }}>
159
+ <Stack direction="column" align="center" gap="sm" style={{ color: 'var(--text-tertiary)' }}>
158
160
  <FigmaIcon style={{ width: '24px', height: '24px' }} />
159
- <span style={{ fontSize: '12px' }}>{error}</span>
160
- </div>
161
- </div>
161
+ <Text size="xs">{error}</Text>
162
+ </Stack>
163
+ </Stack>
162
164
  )}
163
165
 
164
166
  {/*