@fragments-sdk/cli 0.5.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +996 -79
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
- package/dist/chunk-6JBGU74P.js.map +1 -0
- package/dist/chunk-7OPWMLOE.js +1625 -0
- package/dist/chunk-7OPWMLOE.js.map +1 -0
- package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
- package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
- package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
- package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
- package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
- package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
- package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
- package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
- package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
- package/dist/mcp-bin.js +8 -220
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-WY23TJCP.js +12 -0
- package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
- package/dist/static-viewer-GBR7YNF3.js +12 -0
- package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
- package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
- package/dist/viewer-SUFOISZM.js +1822 -0
- package/dist/viewer-SUFOISZM.js.map +1 -0
- package/package.json +6 -5
- package/src/bin.ts +31 -0
- package/src/build.ts +147 -13
- package/src/cli-commands.ts +18 -0
- package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
- package/src/commands/a11y-report.ts +625 -0
- package/src/commands/a11y.ts +168 -14
- package/src/commands/build.ts +16 -0
- package/src/commands/graph.ts +274 -0
- package/src/core/auto-props.ts +464 -0
- package/src/core/composition.ts +64 -1
- package/src/core/graph-extractor.test.ts +542 -0
- package/src/core/graph-extractor.ts +601 -0
- package/src/core/importAnalyzer.ts +5 -0
- package/src/core/schema.ts +2 -0
- package/src/core/types.ts +3 -1
- package/src/index.ts +4 -0
- package/src/mcp/server.ts +13 -220
- package/src/theme/__tests__/component-contrast.test.ts +338 -0
- package/src/theme/__tests__/contrast-validation.test.ts +326 -0
- package/src/theme/contrast.test.ts +331 -0
- package/src/theme/contrast.ts +246 -0
- package/src/theme/generator.ts +213 -1
- package/src/theme/index.ts +16 -0
- package/src/theme/types.ts +51 -0
- package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
- package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
- package/src/viewer/components/AccessibilityPanel.tsx +493 -433
- package/src/viewer/components/ActionCapture.tsx +1 -1
- package/src/viewer/components/ActionsPanel.tsx +142 -183
- package/src/viewer/components/App.tsx +276 -183
- package/src/viewer/components/BottomPanel.tsx +40 -80
- package/src/viewer/components/CodePanel.tsx +9 -87
- package/src/viewer/components/CommandPalette.tsx +117 -74
- package/src/viewer/components/ComponentGraph.tsx +143 -126
- package/src/viewer/components/ComponentHeader.tsx +46 -43
- package/src/viewer/components/ContractPanel.tsx +124 -117
- package/src/viewer/components/ErrorBoundary.tsx +47 -35
- package/src/viewer/components/FigmaEmbed.tsx +18 -13
- package/src/viewer/components/FragmentEditor.tsx +126 -63
- package/src/viewer/components/HealthDashboard.tsx +146 -171
- package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
- package/src/viewer/components/Icons.tsx +151 -98
- package/src/viewer/components/InteractionsPanel.tsx +317 -264
- package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
- package/src/viewer/components/IsolatedRender.tsx +12 -6
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
- package/src/viewer/components/LandingPage.tsx +285 -305
- package/src/viewer/components/Layout.tsx +12 -10
- package/src/viewer/components/LeftSidebar.tsx +103 -155
- package/src/viewer/components/MultiViewportPreview.tsx +254 -63
- package/src/viewer/components/PreviewArea.tsx +113 -44
- package/src/viewer/components/PreviewFrameHost.tsx +36 -6
- package/src/viewer/components/PreviewPane.tsx +2 -3
- package/src/viewer/components/PreviewToolbar.tsx +109 -105
- package/src/viewer/components/PropsEditor.tsx +154 -74
- package/src/viewer/components/PropsTable.tsx +95 -82
- package/src/viewer/components/RelationsSection.tsx +71 -40
- package/src/viewer/components/ResizablePanel.tsx +158 -55
- package/src/viewer/components/RightSidebar.tsx +46 -56
- package/src/viewer/components/ScreenshotButton.tsx +12 -12
- package/src/viewer/components/SkeletonLoader.tsx +99 -83
- package/src/viewer/components/StoryRenderer.tsx +4 -11
- package/src/viewer/components/Toast.tsx +3 -67
- package/src/viewer/components/TokenStylePanel.tsx +136 -118
- package/src/viewer/components/UsageSection.tsx +26 -26
- package/src/viewer/components/VariantMatrix.tsx +140 -47
- package/src/viewer/components/VariantTabs.tsx +24 -68
- package/src/viewer/components/ViewportSelector.tsx +121 -114
- package/src/viewer/constants/ui.ts +23 -22
- package/src/viewer/entry.tsx +8 -3
- package/src/viewer/index.ts +3 -6
- package/src/viewer/preview-frame.html +43 -18
- package/src/viewer/server.ts +7 -16
- package/src/viewer/styles/globals.css +46 -85
- package/src/viewer/utils/a11y-fixes.ts +53 -30
- package/dist/chunk-ICAIQ57V.js.map +0 -1
- package/dist/chunk-U4GQ2JTD.js +0 -832
- package/dist/chunk-U4GQ2JTD.js.map +0 -1
- package/dist/scan-ESEXV7LF.js +0 -12
- package/dist/static-viewer-O37MJ5B6.js +0 -12
- package/dist/viewer-YDGFDTK5.js +0 -11104
- package/dist/viewer-YDGFDTK5.js.map +0 -1
- package/src/viewer/postcss.config.js +0 -6
- package/src/viewer/tailwind.config.js +0 -37
- /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
- /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
- /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
- /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
- /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
- /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
- /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
- /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
- /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
- /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
- /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
|
@@ -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: "
|
|
30
|
-
bgColor: "
|
|
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: "
|
|
36
|
-
bgColor: "
|
|
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: "
|
|
42
|
-
bgColor: "
|
|
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: "
|
|
48
|
-
bgColor: "
|
|
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: "
|
|
54
|
-
bgColor: "
|
|
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
|
|
141
|
-
<div
|
|
142
|
-
<
|
|
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
|
|
147
|
-
<
|
|
148
|
-
<
|
|
149
|
-
<EmptyIcon
|
|
150
|
-
</
|
|
151
|
-
<
|
|
152
|
-
|
|
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
|
|
167
|
-
<div
|
|
168
|
-
<
|
|
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
|
|
173
|
-
<
|
|
174
|
-
<
|
|
175
|
-
<EmptyIcon
|
|
176
|
-
</
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
</
|
|
203
|
+
</EmptyState>
|
|
200
204
|
</div>
|
|
201
205
|
</div>
|
|
202
206
|
);
|
|
203
207
|
}
|
|
204
208
|
|
|
205
209
|
return (
|
|
206
|
-
<div
|
|
210
|
+
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
207
211
|
{/* Header */}
|
|
208
|
-
<div
|
|
209
|
-
<
|
|
210
|
-
<
|
|
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
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
</
|
|
229
|
-
<
|
|
230
|
-
Relationships for <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
|
|
225
|
+
<span style={{ marginLeft: '4px' }}>
|
|
233
226
|
({detectedCount} from variants)
|
|
234
227
|
</span>
|
|
235
228
|
)}
|
|
236
|
-
</
|
|
229
|
+
</Text>
|
|
237
230
|
</div>
|
|
238
231
|
|
|
239
232
|
{/* Graph visualization */}
|
|
240
|
-
<div
|
|
233
|
+
<div style={{ flex: 1, overflowY: 'auto', padding: '16px' }}>
|
|
241
234
|
{/* Defined relations */}
|
|
242
235
|
{groupedRelations && (
|
|
243
|
-
<
|
|
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
|
-
<
|
|
252
|
-
<
|
|
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
|
-
</
|
|
255
|
-
<div
|
|
256
|
-
</
|
|
257
|
-
<
|
|
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
|
-
</
|
|
260
|
+
</Stack>
|
|
268
261
|
</div>
|
|
269
262
|
);
|
|
270
263
|
}
|
|
271
264
|
)}
|
|
272
|
-
</
|
|
265
|
+
</Stack>
|
|
273
266
|
)}
|
|
274
267
|
|
|
275
268
|
{/* Reverse relations (inbound references) */}
|
|
276
269
|
{reverseRelations.length > 0 && (
|
|
277
|
-
<div
|
|
278
|
-
<
|
|
279
|
-
<
|
|
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
|
-
</
|
|
282
|
-
<div
|
|
283
|
-
</
|
|
284
|
-
<
|
|
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
|
-
</
|
|
291
|
+
</Stack>
|
|
299
292
|
</div>
|
|
300
293
|
)}
|
|
301
294
|
|
|
302
295
|
{/* Legend */}
|
|
303
|
-
<div
|
|
304
|
-
<
|
|
305
|
-
<
|
|
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
|
-
<
|
|
309
|
-
<span
|
|
310
|
-
<
|
|
311
|
-
</
|
|
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
|
-
</
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
<
|
|
343
|
-
<
|
|
344
|
-
<
|
|
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
|
-
</
|
|
367
|
+
</Text>
|
|
347
368
|
{relation.isDetected && (
|
|
348
|
-
<
|
|
349
|
-
<WandIcon
|
|
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
|
-
</
|
|
372
|
+
</Badge>
|
|
352
373
|
)}
|
|
353
374
|
{isReverse && (
|
|
354
|
-
<
|
|
355
|
-
inbound
|
|
356
|
-
</span>
|
|
375
|
+
<Badge size="sm" variant="default">inbound</Badge>
|
|
357
376
|
)}
|
|
358
377
|
{!exists && (
|
|
359
|
-
<
|
|
378
|
+
<Text size="xs" color="secondary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
|
360
379
|
(not found)
|
|
361
|
-
</
|
|
380
|
+
</Text>
|
|
362
381
|
)}
|
|
363
|
-
</
|
|
382
|
+
</Stack>
|
|
364
383
|
{exists && (
|
|
365
|
-
<ChevronRightIcon
|
|
384
|
+
<ChevronRightIcon style={{ width: '16px', height: '16px', color: 'var(--text-tertiary)' }} />
|
|
366
385
|
)}
|
|
367
|
-
</
|
|
386
|
+
</Stack>
|
|
368
387
|
{relation.note && (
|
|
369
|
-
<
|
|
388
|
+
<Text size="xs" color="secondary" style={{ marginTop: '4px' }}>
|
|
370
389
|
{relation.note}
|
|
371
|
-
</
|
|
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 "
|
|
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
|
|
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"
|
|
22
|
+
<section id="overview" style={{ scrollMarginTop: '96px', marginBottom: '24px' }}>
|
|
13
23
|
{/* Deprecated Warning Banner */}
|
|
14
24
|
{meta.status === 'deprecated' && (
|
|
15
|
-
<div
|
|
16
|
-
<
|
|
17
|
-
<WarningIcon
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
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
|
-
</
|
|
26
|
-
</
|
|
27
|
-
</
|
|
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
|
|
34
|
-
<
|
|
35
|
-
<BeakerIcon
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
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
|
-
</
|
|
43
|
-
</
|
|
44
|
-
</
|
|
48
|
+
</Alert.Content>
|
|
49
|
+
</Alert.Body>
|
|
50
|
+
</Alert>
|
|
45
51
|
</div>
|
|
46
52
|
)}
|
|
47
53
|
|
|
48
|
-
<h1
|
|
54
|
+
<Text as="h1" size="xl" weight="semibold" style={{ letterSpacing: '-0.025em', marginBottom: '8px' }}>
|
|
49
55
|
{meta.name}
|
|
50
|
-
</
|
|
51
|
-
<p
|
|
56
|
+
</Text>
|
|
57
|
+
<Text as="p" size="base" color="secondary" style={{ lineHeight: 1.625, marginBottom: '20px' }}>
|
|
52
58
|
{meta.description}
|
|
53
|
-
</
|
|
59
|
+
</Text>
|
|
54
60
|
|
|
55
|
-
<
|
|
61
|
+
<Stack direction="row" gap="sm" wrap align="center">
|
|
56
62
|
{/* Category badge */}
|
|
57
63
|
{meta.category && (
|
|
58
|
-
<
|
|
64
|
+
<Badge variant="default">{meta.category}</Badge>
|
|
59
65
|
)}
|
|
60
66
|
|
|
61
67
|
{/* Status badge */}
|
|
62
|
-
{meta.status && (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
<
|
|
77
|
+
<Separator orientation="vertical" />
|
|
75
78
|
{meta.tags.map((tag) => (
|
|
76
|
-
<
|
|
79
|
+
<Badge key={tag} variant="default">
|
|
77
80
|
{tag}
|
|
78
|
-
</
|
|
81
|
+
</Badge>
|
|
79
82
|
))}
|
|
80
83
|
</>
|
|
81
84
|
)}
|
|
82
|
-
</
|
|
85
|
+
</Stack>
|
|
83
86
|
</section>
|
|
84
87
|
);
|
|
85
88
|
}
|