@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import type { PropDefinition } from "../../core/index.js";
|
|
3
|
-
import
|
|
3
|
+
import { Input, Select, Toggle } from "@fragments/ui";
|
|
4
4
|
import { ControlsIcon, ChevronDownIcon, RefreshIcon } from "./Icons.js";
|
|
5
5
|
|
|
6
6
|
interface PropsEditorProps {
|
|
@@ -28,7 +28,7 @@ export function PropsEditor({
|
|
|
28
28
|
// Compact mode - show controls in a single horizontal line
|
|
29
29
|
if (compact) {
|
|
30
30
|
return (
|
|
31
|
-
<div
|
|
31
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
|
32
32
|
{propEntries.map(([name, prop]) => (
|
|
33
33
|
<PropControl
|
|
34
34
|
key={name}
|
|
@@ -42,9 +42,22 @@ export function PropsEditor({
|
|
|
42
42
|
{hasChanges && (
|
|
43
43
|
<button
|
|
44
44
|
onClick={onReset}
|
|
45
|
-
|
|
45
|
+
style={{
|
|
46
|
+
display: 'flex',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
gap: '6px',
|
|
49
|
+
padding: '4px 8px',
|
|
50
|
+
fontSize: '12px',
|
|
51
|
+
fontWeight: 500,
|
|
52
|
+
color: 'var(--color-accent)',
|
|
53
|
+
background: 'transparent',
|
|
54
|
+
border: 'none',
|
|
55
|
+
borderRadius: '4px',
|
|
56
|
+
cursor: 'pointer',
|
|
57
|
+
transition: 'background 0.15s',
|
|
58
|
+
}}
|
|
46
59
|
>
|
|
47
|
-
<RefreshIcon
|
|
60
|
+
<RefreshIcon style={{ width: 12, height: 12 }} />
|
|
48
61
|
Reset
|
|
49
62
|
</button>
|
|
50
63
|
)}
|
|
@@ -53,47 +66,75 @@ export function PropsEditor({
|
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
return (
|
|
56
|
-
<div
|
|
69
|
+
<div style={{ border: '1px solid var(--border)', borderRadius: '12px', overflow: 'hidden', background: 'var(--bg-elevated)' }}>
|
|
57
70
|
{/* Header */}
|
|
58
71
|
<button
|
|
59
72
|
onClick={() => setIsCollapsed(!isCollapsed)}
|
|
60
|
-
|
|
73
|
+
style={{
|
|
74
|
+
width: '100%',
|
|
75
|
+
padding: '12px 16px',
|
|
76
|
+
display: 'flex',
|
|
77
|
+
alignItems: 'center',
|
|
78
|
+
justifyContent: 'space-between',
|
|
79
|
+
background: 'var(--bg-secondary)',
|
|
80
|
+
borderBottom: '1px solid var(--border)',
|
|
81
|
+
cursor: 'pointer',
|
|
82
|
+
transition: 'background 0.15s',
|
|
83
|
+
border: 'none',
|
|
84
|
+
color: 'inherit',
|
|
85
|
+
}}
|
|
61
86
|
>
|
|
62
|
-
<div
|
|
63
|
-
<ControlsIcon
|
|
64
|
-
<span
|
|
87
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
88
|
+
<ControlsIcon style={{ width: 16, height: 16, color: 'var(--text-secondary)' }} />
|
|
89
|
+
<span style={{ fontSize: '13px', fontWeight: 500, color: 'var(--text-primary)' }}>
|
|
65
90
|
Props Editor
|
|
66
91
|
</span>
|
|
67
92
|
{hasChanges && (
|
|
68
|
-
<span
|
|
93
|
+
<span style={{ padding: '2px 6px', fontSize: '10px', fontWeight: 500, background: 'var(--color-accent)', color: '#fff', borderRadius: '4px' }}>
|
|
69
94
|
Modified
|
|
70
95
|
</span>
|
|
71
96
|
)}
|
|
72
97
|
</div>
|
|
73
98
|
<ChevronDownIcon
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
99
|
+
style={{
|
|
100
|
+
width: 16,
|
|
101
|
+
height: 16,
|
|
102
|
+
color: 'var(--text-tertiary)',
|
|
103
|
+
transition: 'transform 0.15s',
|
|
104
|
+
transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)',
|
|
105
|
+
}}
|
|
78
106
|
/>
|
|
79
107
|
</button>
|
|
80
108
|
|
|
81
109
|
{/* Content */}
|
|
82
110
|
{!isCollapsed && (
|
|
83
|
-
<div
|
|
111
|
+
<div style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
84
112
|
{/* Reset button */}
|
|
85
113
|
{hasChanges && (
|
|
86
114
|
<button
|
|
87
115
|
onClick={onReset}
|
|
88
|
-
|
|
116
|
+
style={{
|
|
117
|
+
display: 'flex',
|
|
118
|
+
alignItems: 'center',
|
|
119
|
+
gap: '6px',
|
|
120
|
+
padding: '6px 10px',
|
|
121
|
+
fontSize: '12px',
|
|
122
|
+
fontWeight: 500,
|
|
123
|
+
color: 'var(--color-accent)',
|
|
124
|
+
background: 'transparent',
|
|
125
|
+
border: 'none',
|
|
126
|
+
borderRadius: '4px',
|
|
127
|
+
cursor: 'pointer',
|
|
128
|
+
transition: 'background 0.15s',
|
|
129
|
+
}}
|
|
89
130
|
>
|
|
90
|
-
<RefreshIcon
|
|
131
|
+
<RefreshIcon style={{ width: 14, height: 14 }} />
|
|
91
132
|
Reset to defaults
|
|
92
133
|
</button>
|
|
93
134
|
)}
|
|
94
135
|
|
|
95
136
|
{/* Prop controls */}
|
|
96
|
-
<div
|
|
137
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
97
138
|
{propEntries.map(([name, prop]) => (
|
|
98
139
|
<PropControl
|
|
99
140
|
key={name}
|
|
@@ -195,8 +236,8 @@ function PropControl({
|
|
|
195
236
|
|
|
196
237
|
if (compact) {
|
|
197
238
|
return (
|
|
198
|
-
<div
|
|
199
|
-
<label
|
|
239
|
+
<div style={{ display: 'flex', gap: '8px' }}>
|
|
240
|
+
<label style={{ fontSize: '11px', fontWeight: 500, color: 'var(--text-secondary)', fontFamily: 'monospace', whiteSpace: 'nowrap', minWidth: '100px' }}>
|
|
200
241
|
{name}
|
|
201
242
|
</label>
|
|
202
243
|
{getControlForProp(prop, displayValue, onChange, true)}
|
|
@@ -205,17 +246,17 @@ function PropControl({
|
|
|
205
246
|
}
|
|
206
247
|
|
|
207
248
|
return (
|
|
208
|
-
<div
|
|
209
|
-
<div
|
|
210
|
-
<label
|
|
249
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
|
250
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
251
|
+
<label style={{ fontSize: '12px', fontWeight: 500, color: 'var(--text-primary)', fontFamily: 'monospace' }}>
|
|
211
252
|
{name}
|
|
212
253
|
{prop.required && (
|
|
213
|
-
<span
|
|
254
|
+
<span style={{ color: 'var(--color-danger, #ef4444)', marginLeft: '2px' }}>*</span>
|
|
214
255
|
)}
|
|
215
256
|
</label>
|
|
216
|
-
<span
|
|
257
|
+
<span style={{ fontSize: '11px', color: 'var(--text-tertiary)' }}>{prop.type}</span>
|
|
217
258
|
{prop.controlType && prop.controlType !== prop.type && (
|
|
218
|
-
<span
|
|
259
|
+
<span style={{ fontSize: '10px', color: 'var(--text-tertiary)', background: 'var(--bg-secondary)', padding: '2px 6px', borderRadius: '4px' }}>
|
|
219
260
|
{prop.controlType}
|
|
220
261
|
</span>
|
|
221
262
|
)}
|
|
@@ -224,7 +265,7 @@ function PropControl({
|
|
|
224
265
|
{getControlForProp(prop, displayValue, onChange, false)}
|
|
225
266
|
|
|
226
267
|
{prop.description && (
|
|
227
|
-
<p
|
|
268
|
+
<p style={{ fontSize: '11px', color: 'var(--text-tertiary)', lineHeight: 1.6 }}>
|
|
228
269
|
{prop.description}
|
|
229
270
|
</p>
|
|
230
271
|
)}
|
|
@@ -240,20 +281,10 @@ function BooleanControl({
|
|
|
240
281
|
onChange: (v: boolean) => void;
|
|
241
282
|
}) {
|
|
242
283
|
return (
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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>
|
|
284
|
+
<Toggle
|
|
285
|
+
checked={value}
|
|
286
|
+
onChange={() => onChange(!value)}
|
|
287
|
+
/>
|
|
257
288
|
);
|
|
258
289
|
}
|
|
259
290
|
|
|
@@ -267,17 +298,16 @@ function EnumControl({
|
|
|
267
298
|
onChange: (v: string) => void;
|
|
268
299
|
}) {
|
|
269
300
|
return (
|
|
270
|
-
<
|
|
301
|
+
<Select
|
|
271
302
|
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"
|
|
303
|
+
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => onChange(e.target.value)}
|
|
274
304
|
>
|
|
275
305
|
{values.map((v) => (
|
|
276
306
|
<option key={v} value={v}>
|
|
277
307
|
{v}
|
|
278
308
|
</option>
|
|
279
309
|
))}
|
|
280
|
-
</
|
|
310
|
+
</Select>
|
|
281
311
|
);
|
|
282
312
|
}
|
|
283
313
|
|
|
@@ -289,17 +319,17 @@ function NumberControl({
|
|
|
289
319
|
onChange: (v: number) => void;
|
|
290
320
|
}) {
|
|
291
321
|
return (
|
|
292
|
-
<
|
|
322
|
+
<Input
|
|
293
323
|
type="number"
|
|
294
324
|
value={value ?? ""}
|
|
295
|
-
onChange={(e) =>
|
|
325
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
296
326
|
onChange(
|
|
297
327
|
e.target.value
|
|
298
328
|
? Number(e.target.value)
|
|
299
329
|
: (undefined as unknown as number)
|
|
300
330
|
)
|
|
301
331
|
}
|
|
302
|
-
|
|
332
|
+
style={{ width: '128px', fontFamily: 'monospace' }}
|
|
303
333
|
/>
|
|
304
334
|
);
|
|
305
335
|
}
|
|
@@ -314,14 +344,14 @@ function StringControl({
|
|
|
314
344
|
compact?: boolean;
|
|
315
345
|
}) {
|
|
316
346
|
return (
|
|
317
|
-
<
|
|
347
|
+
<Input
|
|
318
348
|
type="text"
|
|
319
349
|
value={value ?? ""}
|
|
320
|
-
onChange={(e) => onChange(e.target.value)}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
350
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
|
|
351
|
+
style={compact
|
|
352
|
+
? { width: '96px', fontSize: '11px', fontFamily: 'monospace' }
|
|
353
|
+
: { width: '100%', maxWidth: '320px', fontFamily: 'monospace' }
|
|
354
|
+
}
|
|
325
355
|
/>
|
|
326
356
|
);
|
|
327
357
|
}
|
|
@@ -377,24 +407,56 @@ function FunctionControl({
|
|
|
377
407
|
|
|
378
408
|
if (isEditing) {
|
|
379
409
|
return (
|
|
380
|
-
<div
|
|
410
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', flex: 1 }}>
|
|
381
411
|
<textarea
|
|
382
412
|
value={inputValue}
|
|
383
413
|
onChange={(e) => setInputValue(e.target.value)}
|
|
384
|
-
|
|
414
|
+
style={{
|
|
415
|
+
width: '100%',
|
|
416
|
+
padding: '6px 8px',
|
|
417
|
+
fontSize: '11px',
|
|
418
|
+
fontFamily: 'monospace',
|
|
419
|
+
background: 'var(--bg-primary)',
|
|
420
|
+
border: '1px solid var(--border)',
|
|
421
|
+
borderRadius: '4px',
|
|
422
|
+
color: 'var(--text-primary)',
|
|
423
|
+
outline: 'none',
|
|
424
|
+
resize: 'vertical',
|
|
425
|
+
minHeight: '60px',
|
|
426
|
+
}}
|
|
385
427
|
placeholder="() => console.log('clicked')"
|
|
386
428
|
autoFocus
|
|
387
429
|
/>
|
|
388
|
-
<div
|
|
430
|
+
<div style={{ display: 'flex', gap: '6px' }}>
|
|
389
431
|
<button
|
|
390
432
|
onClick={handleSave}
|
|
391
|
-
|
|
433
|
+
style={{
|
|
434
|
+
padding: '4px 8px',
|
|
435
|
+
fontSize: '10px',
|
|
436
|
+
fontWeight: 500,
|
|
437
|
+
background: 'var(--color-accent)',
|
|
438
|
+
color: '#fff',
|
|
439
|
+
borderRadius: '4px',
|
|
440
|
+
border: 'none',
|
|
441
|
+
cursor: 'pointer',
|
|
442
|
+
transition: 'opacity 0.15s',
|
|
443
|
+
}}
|
|
392
444
|
>
|
|
393
445
|
Apply
|
|
394
446
|
</button>
|
|
395
447
|
<button
|
|
396
448
|
onClick={handleCancel}
|
|
397
|
-
|
|
449
|
+
style={{
|
|
450
|
+
padding: '4px 8px',
|
|
451
|
+
fontSize: '10px',
|
|
452
|
+
fontWeight: 500,
|
|
453
|
+
color: 'var(--text-tertiary)',
|
|
454
|
+
background: 'transparent',
|
|
455
|
+
borderRadius: '4px',
|
|
456
|
+
border: 'none',
|
|
457
|
+
cursor: 'pointer',
|
|
458
|
+
transition: 'color 0.15s, background 0.15s',
|
|
459
|
+
}}
|
|
398
460
|
>
|
|
399
461
|
Cancel
|
|
400
462
|
</button>
|
|
@@ -406,7 +468,19 @@ function FunctionControl({
|
|
|
406
468
|
return (
|
|
407
469
|
<button
|
|
408
470
|
onClick={handleStartEdit}
|
|
409
|
-
|
|
471
|
+
style={{
|
|
472
|
+
padding: '6px 10px',
|
|
473
|
+
fontSize: '11px',
|
|
474
|
+
fontFamily: 'monospace',
|
|
475
|
+
color: 'var(--text-tertiary)',
|
|
476
|
+
background: 'var(--bg-secondary)',
|
|
477
|
+
borderRadius: '6px',
|
|
478
|
+
width: 'fit-content',
|
|
479
|
+
textAlign: 'left',
|
|
480
|
+
transition: 'background 0.15s',
|
|
481
|
+
cursor: 'pointer',
|
|
482
|
+
border: 'none',
|
|
483
|
+
}}
|
|
410
484
|
title="Click to edit function"
|
|
411
485
|
>
|
|
412
486
|
{getDisplayValue()}
|
|
@@ -424,28 +498,35 @@ function ColorControl({
|
|
|
424
498
|
presetColors?: string[];
|
|
425
499
|
}) {
|
|
426
500
|
return (
|
|
427
|
-
<div
|
|
501
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
428
502
|
<input
|
|
429
503
|
type="color"
|
|
430
504
|
value={value || "#000000"}
|
|
431
505
|
onChange={(e) => onChange(e.target.value)}
|
|
432
|
-
|
|
506
|
+
style={{ width: '32px', height: '32px', borderRadius: '4px', border: '1px solid var(--border)', cursor: 'pointer', background: 'transparent' }}
|
|
433
507
|
/>
|
|
434
|
-
<
|
|
508
|
+
<Input
|
|
435
509
|
type="text"
|
|
436
510
|
value={value ?? ""}
|
|
437
|
-
onChange={(e) => onChange(e.target.value)}
|
|
511
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value)}
|
|
438
512
|
placeholder="#000000"
|
|
439
|
-
|
|
513
|
+
style={{ width: '96px', fontSize: '11px', fontFamily: 'monospace' }}
|
|
440
514
|
/>
|
|
441
515
|
{presetColors && presetColors.length > 0 && (
|
|
442
|
-
<div
|
|
516
|
+
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
|
443
517
|
{presetColors.slice(0, 6).map((color) => (
|
|
444
518
|
<button
|
|
445
519
|
key={color}
|
|
446
520
|
onClick={() => onChange(color)}
|
|
447
|
-
|
|
448
|
-
|
|
521
|
+
style={{
|
|
522
|
+
width: '20px',
|
|
523
|
+
height: '20px',
|
|
524
|
+
borderRadius: '4px',
|
|
525
|
+
border: '1px solid var(--border)',
|
|
526
|
+
backgroundColor: color,
|
|
527
|
+
cursor: 'pointer',
|
|
528
|
+
padding: 0,
|
|
529
|
+
}}
|
|
449
530
|
title={color}
|
|
450
531
|
/>
|
|
451
532
|
))}
|
|
@@ -468,14 +549,13 @@ function DateControl({
|
|
|
468
549
|
: "";
|
|
469
550
|
|
|
470
551
|
return (
|
|
471
|
-
<
|
|
552
|
+
<Input
|
|
472
553
|
type="datetime-local"
|
|
473
554
|
value={inputValue}
|
|
474
|
-
onChange={(e) => {
|
|
555
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
475
556
|
const date = e.target.value ? new Date(e.target.value).toISOString() : "";
|
|
476
557
|
onChange(date);
|
|
477
558
|
}}
|
|
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
559
|
/>
|
|
480
560
|
);
|
|
481
561
|
}
|
|
@@ -494,7 +574,7 @@ function RangeControl({
|
|
|
494
574
|
step?: number;
|
|
495
575
|
}) {
|
|
496
576
|
return (
|
|
497
|
-
<div
|
|
577
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
498
578
|
<input
|
|
499
579
|
type="range"
|
|
500
580
|
value={value ?? min}
|
|
@@ -502,9 +582,9 @@ function RangeControl({
|
|
|
502
582
|
max={max}
|
|
503
583
|
step={step}
|
|
504
584
|
onChange={(e) => onChange(Number(e.target.value))}
|
|
505
|
-
|
|
585
|
+
style={{ flex: 1, height: '8px', background: 'var(--bg-secondary)', borderRadius: '8px', cursor: 'pointer', accentColor: 'var(--color-accent)' }}
|
|
506
586
|
/>
|
|
507
|
-
<span
|
|
587
|
+
<span style={{ fontSize: '11px', fontFamily: 'monospace', color: 'var(--text-secondary)', minWidth: '3rem', textAlign: 'right' }}>
|
|
508
588
|
{value ?? min}
|
|
509
589
|
</span>
|
|
510
590
|
</div>
|
|
@@ -1,98 +1,111 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
1
2
|
import type { PropDefinition } from '../../core/index.js';
|
|
3
|
+
import { Table, createColumns, Badge, Text, Stack } from '@fragments/ui';
|
|
2
4
|
import { WarningIcon } from './Icons.js';
|
|
3
5
|
|
|
4
6
|
interface PropsTableProps {
|
|
5
7
|
props: Record<string, PropDefinition>;
|
|
6
8
|
}
|
|
7
9
|
|
|
10
|
+
interface PropRow {
|
|
11
|
+
name: string;
|
|
12
|
+
required: boolean;
|
|
13
|
+
type: string;
|
|
14
|
+
values?: string[];
|
|
15
|
+
default: unknown;
|
|
16
|
+
description: string;
|
|
17
|
+
constraints?: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
export function PropsTable({ props }: PropsTableProps) {
|
|
9
21
|
const propEntries = Object.entries(props);
|
|
10
22
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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>
|
|
23
|
+
const data = useMemo<PropRow[]>(() =>
|
|
24
|
+
propEntries.map(([name, prop]) => ({
|
|
25
|
+
name,
|
|
26
|
+
required: !!prop.required,
|
|
27
|
+
type: prop.type,
|
|
28
|
+
values: prop.values,
|
|
29
|
+
default: prop.default,
|
|
30
|
+
description: prop.description,
|
|
31
|
+
constraints: prop.constraints,
|
|
32
|
+
})),
|
|
33
|
+
[propEntries]
|
|
74
34
|
);
|
|
75
|
-
}
|
|
76
35
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
36
|
+
const columns = useMemo(() => createColumns<PropRow>([
|
|
37
|
+
{
|
|
38
|
+
key: 'name',
|
|
39
|
+
header: 'Prop',
|
|
40
|
+
cell: (row) => (
|
|
41
|
+
<Text font="mono" size="sm" weight="medium">
|
|
42
|
+
{row.name}
|
|
43
|
+
{row.required && <span style={{ color: 'var(--color-danger, #ef4444)', marginLeft: '2px' }}>*</span>}
|
|
44
|
+
</Text>
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: 'type',
|
|
49
|
+
header: 'Type',
|
|
50
|
+
cell: (row) => {
|
|
51
|
+
if (row.type === 'enum' && row.values && row.values.length > 0) {
|
|
52
|
+
return (
|
|
53
|
+
<Stack direction="row" gap="xs" style={{ flexWrap: 'wrap' }}>
|
|
54
|
+
{row.values.map((value, index) => (
|
|
55
|
+
<span key={value} style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
56
|
+
<Badge size="sm">{value}</Badge>
|
|
57
|
+
{index < row.values!.length - 1 && <Text size="xs" color="tertiary" style={{ margin: '0 4px' }}>|</Text>}
|
|
58
|
+
</span>
|
|
59
|
+
))}
|
|
60
|
+
</Stack>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return <Text font="mono" size="xs" color="secondary">{row.type}</Text>;
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: 'default',
|
|
68
|
+
header: 'Default',
|
|
69
|
+
cell: (row) => {
|
|
70
|
+
if (row.default !== undefined) {
|
|
71
|
+
return <Text font="mono" size="xs" color="secondary">{String(row.default)}</Text>;
|
|
72
|
+
}
|
|
73
|
+
return <Text color="tertiary">—</Text>;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: 'description',
|
|
78
|
+
header: 'Description',
|
|
79
|
+
cell: (row) => (
|
|
80
|
+
<Stack gap="xs">
|
|
81
|
+
<Text size="sm" color="secondary">{row.description}</Text>
|
|
82
|
+
{row.constraints && row.constraints.length > 0 && (
|
|
83
|
+
<Stack gap="xs">
|
|
84
|
+
{row.constraints.map((constraint, index) => (
|
|
85
|
+
<Stack key={index} direction="row" gap="xs" align="start" style={{ fontSize: '12px', color: 'var(--color-warning, #f59e0b)' }}>
|
|
86
|
+
<WarningIcon style={{ width: 12, height: 12, marginTop: '2px', flexShrink: 0 }} />
|
|
87
|
+
<span>{constraint}</span>
|
|
88
|
+
</Stack>
|
|
89
|
+
))}
|
|
90
|
+
</Stack>
|
|
91
|
+
)}
|
|
92
|
+
</Stack>
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
]), []);
|
|
96
|
+
|
|
97
|
+
if (propEntries.length === 0) return null;
|
|
92
98
|
|
|
93
99
|
return (
|
|
94
|
-
<
|
|
95
|
-
{
|
|
96
|
-
|
|
100
|
+
<section id="props" style={{ scrollMarginTop: '96px' }}>
|
|
101
|
+
<Text as="h2" size="md" weight="semibold" style={{ marginBottom: '16px' }}>Props</Text>
|
|
102
|
+
<Table
|
|
103
|
+
columns={columns}
|
|
104
|
+
data={data}
|
|
105
|
+
size="sm"
|
|
106
|
+
bordered
|
|
107
|
+
getRowId={(row) => row.name}
|
|
108
|
+
/>
|
|
109
|
+
</section>
|
|
97
110
|
);
|
|
98
111
|
}
|