@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.
Files changed (124) hide show
  1. package/dist/bin.js +996 -79
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
  4. package/dist/chunk-6JBGU74P.js.map +1 -0
  5. package/dist/chunk-7OPWMLOE.js +1625 -0
  6. package/dist/chunk-7OPWMLOE.js.map +1 -0
  7. package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
  8. package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
  9. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
  12. package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
  13. package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
  14. package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
  15. package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +15 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
  20. package/dist/mcp-bin.js +8 -220
  21. package/dist/mcp-bin.js.map +1 -1
  22. package/dist/scan-WY23TJCP.js +12 -0
  23. package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
  24. package/dist/static-viewer-GBR7YNF3.js +12 -0
  25. package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
  26. package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
  27. package/dist/viewer-SUFOISZM.js +1822 -0
  28. package/dist/viewer-SUFOISZM.js.map +1 -0
  29. package/package.json +6 -5
  30. package/src/bin.ts +31 -0
  31. package/src/build.ts +147 -13
  32. package/src/cli-commands.ts +18 -0
  33. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  34. package/src/commands/a11y-report.ts +625 -0
  35. package/src/commands/a11y.ts +168 -14
  36. package/src/commands/build.ts +16 -0
  37. package/src/commands/graph.ts +274 -0
  38. package/src/core/auto-props.ts +464 -0
  39. package/src/core/composition.ts +64 -1
  40. package/src/core/graph-extractor.test.ts +542 -0
  41. package/src/core/graph-extractor.ts +601 -0
  42. package/src/core/importAnalyzer.ts +5 -0
  43. package/src/core/schema.ts +2 -0
  44. package/src/core/types.ts +3 -1
  45. package/src/index.ts +4 -0
  46. package/src/mcp/server.ts +13 -220
  47. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  48. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  49. package/src/theme/contrast.test.ts +331 -0
  50. package/src/theme/contrast.ts +246 -0
  51. package/src/theme/generator.ts +213 -1
  52. package/src/theme/index.ts +16 -0
  53. package/src/theme/types.ts +51 -0
  54. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  55. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  56. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  57. package/src/viewer/components/ActionCapture.tsx +1 -1
  58. package/src/viewer/components/ActionsPanel.tsx +142 -183
  59. package/src/viewer/components/App.tsx +276 -183
  60. package/src/viewer/components/BottomPanel.tsx +40 -80
  61. package/src/viewer/components/CodePanel.tsx +9 -87
  62. package/src/viewer/components/CommandPalette.tsx +117 -74
  63. package/src/viewer/components/ComponentGraph.tsx +143 -126
  64. package/src/viewer/components/ComponentHeader.tsx +46 -43
  65. package/src/viewer/components/ContractPanel.tsx +124 -117
  66. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  67. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  68. package/src/viewer/components/FragmentEditor.tsx +126 -63
  69. package/src/viewer/components/HealthDashboard.tsx +146 -171
  70. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  71. package/src/viewer/components/Icons.tsx +151 -98
  72. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  73. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  74. package/src/viewer/components/IsolatedRender.tsx +12 -6
  75. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  76. package/src/viewer/components/LandingPage.tsx +285 -305
  77. package/src/viewer/components/Layout.tsx +12 -10
  78. package/src/viewer/components/LeftSidebar.tsx +103 -155
  79. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  80. package/src/viewer/components/PreviewArea.tsx +113 -44
  81. package/src/viewer/components/PreviewFrameHost.tsx +36 -6
  82. package/src/viewer/components/PreviewPane.tsx +2 -3
  83. package/src/viewer/components/PreviewToolbar.tsx +109 -105
  84. package/src/viewer/components/PropsEditor.tsx +154 -74
  85. package/src/viewer/components/PropsTable.tsx +95 -82
  86. package/src/viewer/components/RelationsSection.tsx +71 -40
  87. package/src/viewer/components/ResizablePanel.tsx +158 -55
  88. package/src/viewer/components/RightSidebar.tsx +46 -56
  89. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  90. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  91. package/src/viewer/components/StoryRenderer.tsx +4 -11
  92. package/src/viewer/components/Toast.tsx +3 -67
  93. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  94. package/src/viewer/components/UsageSection.tsx +26 -26
  95. package/src/viewer/components/VariantMatrix.tsx +140 -47
  96. package/src/viewer/components/VariantTabs.tsx +24 -68
  97. package/src/viewer/components/ViewportSelector.tsx +121 -114
  98. package/src/viewer/constants/ui.ts +23 -22
  99. package/src/viewer/entry.tsx +8 -3
  100. package/src/viewer/index.ts +3 -6
  101. package/src/viewer/preview-frame.html +43 -18
  102. package/src/viewer/server.ts +7 -16
  103. package/src/viewer/styles/globals.css +46 -85
  104. package/src/viewer/utils/a11y-fixes.ts +53 -30
  105. package/dist/chunk-ICAIQ57V.js.map +0 -1
  106. package/dist/chunk-U4GQ2JTD.js +0 -832
  107. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  108. package/dist/scan-ESEXV7LF.js +0 -12
  109. package/dist/static-viewer-O37MJ5B6.js +0 -12
  110. package/dist/viewer-YDGFDTK5.js +0 -11104
  111. package/dist/viewer-YDGFDTK5.js.map +0 -1
  112. package/src/viewer/postcss.config.js +0 -6
  113. package/src/viewer/tailwind.config.js +0 -37
  114. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  115. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  116. /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
  117. /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
  118. /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
  119. /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
  120. /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
  121. /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
  122. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
  123. /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
  124. /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
@@ -9,10 +9,10 @@
9
9
  */
10
10
 
11
11
  import { useMemo, useState } from "react";
12
- import clsx from "clsx";
13
12
  import type { SegmentDefinition, ComponentRelation, RelationshipType } from "../../core/index.js";
14
13
  import { ChevronRightIcon, EmptyIcon, WandIcon } from "./Icons.js";
15
14
  import { detectAllRelationships, mergeRelationships } from "../utils/detectRelationships.js";
15
+ import { Stack, Text, Badge, Button, EmptyState, CodeBlock, Grid, Separator } from "@fragments/ui";
16
16
 
17
17
  interface ComponentGraphProps {
18
18
  /** Current segment definition */
@@ -26,34 +26,46 @@ interface ComponentGraphProps {
26
26
  const RELATIONSHIP_CONFIG: Record<RelationshipType, { label: string; color: string; bgColor: string; icon: string }> = {
27
27
  parent: {
28
28
  label: "Parent",
29
- color: "text-purple-600 dark:text-purple-400",
30
- bgColor: "bg-purple-100 dark:bg-purple-900/30",
29
+ color: "#9333ea",
30
+ bgColor: "rgba(147, 51, 234, 0.1)",
31
31
  icon: "^",
32
32
  },
33
33
  child: {
34
34
  label: "Child",
35
- color: "text-blue-600 dark:text-blue-400",
36
- bgColor: "bg-blue-100 dark:bg-blue-900/30",
35
+ color: "#2563eb",
36
+ bgColor: "rgba(37, 99, 235, 0.1)",
37
37
  icon: "v",
38
38
  },
39
39
  sibling: {
40
40
  label: "Sibling",
41
- color: "text-green-600 dark:text-green-400",
42
- bgColor: "bg-green-100 dark:bg-green-900/30",
41
+ color: "#16a34a",
42
+ bgColor: "rgba(22, 163, 74, 0.1)",
43
43
  icon: "<->",
44
44
  },
45
45
  alternative: {
46
46
  label: "Alternative",
47
- color: "text-amber-600 dark:text-amber-400",
48
- bgColor: "bg-amber-100 dark:bg-amber-900/30",
47
+ color: "#d97706",
48
+ bgColor: "rgba(217, 119, 6, 0.1)",
49
49
  icon: "~",
50
50
  },
51
51
  composition: {
52
52
  label: "Composition",
53
- color: "text-cyan-600 dark:text-cyan-400",
54
- bgColor: "bg-cyan-100 dark:bg-cyan-900/30",
53
+ color: "#0891b2",
54
+ bgColor: "rgba(8, 145, 178, 0.1)",
55
55
  icon: "+",
56
56
  },
57
+ complementary: {
58
+ label: "Complementary",
59
+ color: "#e11d48",
60
+ bgColor: "rgba(225, 29, 72, 0.1)",
61
+ icon: "&",
62
+ },
63
+ "used-by": {
64
+ label: "Used By",
65
+ color: "#4f46e5",
66
+ bgColor: "rgba(79, 70, 229, 0.1)",
67
+ icon: "<-",
68
+ },
57
69
  };
58
70
 
59
71
  export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGraphProps) {
@@ -85,6 +97,8 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
85
97
  sibling: [],
86
98
  alternative: [],
87
99
  composition: [],
100
+ complementary: [],
101
+ "used-by": [],
88
102
  };
89
103
 
90
104
  for (const relation of allRelationships) {
@@ -137,21 +151,17 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
137
151
  // No segment selected
138
152
  if (!segment) {
139
153
  return (
140
- <div className="h-full flex flex-col">
141
- <div className="p-4 border-b border-gray-200 dark:border-gray-700">
142
- <h3 className="font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2">
143
- Component Graph
144
- </h3>
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>
145
157
  </div>
146
- <div className="flex-1 flex items-center justify-center p-8 text-center">
147
- <div className="max-w-md">
148
- <div className="w-12 h-12 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center mx-auto mb-4">
149
- <EmptyIcon className="w-6 h-6 text-gray-400" />
150
- </div>
151
- <p className="text-sm text-gray-500 dark:text-gray-400">
152
- Select a component to view its relationships
153
- </p>
154
- </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>
155
165
  </div>
156
166
  </div>
157
167
  );
@@ -163,26 +173,21 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
163
173
  // No relations defined
164
174
  if (!hasRelations) {
165
175
  return (
166
- <div className="h-full flex flex-col">
167
- <div className="p-4 border-b border-gray-200 dark:border-gray-700">
168
- <h3 className="font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2">
169
- Component Graph
170
- </h3>
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>
171
179
  </div>
172
- <div className="flex-1 flex items-center justify-center p-8 text-center">
173
- <div className="max-w-md">
174
- <div className="w-12 h-12 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center mx-auto mb-4">
175
- <EmptyIcon className="w-6 h-6 text-gray-400" />
176
- </div>
177
- <h4 className="font-medium text-gray-700 dark:text-gray-300 mb-2">
178
- No relationships defined
179
- </h4>
180
- <p className="text-sm text-gray-500 dark:text-gray-400">
181
- This component doesn't have any defined relationships. Add a <code className="px-1 py-0.5 bg-gray-100 dark:bg-gray-800 rounded text-xs">relations</code> array to your segment definition to visualize component connections.
182
- </p>
183
- <div className="mt-4 p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg text-left">
184
- <pre className="text-xs text-gray-600 dark:text-gray-400 overflow-x-auto">
185
- {`relations: [
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 segment definition to visualize component connections.
188
+ </EmptyState.Description>
189
+ <div style={{ marginTop: '16px', width: '100%' }}>
190
+ <CodeBlock language="typescript">{`relations: [
186
191
  {
187
192
  component: "ButtonGroup",
188
193
  relationship: "parent",
@@ -193,54 +198,42 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
193
198
  relationship: "alternative",
194
199
  note: "Use when only an icon is needed"
195
200
  }
196
- ]`}
197
- </pre>
201
+ ]`}</CodeBlock>
198
202
  </div>
199
- </div>
203
+ </EmptyState>
200
204
  </div>
201
205
  </div>
202
206
  );
203
207
  }
204
208
 
205
209
  return (
206
- <div className="h-full flex flex-col">
210
+ <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
207
211
  {/* Header */}
208
- <div className="p-4 border-b border-gray-200 dark:border-gray-700">
209
- <div className="flex items-center justify-between">
210
- <h3 className="font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2">
211
- Component Graph
212
- </h3>
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>
213
215
  {detectedCount > 0 && (
214
- <button
215
- onClick={() => setShowAutoDetected(!showAutoDetected)}
216
- className={clsx(
217
- "flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium transition-colors",
218
- showAutoDetected
219
- ? "bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300"
220
- : "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
221
- )}
222
- title={showAutoDetected ? "Hide composition relationships detected from variants" : "Show composition relationships detected from variants"}
223
- >
224
- <WandIcon className="w-3.5 h-3.5" />
225
- {showAutoDetected ? "Composition on" : "Composition off"}
226
- </button>
216
+ <AutoDetectToggle
217
+ showAutoDetected={showAutoDetected}
218
+ onToggle={() => setShowAutoDetected(!showAutoDetected)}
219
+ />
227
220
  )}
228
- </div>
229
- <p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
230
- Relationships for <span className="font-medium">{segment.meta.name}</span>
221
+ </Stack>
222
+ <Text size="xs" color="secondary" style={{ marginTop: '4px' }}>
223
+ Relationships for <Text as="span" weight="medium" size="xs">{segment.meta.name}</Text>
231
224
  {showAutoDetected && detectedCount > 0 && (
232
- <span className="ml-1">
225
+ <span style={{ marginLeft: '4px' }}>
233
226
  ({detectedCount} from variants)
234
227
  </span>
235
228
  )}
236
- </p>
229
+ </Text>
237
230
  </div>
238
231
 
239
232
  {/* Graph visualization */}
240
- <div className="flex-1 overflow-y-auto p-4">
233
+ <div style={{ flex: 1, overflowY: 'auto', padding: '16px' }}>
241
234
  {/* Defined relations */}
242
235
  {groupedRelations && (
243
- <div className="space-y-4">
236
+ <Stack direction="column" gap="md">
244
237
  {(Object.entries(groupedRelations) as [RelationshipType, ComponentRelation[]][]).map(
245
238
  ([type, relations]) => {
246
239
  if (relations.length === 0) return null;
@@ -248,13 +241,13 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
248
241
 
249
242
  return (
250
243
  <div key={type}>
251
- <div className="flex items-center gap-2 mb-2">
252
- <span className={clsx("text-xs font-medium uppercase tracking-wide", config.color)}>
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 }}>
253
246
  {config.label}
254
- </span>
255
- <div className="flex-1 h-px bg-gray-200 dark:bg-gray-700" />
256
- </div>
257
- <div className="space-y-2">
247
+ </Text>
248
+ <div style={{ flex: 1, height: '1px', background: 'var(--border)' }} />
249
+ </Stack>
250
+ <Stack direction="column" gap="sm">
258
251
  {relations.map((relation, index) => (
259
252
  <RelationCard
260
253
  key={`${relation.component}-${index}`}
@@ -264,24 +257,24 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
264
257
  onNavigate={handleNavigate}
265
258
  />
266
259
  ))}
267
- </div>
260
+ </Stack>
268
261
  </div>
269
262
  );
270
263
  }
271
264
  )}
272
- </div>
265
+ </Stack>
273
266
  )}
274
267
 
275
268
  {/* Reverse relations (inbound references) */}
276
269
  {reverseRelations.length > 0 && (
277
- <div className="mt-6">
278
- <div className="flex items-center gap-2 mb-2">
279
- <span className="text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
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' }}>
280
273
  Referenced By
281
- </span>
282
- <div className="flex-1 h-px bg-gray-200 dark:bg-gray-700" />
283
- </div>
284
- <div className="space-y-2">
274
+ </Text>
275
+ <div style={{ flex: 1, height: '1px', background: 'var(--border)' }} />
276
+ </Stack>
277
+ <Stack direction="column" gap="sm">
285
278
  {reverseRelations.map(({ relation }, index) => {
286
279
  const config = RELATIONSHIP_CONFIG[relation.relationship];
287
280
  return (
@@ -295,29 +288,47 @@ export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGr
295
288
  />
296
289
  );
297
290
  })}
298
- </div>
291
+ </Stack>
299
292
  </div>
300
293
  )}
301
294
 
302
295
  {/* Legend */}
303
- <div className="mt-8 pt-4 border-t border-gray-200 dark:border-gray-700">
304
- <div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">Legend</div>
305
- <div className="grid grid-cols-2 gap-2">
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">
306
299
  {(Object.entries(RELATIONSHIP_CONFIG) as [RelationshipType, typeof RELATIONSHIP_CONFIG[RelationshipType]][]).map(
307
300
  ([type, config]) => (
308
- <div key={type} className="flex items-center gap-2">
309
- <span className={clsx("w-2 h-2 rounded-full", config.bgColor, config.color)} />
310
- <span className="text-xs text-gray-600 dark:text-gray-400">{config.label}</span>
311
- </div>
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>
312
305
  )
313
306
  )}
314
- </div>
307
+ </Grid>
315
308
  </div>
316
309
  </div>
317
310
  </div>
318
311
  );
319
312
  }
320
313
 
314
+ function AutoDetectToggle({ showAutoDetected, onToggle }: { showAutoDetected: boolean; onToggle: () => void }) {
315
+ return (
316
+ <Button
317
+ onClick={onToggle}
318
+ variant="ghost"
319
+ size="sm"
320
+ style={{
321
+ background: showAutoDetected ? 'rgba(147, 51, 234, 0.1)' : 'transparent',
322
+ color: showAutoDetected ? '#9333ea' : undefined,
323
+ }}
324
+ title={showAutoDetected ? "Hide composition relationships detected from variants" : "Show composition relationships detected from variants"}
325
+ >
326
+ <WandIcon style={{ width: '14px', height: '14px' }} />
327
+ {showAutoDetected ? "Composition on" : "Composition off"}
328
+ </Button>
329
+ );
330
+ }
331
+
321
332
  interface RelationCardProps {
322
333
  relation: ComponentRelation & { isDetected?: boolean; confidence?: number };
323
334
  config: typeof RELATIONSHIP_CONFIG[RelationshipType];
@@ -327,48 +338,56 @@ interface RelationCardProps {
327
338
  }
328
339
 
329
340
  function RelationCard({ relation, config, exists, onNavigate, isReverse }: RelationCardProps) {
341
+ const [hovered, setHovered] = useState(false);
342
+
330
343
  return (
331
344
  <button
332
345
  onClick={() => exists && onNavigate(relation.component)}
333
346
  disabled={!exists}
334
- className={clsx(
335
- "w-full text-left p-3 rounded-lg border transition-[transform,box-shadow] duration-150",
336
- config.bgColor,
337
- exists
338
- ? "cursor-pointer hover:shadow-md hover:scale-[1.01] border-transparent"
339
- : "cursor-not-allowed opacity-60 border-dashed border-gray-300 dark:border-gray-600"
340
- )}
347
+ onMouseEnter={() => setHovered(true)}
348
+ onMouseLeave={() => setHovered(false)}
349
+ style={{
350
+ width: '100%',
351
+ textAlign: 'left',
352
+ padding: '12px',
353
+ borderRadius: '8px',
354
+ border: exists ? '1px solid transparent' : '1px dashed var(--border)',
355
+ transition: 'transform 150ms, box-shadow 150ms',
356
+ background: config.bgColor,
357
+ cursor: exists ? 'pointer' : 'not-allowed',
358
+ opacity: exists ? 1 : 0.6,
359
+ transform: exists && hovered ? 'scale(1.01)' : 'scale(1)',
360
+ boxShadow: exists && hovered ? '0 4px 6px -1px rgba(0,0,0,0.1)' : 'none',
361
+ }}
341
362
  >
342
- <div className="flex items-center justify-between">
343
- <div className="flex items-center gap-2 flex-wrap">
344
- <span className={clsx("font-medium text-sm", config.color)}>
363
+ <Stack direction="row" align="center" justify="between">
364
+ <Stack direction="row" align="center" gap="sm" style={{ flexWrap: 'wrap' }}>
365
+ <Text weight="medium" size="sm" style={{ color: config.color }}>
345
366
  {relation.component}
346
- </span>
367
+ </Text>
347
368
  {relation.isDetected && (
348
- <span className="flex items-center gap-0.5 text-[10px] uppercase tracking-wide text-purple-600 dark:text-purple-400 bg-purple-100 dark:bg-purple-900/30 px-1.5 py-0.5 rounded">
349
- <WandIcon className="w-2.5 h-2.5" />
369
+ <Badge size="sm" style={{ background: 'rgba(147, 51, 234, 0.1)', color: '#9333ea' }}>
370
+ <WandIcon style={{ width: '10px', height: '10px' }} />
350
371
  auto
351
- </span>
372
+ </Badge>
352
373
  )}
353
374
  {isReverse && (
354
- <span className="text-[10px] uppercase tracking-wide text-gray-500 dark:text-gray-400 bg-gray-200 dark:bg-gray-700 px-1.5 py-0.5 rounded">
355
- inbound
356
- </span>
375
+ <Badge size="sm" variant="default">inbound</Badge>
357
376
  )}
358
377
  {!exists && (
359
- <span className="text-[10px] uppercase tracking-wide text-gray-500 dark:text-gray-400">
378
+ <Text size="xs" color="secondary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
360
379
  (not found)
361
- </span>
380
+ </Text>
362
381
  )}
363
- </div>
382
+ </Stack>
364
383
  {exists && (
365
- <ChevronRightIcon className="w-4 h-4 text-gray-400" />
384
+ <ChevronRightIcon style={{ width: '16px', height: '16px', color: 'var(--text-tertiary)' }} />
366
385
  )}
367
- </div>
386
+ </Stack>
368
387
  {relation.note && (
369
- <p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
388
+ <Text size="xs" color="secondary" style={{ marginTop: '4px' }}>
370
389
  {relation.note}
371
- </p>
390
+ </Text>
372
391
  )}
373
392
  </button>
374
393
  );
@@ -384,11 +403,9 @@ function flipRelationship(type: RelationshipType): RelationshipType {
384
403
  return "child";
385
404
  case "child":
386
405
  return "parent";
387
- case "sibling":
388
- return "sibling";
389
- case "alternative":
390
- return "alternative";
391
- case "composition":
406
+ case "used-by":
392
407
  return "composition";
408
+ default:
409
+ return type;
393
410
  }
394
411
  }
@@ -1,85 +1,88 @@
1
1
  import type { SegmentMeta } from '../../core/index.js';
2
- import clsx from 'clsx';
3
- import { getStatusConfig } from '../constants/ui.js';
2
+ import { Badge, Alert, Text, Stack, Separator } from '@fragments/ui';
4
3
  import { WarningIcon, BeakerIcon } from './Icons.js';
5
4
 
6
5
  interface ComponentHeaderProps {
7
6
  meta: SegmentMeta;
8
7
  }
9
8
 
9
+ // Map status to Badge variant
10
+ function getStatusBadgeVariant(status: string): 'success' | 'warning' | 'error' | 'default' {
11
+ switch (status) {
12
+ case 'stable': return 'success';
13
+ case 'experimental': return 'warning';
14
+ case 'deprecated': return 'error';
15
+ case 'beta': return 'warning';
16
+ default: return 'default';
17
+ }
18
+ }
19
+
10
20
  export function ComponentHeader({ meta }: ComponentHeaderProps) {
11
21
  return (
12
- <section id="overview" className="scroll-mt-24 mb-6">
22
+ <section id="overview" style={{ scrollMarginTop: '96px', marginBottom: '24px' }}>
13
23
  {/* Deprecated Warning Banner */}
14
24
  {meta.status === 'deprecated' && (
15
- <div className="mb-5 p-4 rounded-xl border border-red-500/30 bg-red-500/10">
16
- <div className="flex items-start gap-3">
17
- <WarningIcon className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
18
- <div>
19
- <h3 className="text-sm font-semibold text-red-600 dark:text-red-400 mb-1">
20
- Deprecated Component
21
- </h3>
22
- <p className="text-sm text-red-600/80 dark:text-red-400/80">
25
+ <div style={{ marginBottom: '20px' }}>
26
+ <Alert severity="error">
27
+ <Alert.Icon><WarningIcon /></Alert.Icon>
28
+ <Alert.Body>
29
+ <Alert.Title>Deprecated Component</Alert.Title>
30
+ <Alert.Content>
23
31
  This component is deprecated and will be removed in a future version.
24
32
  {meta.tags?.includes('migration') && ' Check the usage guidelines for migration instructions.'}
25
- </p>
26
- </div>
27
- </div>
33
+ </Alert.Content>
34
+ </Alert.Body>
35
+ </Alert>
28
36
  </div>
29
37
  )}
30
38
 
31
39
  {/* Experimental Warning Banner */}
32
40
  {meta.status === 'experimental' && (
33
- <div className="mb-5 p-4 rounded-xl border border-purple-500/30 bg-purple-500/10">
34
- <div className="flex items-start gap-3">
35
- <BeakerIcon className="w-5 h-5 text-purple-500 flex-shrink-0 mt-0.5" />
36
- <div>
37
- <h3 className="text-sm font-semibold text-purple-600 dark:text-purple-400 mb-1">
38
- Experimental Component
39
- </h3>
40
- <p className="text-sm text-purple-600/80 dark:text-purple-400/80">
41
+ <div style={{ marginBottom: '20px' }}>
42
+ <Alert severity="warning">
43
+ <Alert.Icon><BeakerIcon /></Alert.Icon>
44
+ <Alert.Body>
45
+ <Alert.Title>Experimental Component</Alert.Title>
46
+ <Alert.Content>
41
47
  This component is experimental. The API may change without notice.
42
- </p>
43
- </div>
44
- </div>
48
+ </Alert.Content>
49
+ </Alert.Body>
50
+ </Alert>
45
51
  </div>
46
52
  )}
47
53
 
48
- <h1 className="text-2xl font-semibold text-primary tracking-tight mb-2">
54
+ <Text as="h1" size="xl" weight="semibold" style={{ letterSpacing: '-0.025em', marginBottom: '8px' }}>
49
55
  {meta.name}
50
- </h1>
51
- <p className="text-base text-secondary leading-relaxed mb-5">
56
+ </Text>
57
+ <Text as="p" size="base" color="secondary" style={{ lineHeight: 1.625, marginBottom: '20px' }}>
52
58
  {meta.description}
53
- </p>
59
+ </Text>
54
60
 
55
- <div className="flex flex-wrap items-center gap-2">
61
+ <Stack direction="row" gap="sm" wrap align="center">
56
62
  {/* Category badge */}
57
63
  {meta.category && (
58
- <span className="badge badge-default">{meta.category}</span>
64
+ <Badge variant="default">{meta.category}</Badge>
59
65
  )}
60
66
 
61
67
  {/* Status badge */}
62
- {meta.status && (() => {
63
- const config = getStatusConfig(meta.status);
64
- return config ? (
65
- <span className={clsx('badge', config.badge)}>
66
- {meta.status}
67
- </span>
68
- ) : null;
69
- })()}
68
+ {meta.status && (
69
+ <Badge variant={getStatusBadgeVariant(meta.status)}>
70
+ {meta.status}
71
+ </Badge>
72
+ )}
70
73
 
71
74
  {/* Tags */}
72
75
  {meta.tags && meta.tags.length > 0 && (
73
76
  <>
74
- <span className="text-[--text-muted] mx-1">|</span>
77
+ <Separator orientation="vertical" />
75
78
  {meta.tags.map((tag) => (
76
- <span key={tag} className="badge badge-default">
79
+ <Badge key={tag} variant="default">
77
80
  {tag}
78
- </span>
81
+ </Badge>
79
82
  ))}
80
83
  </>
81
84
  )}
82
- </div>
85
+ </Stack>
83
86
  </section>
84
87
  );
85
88
  }