@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,512 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { PropDefinition } from "../../core/index.js";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { ControlsIcon, ChevronDownIcon, RefreshIcon } from "./Icons.js";
|
|
5
|
+
|
|
6
|
+
interface PropsEditorProps {
|
|
7
|
+
props: Record<string, PropDefinition>;
|
|
8
|
+
values: Record<string, unknown>;
|
|
9
|
+
onChange: (name: string, value: unknown) => void;
|
|
10
|
+
onReset: () => void;
|
|
11
|
+
hasChanges: boolean;
|
|
12
|
+
compact?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function PropsEditor({
|
|
16
|
+
props,
|
|
17
|
+
values,
|
|
18
|
+
onChange,
|
|
19
|
+
onReset,
|
|
20
|
+
hasChanges,
|
|
21
|
+
compact = false,
|
|
22
|
+
}: PropsEditorProps) {
|
|
23
|
+
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
24
|
+
const propEntries = Object.entries(props);
|
|
25
|
+
|
|
26
|
+
if (propEntries.length === 0) return null;
|
|
27
|
+
|
|
28
|
+
// Compact mode - show controls in a single horizontal line
|
|
29
|
+
if (compact) {
|
|
30
|
+
return (
|
|
31
|
+
<div className="flex flex-col gap-6">
|
|
32
|
+
{propEntries.map(([name, prop]) => (
|
|
33
|
+
<PropControl
|
|
34
|
+
key={name}
|
|
35
|
+
name={name}
|
|
36
|
+
prop={prop}
|
|
37
|
+
value={values[name]}
|
|
38
|
+
onChange={(value) => onChange(name, value)}
|
|
39
|
+
compact
|
|
40
|
+
/>
|
|
41
|
+
))}
|
|
42
|
+
{hasChanges && (
|
|
43
|
+
<button
|
|
44
|
+
onClick={onReset}
|
|
45
|
+
className="flex items-center gap-1.5 px-2 py-1 text-xs font-medium text-[--color-accent] hover:bg-[--bg-hover] rounded transition-colors"
|
|
46
|
+
>
|
|
47
|
+
<RefreshIcon className="w-3 h-3" />
|
|
48
|
+
Reset
|
|
49
|
+
</button>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="border border-[--border] rounded-xl overflow-hidden bg-[--bg-elevated]">
|
|
57
|
+
{/* Header */}
|
|
58
|
+
<button
|
|
59
|
+
onClick={() => setIsCollapsed(!isCollapsed)}
|
|
60
|
+
className="w-full px-4 py-3 flex items-center justify-between bg-[--bg-secondary] border-b border-[--border-subtle] hover:bg-[--bg-hover] transition-colors"
|
|
61
|
+
>
|
|
62
|
+
<div className="flex items-center gap-2">
|
|
63
|
+
<ControlsIcon className="w-4 h-4 text-secondary" />
|
|
64
|
+
<span className="text-[13px] font-medium text-primary">
|
|
65
|
+
Props Editor
|
|
66
|
+
</span>
|
|
67
|
+
{hasChanges && (
|
|
68
|
+
<span className="px-1.5 py-0.5 text-[10px] font-medium bg-[--color-accent] text-white rounded">
|
|
69
|
+
Modified
|
|
70
|
+
</span>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
<ChevronDownIcon
|
|
74
|
+
className={clsx(
|
|
75
|
+
"w-4 h-4 text-tertiary transition-transform",
|
|
76
|
+
isCollapsed && "-rotate-90"
|
|
77
|
+
)}
|
|
78
|
+
/>
|
|
79
|
+
</button>
|
|
80
|
+
|
|
81
|
+
{/* Content */}
|
|
82
|
+
{!isCollapsed && (
|
|
83
|
+
<div className="p-4 space-y-4">
|
|
84
|
+
{/* Reset button */}
|
|
85
|
+
{hasChanges && (
|
|
86
|
+
<button
|
|
87
|
+
onClick={onReset}
|
|
88
|
+
className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-[--color-accent] hover:bg-[--bg-hover] rounded transition-colors"
|
|
89
|
+
>
|
|
90
|
+
<RefreshIcon className="w-3.5 h-3.5" />
|
|
91
|
+
Reset to defaults
|
|
92
|
+
</button>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
{/* Prop controls */}
|
|
96
|
+
<div className="space-y-3">
|
|
97
|
+
{propEntries.map(([name, prop]) => (
|
|
98
|
+
<PropControl
|
|
99
|
+
key={name}
|
|
100
|
+
name={name}
|
|
101
|
+
prop={prop}
|
|
102
|
+
value={values[name]}
|
|
103
|
+
onChange={(value) => onChange(name, value)}
|
|
104
|
+
/>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface PropControlProps {
|
|
114
|
+
name: string;
|
|
115
|
+
prop: PropDefinition;
|
|
116
|
+
value: unknown;
|
|
117
|
+
onChange: (value: unknown) => void;
|
|
118
|
+
compact?: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Determine which control to render based on prop type and controlType
|
|
123
|
+
*/
|
|
124
|
+
function getControlForProp(
|
|
125
|
+
prop: PropDefinition,
|
|
126
|
+
displayValue: unknown,
|
|
127
|
+
onChange: (value: unknown) => void,
|
|
128
|
+
compact: boolean = false
|
|
129
|
+
) {
|
|
130
|
+
// Check controlType first for special controls (color, date, range)
|
|
131
|
+
if (prop.controlType === "color") {
|
|
132
|
+
return (
|
|
133
|
+
<ColorControl
|
|
134
|
+
value={displayValue as string}
|
|
135
|
+
onChange={onChange}
|
|
136
|
+
presetColors={prop.controlOptions?.presetColors}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (prop.controlType === "date") {
|
|
142
|
+
return <DateControl value={displayValue as string} onChange={onChange} />;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (prop.controlType === "range") {
|
|
146
|
+
return (
|
|
147
|
+
<RangeControl
|
|
148
|
+
value={displayValue as number}
|
|
149
|
+
onChange={onChange}
|
|
150
|
+
min={prop.controlOptions?.min}
|
|
151
|
+
max={prop.controlOptions?.max}
|
|
152
|
+
step={prop.controlOptions?.step}
|
|
153
|
+
/>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Fall back to type-based controls
|
|
158
|
+
if (prop.type === "boolean") {
|
|
159
|
+
return <BooleanControl value={displayValue as boolean} onChange={onChange} />;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (prop.type === "enum" && prop.values) {
|
|
163
|
+
return (
|
|
164
|
+
<EnumControl
|
|
165
|
+
value={displayValue as string}
|
|
166
|
+
values={prop.values as string[]}
|
|
167
|
+
onChange={onChange}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (prop.type === "number") {
|
|
173
|
+
return <NumberControl value={displayValue as number} onChange={onChange} />;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (prop.type === "string") {
|
|
177
|
+
return <StringControl value={displayValue as string} onChange={onChange} compact={compact} />;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (prop.type === "function") {
|
|
181
|
+
return <FunctionControl value={displayValue} onChange={onChange} />;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return <StringControl value={String(displayValue)} onChange={onChange} compact={compact} />;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function PropControl({
|
|
188
|
+
name,
|
|
189
|
+
prop,
|
|
190
|
+
value,
|
|
191
|
+
onChange,
|
|
192
|
+
compact = false,
|
|
193
|
+
}: PropControlProps) {
|
|
194
|
+
const displayValue = value ?? prop.default ?? "";
|
|
195
|
+
|
|
196
|
+
if (compact) {
|
|
197
|
+
return (
|
|
198
|
+
<div className="flex gap-2">
|
|
199
|
+
<label className="text-[11px] font-medium text-secondary font-mono whitespace-nowrap min-w-[100px]">
|
|
200
|
+
{name}
|
|
201
|
+
</label>
|
|
202
|
+
{getControlForProp(prop, displayValue, onChange, true)}
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="flex flex-col gap-1.5">
|
|
209
|
+
<div className="flex items-center gap-2">
|
|
210
|
+
<label className="text-[12px] font-medium text-primary font-mono">
|
|
211
|
+
{name}
|
|
212
|
+
{prop.required && (
|
|
213
|
+
<span className="text-[--color-danger] ml-0.5">*</span>
|
|
214
|
+
)}
|
|
215
|
+
</label>
|
|
216
|
+
<span className="text-[11px] text-tertiary">{prop.type}</span>
|
|
217
|
+
{prop.controlType && prop.controlType !== prop.type && (
|
|
218
|
+
<span className="text-[10px] text-tertiary bg-[--bg-tertiary] px-1.5 py-0.5 rounded">
|
|
219
|
+
{prop.controlType}
|
|
220
|
+
</span>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
{getControlForProp(prop, displayValue, onChange, false)}
|
|
225
|
+
|
|
226
|
+
{prop.description && (
|
|
227
|
+
<p className="text-[11px] text-tertiary leading-relaxed">
|
|
228
|
+
{prop.description}
|
|
229
|
+
</p>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function BooleanControl({
|
|
236
|
+
value,
|
|
237
|
+
onChange,
|
|
238
|
+
}: {
|
|
239
|
+
value: boolean;
|
|
240
|
+
onChange: (v: boolean) => void;
|
|
241
|
+
}) {
|
|
242
|
+
return (
|
|
243
|
+
<button
|
|
244
|
+
onClick={() => onChange(!value)}
|
|
245
|
+
className={clsx(
|
|
246
|
+
"relative inline-flex h-6 w-11 items-center rounded-full transition-colors",
|
|
247
|
+
value ? "bg-[--color-accent]" : "bg-[--bg-tertiary]"
|
|
248
|
+
)}
|
|
249
|
+
>
|
|
250
|
+
<span
|
|
251
|
+
className={clsx(
|
|
252
|
+
"inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform",
|
|
253
|
+
value ? "translate-x-6" : "translate-x-1"
|
|
254
|
+
)}
|
|
255
|
+
/>
|
|
256
|
+
</button>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function EnumControl({
|
|
261
|
+
value,
|
|
262
|
+
values,
|
|
263
|
+
onChange,
|
|
264
|
+
}: {
|
|
265
|
+
value: string;
|
|
266
|
+
values: string[];
|
|
267
|
+
onChange: (v: string) => void;
|
|
268
|
+
}) {
|
|
269
|
+
return (
|
|
270
|
+
<select
|
|
271
|
+
value={value}
|
|
272
|
+
onChange={(e) => onChange(e.target.value)}
|
|
273
|
+
className="px-2.5 py-1.5 text-[12px] bg-[--bg-primary] border border-[--border] rounded-md text-primary focus:outline-none focus:ring-2 focus:ring-[--color-accent] focus:border-transparent"
|
|
274
|
+
>
|
|
275
|
+
{values.map((v) => (
|
|
276
|
+
<option key={v} value={v}>
|
|
277
|
+
{v}
|
|
278
|
+
</option>
|
|
279
|
+
))}
|
|
280
|
+
</select>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function NumberControl({
|
|
285
|
+
value,
|
|
286
|
+
onChange,
|
|
287
|
+
}: {
|
|
288
|
+
value: number;
|
|
289
|
+
onChange: (v: number) => void;
|
|
290
|
+
}) {
|
|
291
|
+
return (
|
|
292
|
+
<input
|
|
293
|
+
type="number"
|
|
294
|
+
value={value ?? ""}
|
|
295
|
+
onChange={(e) =>
|
|
296
|
+
onChange(
|
|
297
|
+
e.target.value
|
|
298
|
+
? Number(e.target.value)
|
|
299
|
+
: (undefined as unknown as number)
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
className="px-2.5 py-1.5 text-[12px] font-mono bg-[--bg-primary] border border-[--border] rounded-md text-primary focus:outline-none focus:ring-2 focus:ring-[--color-accent] focus:border-transparent w-32"
|
|
303
|
+
/>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function StringControl({
|
|
308
|
+
value,
|
|
309
|
+
onChange,
|
|
310
|
+
compact = false,
|
|
311
|
+
}: {
|
|
312
|
+
value: string;
|
|
313
|
+
onChange: (v: string) => void;
|
|
314
|
+
compact?: boolean;
|
|
315
|
+
}) {
|
|
316
|
+
return (
|
|
317
|
+
<input
|
|
318
|
+
type="text"
|
|
319
|
+
value={value ?? ""}
|
|
320
|
+
onChange={(e) => onChange(e.target.value)}
|
|
321
|
+
className={clsx(
|
|
322
|
+
"px-2 py-1 text-[11px] font-mono bg-[--bg-primary] border border-[--border] rounded text-primary focus:outline-none focus:ring-1 focus:ring-[--color-accent] focus:border-transparent",
|
|
323
|
+
compact ? "w-24" : "w-full max-w-xs px-2.5 py-1.5 text-[12px]"
|
|
324
|
+
)}
|
|
325
|
+
/>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function FunctionControl({
|
|
330
|
+
value,
|
|
331
|
+
onChange,
|
|
332
|
+
}: {
|
|
333
|
+
value: unknown;
|
|
334
|
+
onChange: (v: unknown) => void;
|
|
335
|
+
}) {
|
|
336
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
337
|
+
const [inputValue, setInputValue] = useState('');
|
|
338
|
+
|
|
339
|
+
// Get a display string for the function
|
|
340
|
+
const getDisplayValue = () => {
|
|
341
|
+
if (typeof value === 'function') {
|
|
342
|
+
const fnStr = value.toString();
|
|
343
|
+
// Truncate if too long
|
|
344
|
+
if (fnStr.length > 50) {
|
|
345
|
+
return fnStr.slice(0, 47) + '...';
|
|
346
|
+
}
|
|
347
|
+
return fnStr;
|
|
348
|
+
}
|
|
349
|
+
if (typeof value === 'string' && value.trim()) {
|
|
350
|
+
return value;
|
|
351
|
+
}
|
|
352
|
+
return '() => {}';
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const handleStartEdit = () => {
|
|
356
|
+
// Convert function to string for editing
|
|
357
|
+
if (typeof value === 'function') {
|
|
358
|
+
setInputValue(value.toString());
|
|
359
|
+
} else if (typeof value === 'string') {
|
|
360
|
+
setInputValue(value);
|
|
361
|
+
} else {
|
|
362
|
+
setInputValue('() => {}');
|
|
363
|
+
}
|
|
364
|
+
setIsEditing(true);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const handleSave = () => {
|
|
368
|
+
// Save as string - the parent will handle conversion
|
|
369
|
+
onChange(inputValue);
|
|
370
|
+
setIsEditing(false);
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const handleCancel = () => {
|
|
374
|
+
setIsEditing(false);
|
|
375
|
+
setInputValue('');
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
if (isEditing) {
|
|
379
|
+
return (
|
|
380
|
+
<div className="flex flex-col gap-2 flex-1">
|
|
381
|
+
<textarea
|
|
382
|
+
value={inputValue}
|
|
383
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
384
|
+
className="w-full px-2 py-1.5 text-[11px] font-mono bg-[--bg-primary] border border-[--border] rounded text-primary focus:outline-none focus:ring-1 focus:ring-[--color-accent] resize-y min-h-[60px]"
|
|
385
|
+
placeholder="() => console.log('clicked')"
|
|
386
|
+
autoFocus
|
|
387
|
+
/>
|
|
388
|
+
<div className="flex gap-1.5">
|
|
389
|
+
<button
|
|
390
|
+
onClick={handleSave}
|
|
391
|
+
className="px-2 py-1 text-[10px] font-medium bg-[--color-accent] text-white rounded hover:opacity-90 transition-opacity"
|
|
392
|
+
>
|
|
393
|
+
Apply
|
|
394
|
+
</button>
|
|
395
|
+
<button
|
|
396
|
+
onClick={handleCancel}
|
|
397
|
+
className="px-2 py-1 text-[10px] font-medium text-tertiary hover:text-secondary hover:bg-[--bg-hover] rounded transition-colors"
|
|
398
|
+
>
|
|
399
|
+
Cancel
|
|
400
|
+
</button>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
<button
|
|
408
|
+
onClick={handleStartEdit}
|
|
409
|
+
className="px-2.5 py-1.5 text-[11px] font-mono text-tertiary bg-[--bg-tertiary] hover:bg-[--bg-hover] rounded-md w-fit text-left transition-colors cursor-pointer"
|
|
410
|
+
title="Click to edit function"
|
|
411
|
+
>
|
|
412
|
+
{getDisplayValue()}
|
|
413
|
+
</button>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function ColorControl({
|
|
418
|
+
value,
|
|
419
|
+
onChange,
|
|
420
|
+
presetColors,
|
|
421
|
+
}: {
|
|
422
|
+
value: string;
|
|
423
|
+
onChange: (v: string) => void;
|
|
424
|
+
presetColors?: string[];
|
|
425
|
+
}) {
|
|
426
|
+
return (
|
|
427
|
+
<div className="flex items-center gap-2">
|
|
428
|
+
<input
|
|
429
|
+
type="color"
|
|
430
|
+
value={value || "#000000"}
|
|
431
|
+
onChange={(e) => onChange(e.target.value)}
|
|
432
|
+
className="w-8 h-8 rounded border border-[--border] cursor-pointer bg-transparent"
|
|
433
|
+
/>
|
|
434
|
+
<input
|
|
435
|
+
type="text"
|
|
436
|
+
value={value ?? ""}
|
|
437
|
+
onChange={(e) => onChange(e.target.value)}
|
|
438
|
+
placeholder="#000000"
|
|
439
|
+
className="px-2 py-1 text-[11px] font-mono bg-[--bg-primary] border border-[--border] rounded text-primary focus:outline-none focus:ring-1 focus:ring-[--color-accent] w-24"
|
|
440
|
+
/>
|
|
441
|
+
{presetColors && presetColors.length > 0 && (
|
|
442
|
+
<div className="flex gap-1 flex-wrap">
|
|
443
|
+
{presetColors.slice(0, 6).map((color) => (
|
|
444
|
+
<button
|
|
445
|
+
key={color}
|
|
446
|
+
onClick={() => onChange(color)}
|
|
447
|
+
className="w-5 h-5 rounded border border-[--border] hover:ring-2 hover:ring-[--color-accent]"
|
|
448
|
+
style={{ backgroundColor: color }}
|
|
449
|
+
title={color}
|
|
450
|
+
/>
|
|
451
|
+
))}
|
|
452
|
+
</div>
|
|
453
|
+
)}
|
|
454
|
+
</div>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function DateControl({
|
|
459
|
+
value,
|
|
460
|
+
onChange,
|
|
461
|
+
}: {
|
|
462
|
+
value: string;
|
|
463
|
+
onChange: (v: string) => void;
|
|
464
|
+
}) {
|
|
465
|
+
// Convert ISO string to datetime-local format for input
|
|
466
|
+
const inputValue = value
|
|
467
|
+
? new Date(value).toISOString().slice(0, 16)
|
|
468
|
+
: "";
|
|
469
|
+
|
|
470
|
+
return (
|
|
471
|
+
<input
|
|
472
|
+
type="datetime-local"
|
|
473
|
+
value={inputValue}
|
|
474
|
+
onChange={(e) => {
|
|
475
|
+
const date = e.target.value ? new Date(e.target.value).toISOString() : "";
|
|
476
|
+
onChange(date);
|
|
477
|
+
}}
|
|
478
|
+
className="px-2.5 py-1.5 text-[12px] bg-[--bg-primary] border border-[--border] rounded-md text-primary focus:outline-none focus:ring-2 focus:ring-[--color-accent] focus:border-transparent"
|
|
479
|
+
/>
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function RangeControl({
|
|
484
|
+
value,
|
|
485
|
+
onChange,
|
|
486
|
+
min = 0,
|
|
487
|
+
max = 100,
|
|
488
|
+
step = 1,
|
|
489
|
+
}: {
|
|
490
|
+
value: number;
|
|
491
|
+
onChange: (v: number) => void;
|
|
492
|
+
min?: number;
|
|
493
|
+
max?: number;
|
|
494
|
+
step?: number;
|
|
495
|
+
}) {
|
|
496
|
+
return (
|
|
497
|
+
<div className="flex items-center gap-3">
|
|
498
|
+
<input
|
|
499
|
+
type="range"
|
|
500
|
+
value={value ?? min}
|
|
501
|
+
min={min}
|
|
502
|
+
max={max}
|
|
503
|
+
step={step}
|
|
504
|
+
onChange={(e) => onChange(Number(e.target.value))}
|
|
505
|
+
className="flex-1 h-2 bg-[--bg-tertiary] rounded-lg appearance-none cursor-pointer accent-[--color-accent]"
|
|
506
|
+
/>
|
|
507
|
+
<span className="text-[11px] font-mono text-secondary min-w-[3rem] text-right">
|
|
508
|
+
{value ?? min}
|
|
509
|
+
</span>
|
|
510
|
+
</div>
|
|
511
|
+
);
|
|
512
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { PropDefinition } from '../../core/index.js';
|
|
2
|
+
import { WarningIcon } from './Icons.js';
|
|
3
|
+
|
|
4
|
+
interface PropsTableProps {
|
|
5
|
+
props: Record<string, PropDefinition>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function PropsTable({ props }: PropsTableProps) {
|
|
9
|
+
const propEntries = Object.entries(props);
|
|
10
|
+
|
|
11
|
+
if (propEntries.length === 0) return null;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<section id="props" className="scroll-mt-24">
|
|
15
|
+
<h2 className="text-base font-semibold text-primary mb-4">Props</h2>
|
|
16
|
+
<div className="border border-[--border] rounded-xl overflow-hidden">
|
|
17
|
+
<table className="w-full text-[13px]">
|
|
18
|
+
<thead>
|
|
19
|
+
<tr className="bg-[--bg-secondary] border-b border-[--border-subtle]">
|
|
20
|
+
<th className="text-left px-4 py-3 text-[11px] font-medium text-tertiary uppercase tracking-wider">Prop</th>
|
|
21
|
+
<th className="text-left px-4 py-3 text-[11px] font-medium text-tertiary uppercase tracking-wider">Type</th>
|
|
22
|
+
<th className="text-left px-4 py-3 text-[11px] font-medium text-tertiary uppercase tracking-wider">Default</th>
|
|
23
|
+
<th className="text-left px-4 py-3 text-[11px] font-medium text-tertiary uppercase tracking-wider">Description</th>
|
|
24
|
+
</tr>
|
|
25
|
+
</thead>
|
|
26
|
+
<tbody>
|
|
27
|
+
{propEntries.map(([name, prop], index) => (
|
|
28
|
+
<tr
|
|
29
|
+
key={name}
|
|
30
|
+
className={`hover:bg-[--bg-hover] transition-colors ${
|
|
31
|
+
index !== propEntries.length - 1 ? 'border-b border-[--border-subtle]' : ''
|
|
32
|
+
}`}
|
|
33
|
+
>
|
|
34
|
+
<td className="px-4 py-3 align-top">
|
|
35
|
+
<code className="text-[13px] font-mono text-primary font-medium">
|
|
36
|
+
{name}
|
|
37
|
+
{prop.required && <span className="text-[--color-danger] ml-0.5">*</span>}
|
|
38
|
+
</code>
|
|
39
|
+
</td>
|
|
40
|
+
<td className="px-4 py-3 align-top">
|
|
41
|
+
<PropType type={prop.type} values={prop.values} />
|
|
42
|
+
</td>
|
|
43
|
+
<td className="px-4 py-3 align-top">
|
|
44
|
+
{prop.default !== undefined ? (
|
|
45
|
+
<code className="text-[12px] font-mono bg-[--bg-tertiary] px-1.5 py-0.5 rounded text-secondary">
|
|
46
|
+
{String(prop.default)}
|
|
47
|
+
</code>
|
|
48
|
+
) : (
|
|
49
|
+
<span className="text-[--text-muted]">—</span>
|
|
50
|
+
)}
|
|
51
|
+
</td>
|
|
52
|
+
<td className="px-4 py-3 align-top">
|
|
53
|
+
<div className="text-secondary leading-relaxed">{prop.description}</div>
|
|
54
|
+
{prop.constraints && prop.constraints.length > 0 && (
|
|
55
|
+
<div className="mt-2 space-y-1">
|
|
56
|
+
{prop.constraints.map((constraint, index) => (
|
|
57
|
+
<div
|
|
58
|
+
key={index}
|
|
59
|
+
className="text-[12px] text-[--color-warning] flex items-start gap-1.5"
|
|
60
|
+
>
|
|
61
|
+
<WarningIcon className="w-3 h-3 mt-0.5 flex-shrink-0" />
|
|
62
|
+
<span>{constraint}</span>
|
|
63
|
+
</div>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</td>
|
|
68
|
+
</tr>
|
|
69
|
+
))}
|
|
70
|
+
</tbody>
|
|
71
|
+
</table>
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function PropType({ type, values }: { type: string; values?: string[] }) {
|
|
78
|
+
if (type === 'enum' && values && values.length > 0) {
|
|
79
|
+
return (
|
|
80
|
+
<div className="flex flex-wrap gap-1">
|
|
81
|
+
{values.map((value, index) => (
|
|
82
|
+
<span key={value} className="inline-flex items-center">
|
|
83
|
+
<code className="text-[12px] font-mono bg-[--bg-tertiary] px-1.5 py-0.5 rounded text-[--color-accent]">
|
|
84
|
+
{value}
|
|
85
|
+
</code>
|
|
86
|
+
{index < values.length - 1 && <span className="text-[--text-muted] mx-1">|</span>}
|
|
87
|
+
</span>
|
|
88
|
+
))}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<code className="text-[12px] font-mono text-secondary">
|
|
95
|
+
{type}
|
|
96
|
+
</code>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ComponentRelation } from '../../core/index.js';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { getRelationshipConfig } from '../constants/ui.js';
|
|
4
|
+
import { ChevronRightIcon } from './Icons.js';
|
|
5
|
+
|
|
6
|
+
interface RelationsSectionProps {
|
|
7
|
+
relations: ComponentRelation[];
|
|
8
|
+
onNavigate?: (componentName: string) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function RelationsSection({ relations, onNavigate }: RelationsSectionProps) {
|
|
12
|
+
if (relations.length === 0) return null;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<section id="relations" className="scroll-mt-24">
|
|
16
|
+
<h2 className="text-base font-semibold text-primary mb-4">Related Components</h2>
|
|
17
|
+
<div className="grid sm:grid-cols-2 gap-3">
|
|
18
|
+
{relations.map((relation, index) => {
|
|
19
|
+
const config = getRelationshipConfig(relation.relationship);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<button
|
|
23
|
+
key={index}
|
|
24
|
+
onClick={() => onNavigate?.(relation.component)}
|
|
25
|
+
className={clsx(
|
|
26
|
+
'group text-left p-4 rounded-xl border border-[--border]',
|
|
27
|
+
'bg-[--bg-elevated] hover:border-[--border-strong]',
|
|
28
|
+
'transition-all duration-150',
|
|
29
|
+
'focus:outline-none focus-visible:ring-2 focus-visible:ring-[--color-accent]'
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
33
|
+
<span className="text-[13px] font-medium text-primary group-hover:text-[--color-accent] transition-colors">
|
|
34
|
+
{relation.component}
|
|
35
|
+
</span>
|
|
36
|
+
<span className={clsx(
|
|
37
|
+
'text-[10px] font-medium px-1.5 py-0.5 rounded',
|
|
38
|
+
config.bg,
|
|
39
|
+
config.text
|
|
40
|
+
)}>
|
|
41
|
+
{config.label}
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
{relation.note && (
|
|
45
|
+
<p className="text-[13px] text-secondary leading-relaxed">{relation.note}</p>
|
|
46
|
+
)}
|
|
47
|
+
<div className="mt-2 flex items-center text-[12px] text-tertiary group-hover:text-secondary transition-colors">
|
|
48
|
+
<span>View component</span>
|
|
49
|
+
<ChevronRightIcon className="w-3 h-3 ml-1 group-hover:translate-x-0.5 transition-transform" />
|
|
50
|
+
</div>
|
|
51
|
+
</button>
|
|
52
|
+
);
|
|
53
|
+
})}
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
);
|
|
57
|
+
}
|