@fragments-sdk/cli 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Graph Panel - Visualizes component relationships
|
|
3
|
+
*
|
|
4
|
+
* Displays a visual graph showing:
|
|
5
|
+
* - The current component in the center
|
|
6
|
+
* - Related components (parent, child, sibling, alternative, composition)
|
|
7
|
+
* - Auto-detected relationships from render analysis
|
|
8
|
+
* - Click to navigate to related components
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useMemo, useState } from "react";
|
|
12
|
+
import clsx from "clsx";
|
|
13
|
+
import type { SegmentDefinition, ComponentRelation, RelationshipType } from "../../core/index.js";
|
|
14
|
+
import { ChevronRightIcon, EmptyIcon, WandIcon } from "./Icons.js";
|
|
15
|
+
import { detectAllRelationships, mergeRelationships } from "../utils/detectRelationships.js";
|
|
16
|
+
|
|
17
|
+
interface ComponentGraphProps {
|
|
18
|
+
/** Current segment definition */
|
|
19
|
+
segment: SegmentDefinition | null;
|
|
20
|
+
/** All available segments for navigation */
|
|
21
|
+
allSegments: Array<{ path: string; segment: SegmentDefinition }>;
|
|
22
|
+
/** Callback when a component is clicked */
|
|
23
|
+
onNavigate?: (componentName: string) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const RELATIONSHIP_CONFIG: Record<RelationshipType, { label: string; color: string; bgColor: string; icon: string }> = {
|
|
27
|
+
parent: {
|
|
28
|
+
label: "Parent",
|
|
29
|
+
color: "text-purple-600 dark:text-purple-400",
|
|
30
|
+
bgColor: "bg-purple-100 dark:bg-purple-900/30",
|
|
31
|
+
icon: "^",
|
|
32
|
+
},
|
|
33
|
+
child: {
|
|
34
|
+
label: "Child",
|
|
35
|
+
color: "text-blue-600 dark:text-blue-400",
|
|
36
|
+
bgColor: "bg-blue-100 dark:bg-blue-900/30",
|
|
37
|
+
icon: "v",
|
|
38
|
+
},
|
|
39
|
+
sibling: {
|
|
40
|
+
label: "Sibling",
|
|
41
|
+
color: "text-green-600 dark:text-green-400",
|
|
42
|
+
bgColor: "bg-green-100 dark:bg-green-900/30",
|
|
43
|
+
icon: "<->",
|
|
44
|
+
},
|
|
45
|
+
alternative: {
|
|
46
|
+
label: "Alternative",
|
|
47
|
+
color: "text-amber-600 dark:text-amber-400",
|
|
48
|
+
bgColor: "bg-amber-100 dark:bg-amber-900/30",
|
|
49
|
+
icon: "~",
|
|
50
|
+
},
|
|
51
|
+
composition: {
|
|
52
|
+
label: "Composition",
|
|
53
|
+
color: "text-cyan-600 dark:text-cyan-400",
|
|
54
|
+
bgColor: "bg-cyan-100 dark:bg-cyan-900/30",
|
|
55
|
+
icon: "+",
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function ComponentGraph({ segment, allSegments, onNavigate }: ComponentGraphProps) {
|
|
60
|
+
const [showAutoDetected, setShowAutoDetected] = useState(true);
|
|
61
|
+
|
|
62
|
+
// Auto-detect relationships
|
|
63
|
+
const detectedRelationships = useMemo(() => {
|
|
64
|
+
if (!segment) return [];
|
|
65
|
+
try {
|
|
66
|
+
return detectAllRelationships(segment, allSegments);
|
|
67
|
+
} catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}, [segment, allSegments]);
|
|
71
|
+
|
|
72
|
+
// Merge manual and detected relationships
|
|
73
|
+
const allRelationships = useMemo(() => {
|
|
74
|
+
if (!segment) return [];
|
|
75
|
+
return mergeRelationships(segment.relations, showAutoDetected ? detectedRelationships : []);
|
|
76
|
+
}, [segment, detectedRelationships, showAutoDetected]);
|
|
77
|
+
|
|
78
|
+
// Group relations by type
|
|
79
|
+
const groupedRelations = useMemo(() => {
|
|
80
|
+
if (allRelationships.length === 0) return null;
|
|
81
|
+
|
|
82
|
+
const groups: Record<RelationshipType, Array<ComponentRelation & { isDetected?: boolean; confidence?: number }>> = {
|
|
83
|
+
parent: [],
|
|
84
|
+
child: [],
|
|
85
|
+
sibling: [],
|
|
86
|
+
alternative: [],
|
|
87
|
+
composition: [],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
for (const relation of allRelationships) {
|
|
91
|
+
groups[relation.relationship].push(relation);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return groups;
|
|
95
|
+
}, [allRelationships]);
|
|
96
|
+
|
|
97
|
+
// Find reverse relations (components that reference this one)
|
|
98
|
+
const reverseRelations = useMemo(() => {
|
|
99
|
+
if (!segment) return [];
|
|
100
|
+
|
|
101
|
+
const componentName = segment.meta.name;
|
|
102
|
+
const reverse: Array<{ segment: SegmentDefinition; relation: ComponentRelation }> = [];
|
|
103
|
+
|
|
104
|
+
for (const { segment: otherSegment } of allSegments) {
|
|
105
|
+
if (otherSegment.meta.name === componentName) continue;
|
|
106
|
+
if (!otherSegment.relations) continue;
|
|
107
|
+
|
|
108
|
+
for (const relation of otherSegment.relations) {
|
|
109
|
+
if (relation.component === componentName) {
|
|
110
|
+
reverse.push({
|
|
111
|
+
segment: otherSegment,
|
|
112
|
+
relation: {
|
|
113
|
+
...relation,
|
|
114
|
+
component: otherSegment.meta.name,
|
|
115
|
+
// Flip the relationship direction for display
|
|
116
|
+
relationship: flipRelationship(relation.relationship),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return reverse;
|
|
124
|
+
}, [segment, allSegments]);
|
|
125
|
+
|
|
126
|
+
// Check if a component exists in our segments
|
|
127
|
+
const componentExists = (name: string) => {
|
|
128
|
+
return allSegments.some(s => s.segment.meta.name === name);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const handleNavigate = (componentName: string) => {
|
|
132
|
+
if (componentExists(componentName) && onNavigate) {
|
|
133
|
+
onNavigate(componentName);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// No segment selected
|
|
138
|
+
if (!segment) {
|
|
139
|
+
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>
|
|
145
|
+
</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>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const hasRelations = (groupedRelations && Object.values(groupedRelations).some(g => g.length > 0)) || reverseRelations.length > 0;
|
|
161
|
+
const detectedCount = detectedRelationships.length;
|
|
162
|
+
|
|
163
|
+
// No relations defined
|
|
164
|
+
if (!hasRelations) {
|
|
165
|
+
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>
|
|
171
|
+
</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: [
|
|
186
|
+
{
|
|
187
|
+
component: "ButtonGroup",
|
|
188
|
+
relationship: "parent",
|
|
189
|
+
note: "Use ButtonGroup for multiple buttons"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
component: "IconButton",
|
|
193
|
+
relationship: "alternative",
|
|
194
|
+
note: "Use when only an icon is needed"
|
|
195
|
+
}
|
|
196
|
+
]`}
|
|
197
|
+
</pre>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<div className="h-full flex flex-col">
|
|
207
|
+
{/* 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>
|
|
213
|
+
{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>
|
|
227
|
+
)}
|
|
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>
|
|
231
|
+
{showAutoDetected && detectedCount > 0 && (
|
|
232
|
+
<span className="ml-1">
|
|
233
|
+
({detectedCount} from variants)
|
|
234
|
+
</span>
|
|
235
|
+
)}
|
|
236
|
+
</p>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{/* Graph visualization */}
|
|
240
|
+
<div className="flex-1 overflow-y-auto p-4">
|
|
241
|
+
{/* Defined relations */}
|
|
242
|
+
{groupedRelations && (
|
|
243
|
+
<div className="space-y-4">
|
|
244
|
+
{(Object.entries(groupedRelations) as [RelationshipType, ComponentRelation[]][]).map(
|
|
245
|
+
([type, relations]) => {
|
|
246
|
+
if (relations.length === 0) return null;
|
|
247
|
+
const config = RELATIONSHIP_CONFIG[type];
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<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)}>
|
|
253
|
+
{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">
|
|
258
|
+
{relations.map((relation, index) => (
|
|
259
|
+
<RelationCard
|
|
260
|
+
key={`${relation.component}-${index}`}
|
|
261
|
+
relation={relation}
|
|
262
|
+
config={config}
|
|
263
|
+
exists={componentExists(relation.component)}
|
|
264
|
+
onNavigate={handleNavigate}
|
|
265
|
+
/>
|
|
266
|
+
))}
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
)}
|
|
272
|
+
</div>
|
|
273
|
+
)}
|
|
274
|
+
|
|
275
|
+
{/* Reverse relations (inbound references) */}
|
|
276
|
+
{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">
|
|
280
|
+
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">
|
|
285
|
+
{reverseRelations.map(({ relation }, index) => {
|
|
286
|
+
const config = RELATIONSHIP_CONFIG[relation.relationship];
|
|
287
|
+
return (
|
|
288
|
+
<RelationCard
|
|
289
|
+
key={`reverse-${relation.component}-${index}`}
|
|
290
|
+
relation={relation}
|
|
291
|
+
config={config}
|
|
292
|
+
exists={componentExists(relation.component)}
|
|
293
|
+
onNavigate={handleNavigate}
|
|
294
|
+
isReverse
|
|
295
|
+
/>
|
|
296
|
+
);
|
|
297
|
+
})}
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
301
|
+
|
|
302
|
+
{/* 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">
|
|
306
|
+
{(Object.entries(RELATIONSHIP_CONFIG) as [RelationshipType, typeof RELATIONSHIP_CONFIG[RelationshipType]][]).map(
|
|
307
|
+
([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>
|
|
312
|
+
)
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
interface RelationCardProps {
|
|
322
|
+
relation: ComponentRelation & { isDetected?: boolean; confidence?: number };
|
|
323
|
+
config: typeof RELATIONSHIP_CONFIG[RelationshipType];
|
|
324
|
+
exists: boolean;
|
|
325
|
+
onNavigate: (name: string) => void;
|
|
326
|
+
isReverse?: boolean;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function RelationCard({ relation, config, exists, onNavigate, isReverse }: RelationCardProps) {
|
|
330
|
+
return (
|
|
331
|
+
<button
|
|
332
|
+
onClick={() => exists && onNavigate(relation.component)}
|
|
333
|
+
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
|
+
)}
|
|
341
|
+
>
|
|
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)}>
|
|
345
|
+
{relation.component}
|
|
346
|
+
</span>
|
|
347
|
+
{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" />
|
|
350
|
+
auto
|
|
351
|
+
</span>
|
|
352
|
+
)}
|
|
353
|
+
{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>
|
|
357
|
+
)}
|
|
358
|
+
{!exists && (
|
|
359
|
+
<span className="text-[10px] uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
360
|
+
(not found)
|
|
361
|
+
</span>
|
|
362
|
+
)}
|
|
363
|
+
</div>
|
|
364
|
+
{exists && (
|
|
365
|
+
<ChevronRightIcon className="w-4 h-4 text-gray-400" />
|
|
366
|
+
)}
|
|
367
|
+
</div>
|
|
368
|
+
{relation.note && (
|
|
369
|
+
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
|
370
|
+
{relation.note}
|
|
371
|
+
</p>
|
|
372
|
+
)}
|
|
373
|
+
</button>
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Flip a relationship for reverse display.
|
|
379
|
+
* e.g., if Button has child: IconButton, then IconButton sees Button as parent
|
|
380
|
+
*/
|
|
381
|
+
function flipRelationship(type: RelationshipType): RelationshipType {
|
|
382
|
+
switch (type) {
|
|
383
|
+
case "parent":
|
|
384
|
+
return "child";
|
|
385
|
+
case "child":
|
|
386
|
+
return "parent";
|
|
387
|
+
case "sibling":
|
|
388
|
+
return "sibling";
|
|
389
|
+
case "alternative":
|
|
390
|
+
return "alternative";
|
|
391
|
+
case "composition":
|
|
392
|
+
return "composition";
|
|
393
|
+
}
|
|
394
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { SegmentMeta } from '../../core/index.js';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { getStatusConfig } from '../constants/ui.js';
|
|
4
|
+
import { WarningIcon, BeakerIcon } from './Icons.js';
|
|
5
|
+
|
|
6
|
+
interface ComponentHeaderProps {
|
|
7
|
+
meta: SegmentMeta;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ComponentHeader({ meta }: ComponentHeaderProps) {
|
|
11
|
+
return (
|
|
12
|
+
<section id="overview" className="scroll-mt-24 mb-6">
|
|
13
|
+
{/* Deprecated Warning Banner */}
|
|
14
|
+
{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">
|
|
23
|
+
This component is deprecated and will be removed in a future version.
|
|
24
|
+
{meta.tags?.includes('migration') && ' Check the usage guidelines for migration instructions.'}
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
)}
|
|
30
|
+
|
|
31
|
+
{/* Experimental Warning Banner */}
|
|
32
|
+
{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
|
+
This component is experimental. The API may change without notice.
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
<h1 className="text-2xl font-semibold text-primary tracking-tight mb-2">
|
|
49
|
+
{meta.name}
|
|
50
|
+
</h1>
|
|
51
|
+
<p className="text-base text-secondary leading-relaxed mb-5">
|
|
52
|
+
{meta.description}
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
56
|
+
{/* Category badge */}
|
|
57
|
+
{meta.category && (
|
|
58
|
+
<span className="badge badge-default">{meta.category}</span>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{/* 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
|
+
})()}
|
|
70
|
+
|
|
71
|
+
{/* Tags */}
|
|
72
|
+
{meta.tags && meta.tags.length > 0 && (
|
|
73
|
+
<>
|
|
74
|
+
<span className="text-[--text-muted] mx-1">|</span>
|
|
75
|
+
{meta.tags.map((tag) => (
|
|
76
|
+
<span key={tag} className="badge badge-default">
|
|
77
|
+
{tag}
|
|
78
|
+
</span>
|
|
79
|
+
))}
|
|
80
|
+
</>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
84
|
+
);
|
|
85
|
+
}
|