@fragments-sdk/cli 0.8.1 → 0.9.1
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 +517 -77
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-WI6SLMSO.js → chunk-5GT62FCB.js} +2 -2
- package/dist/{chunk-CJEGT3WD.js → chunk-BW3ZATBW.js} +20 -3
- package/dist/chunk-BW3ZATBW.js.map +1 -0
- package/dist/{chunk-2JIKCJX3.js → chunk-D7372LQX.js} +13 -6
- package/dist/chunk-D7372LQX.js.map +1 -0
- package/dist/chunk-EZYXYWNF.js +131 -0
- package/dist/chunk-EZYXYWNF.js.map +1 -0
- package/dist/{chunk-NGIMCIK2.js → chunk-GF6OVPIN.js} +2 -2
- package/dist/{chunk-GOVI6COW.js → chunk-NVSPGSKB.js} +12 -4
- package/dist/chunk-NVSPGSKB.js.map +1 -0
- package/dist/core/index.d.ts +105 -3
- package/dist/core/index.js +12 -2
- package/dist/{defineFragment-D0UTve-I.d.ts → defineFragment-CBMS7Bab.d.ts} +21 -1
- package/dist/generate-LQA2R7FN.js +461 -0
- package/dist/generate-LQA2R7FN.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/{init-KFYN37ZY.js → init-2GEGVIUQ.js} +14 -76
- package/dist/init-2GEGVIUQ.js.map +1 -0
- package/dist/mcp-bin.js +4 -3
- package/dist/mcp-bin.js.map +1 -1
- package/dist/{scan-65RH3QMM.js → scan-JGS65S7P.js} +6 -5
- package/dist/{service-A5GIGGGK.js → service-XP2EAJXD.js} +4 -3
- package/dist/{static-viewer-NSODM5VX.js → static-viewer-XCS7UJTO.js} +4 -3
- package/dist/storyFilters-3LUYAFZF.js +15 -0
- package/dist/storyFilters-3LUYAFZF.js.map +1 -0
- package/dist/{test-RPWZAYSJ.js → test-TD6TJNVY.js} +3 -3
- package/dist/{tokens-NIXSZRX7.js → tokens-2EXPCVP3.js} +5 -4
- package/dist/{tokens-NIXSZRX7.js.map → tokens-2EXPCVP3.js.map} +1 -1
- package/dist/{viewer-HZK4BSDK.js → viewer-RFA2KVBG.js} +249 -22
- package/dist/viewer-RFA2KVBG.js.map +1 -0
- package/package.json +2 -2
- package/src/bin.ts +26 -0
- package/src/build.ts +12 -2
- package/src/commands/build.ts +16 -2
- package/src/commands/doctor.ts +498 -0
- package/src/commands/generate.ts +383 -68
- package/src/commands/init-framework.ts +1 -1
- package/src/commands/init.ts +9 -51
- package/src/core/config.ts +15 -2
- package/src/core/generators/typescript-extractor.ts +10 -0
- package/src/core/index.ts +15 -0
- package/src/core/schema.ts +10 -2
- package/src/core/storyFilters.test.ts +350 -0
- package/src/core/storyFilters.ts +253 -0
- package/src/core/types.ts +22 -0
- package/src/migrate/converter.ts +9 -1
- package/src/migrate/parser.ts +2 -0
- package/src/migrate/types.ts +2 -0
- package/src/setup.ts +69 -24
- package/src/viewer/__tests__/viewer-integration.test.ts +1 -1
- package/src/viewer/components/AccessibilityPanel.tsx +305 -312
- package/src/viewer/components/ActionsPanel.tsx +31 -29
- package/src/viewer/components/AllVariantsPreview.tsx +78 -0
- package/src/viewer/components/App.tsx +187 -740
- package/src/viewer/components/BottomPanel.tsx +228 -132
- package/src/viewer/components/CodePanel.tsx +1 -1
- package/src/viewer/components/CommandPalette.tsx +7 -10
- package/src/viewer/components/ComponentDocView.tsx +164 -0
- package/src/viewer/components/ComponentGraph.tsx +111 -142
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
- package/src/viewer/components/FigmaEmbed.tsx +20 -18
- package/src/viewer/components/FragmentEditor.tsx +92 -115
- package/src/viewer/components/HeaderSearch.tsx +24 -0
- package/src/viewer/components/HealthDashboard.tsx +16 -2
- package/src/viewer/components/Icons.tsx +9 -0
- package/src/viewer/components/InteractionsPanel.tsx +101 -117
- package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
- package/src/viewer/components/LandingPage.tsx +3 -3
- package/src/viewer/components/LeftSidebar.tsx +141 -63
- package/src/viewer/components/LoadErrorMessage.tsx +102 -0
- package/src/viewer/components/MultiViewportPreview.tsx +61 -142
- package/src/viewer/components/NoVariantsMessage.tsx +59 -0
- package/src/viewer/components/PanelShell.tsx +161 -0
- package/src/viewer/components/PerformancePanel.tsx +31 -28
- package/src/viewer/components/PreviewArea.tsx +1 -1
- package/src/viewer/components/PreviewAside.tsx +168 -0
- package/src/viewer/components/PreviewFrameHost.tsx +3 -3
- package/src/viewer/components/PropsEditor.tsx +70 -156
- package/src/viewer/components/ResizablePanel.tsx +103 -263
- package/src/viewer/components/RightSidebar.tsx +3 -9
- package/src/viewer/components/SkeletonLoader.tsx +13 -13
- package/src/viewer/components/TokenStylePanel.tsx +182 -209
- package/src/viewer/components/TopToolbar.tsx +159 -0
- package/src/viewer/components/VariantMatrix.tsx +42 -86
- package/src/viewer/components/VariantTabs.tsx +3 -3
- package/src/viewer/components/ViewerHeader.tsx +69 -0
- package/src/viewer/components/WebMCPDevTools.tsx +17 -23
- package/src/viewer/components/viewer-utils.ts +16 -0
- package/src/viewer/entry.tsx +5 -0
- package/src/viewer/hooks/useAppState.ts +27 -4
- package/src/viewer/hooks/usePreviewBridge.ts +2 -2
- package/src/viewer/preview-frame.html +6 -12
- package/src/viewer/server.ts +184 -6
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
- package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
- package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
- package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
- package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +122 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
- package/src/viewer/vendor/shared/src/docs-data/index.ts +32 -0
- package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +72 -0
- package/src/viewer/vendor/shared/src/docs-data/palettes.ts +75 -0
- package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +55 -0
- package/src/viewer/vendor/shared/src/index.ts +8 -0
- package/src/viewer/vendor/shared/src/types.ts +12 -0
- package/src/viewer/vite-plugin.ts +109 -4
- package/dist/chunk-2JIKCJX3.js.map +0 -1
- package/dist/chunk-CJEGT3WD.js.map +0 -1
- package/dist/chunk-GOVI6COW.js.map +0 -1
- package/dist/generate-35OIMW4Y.js +0 -252
- package/dist/generate-35OIMW4Y.js.map +0 -1
- package/dist/init-KFYN37ZY.js.map +0 -1
- package/dist/viewer-HZK4BSDK.js.map +0 -1
- /package/dist/{chunk-WI6SLMSO.js.map → chunk-5GT62FCB.js.map} +0 -0
- /package/dist/{chunk-NGIMCIK2.js.map → chunk-GF6OVPIN.js.map} +0 -0
- /package/dist/{scan-65RH3QMM.js.map → scan-JGS65S7P.js.map} +0 -0
- /package/dist/{service-A5GIGGGK.js.map → service-XP2EAJXD.js.map} +0 -0
- /package/dist/{static-viewer-NSODM5VX.js.map → static-viewer-XCS7UJTO.js.map} +0 -0
- /package/dist/{test-RPWZAYSJ.js.map → test-TD6TJNVY.js.map} +0 -0
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { useState, useEffect } from 'react';
|
|
14
|
-
import { Card, Badge, Text, Stack,
|
|
14
|
+
import { Card, Badge, Text, Stack, Alert, Collapsible, Box } from '@fragments-sdk/ui';
|
|
15
|
+
import { Lightning, Package, FileCode, Star } from '@phosphor-icons/react';
|
|
15
16
|
import { BRAND } from '../../core/index.js';
|
|
17
|
+
import { PanelShell } from './PanelShell.js';
|
|
16
18
|
|
|
17
19
|
interface ImportEntry {
|
|
18
20
|
path: string;
|
|
@@ -211,32 +213,24 @@ export function PerformancePanel({ componentName }: PerformancePanelProps) {
|
|
|
211
213
|
return () => { cancelled = true; };
|
|
212
214
|
}, [componentName]);
|
|
213
215
|
|
|
214
|
-
|
|
215
|
-
return (
|
|
216
|
-
<div style={{ padding: '16px' }}>
|
|
217
|
-
<Text size="sm" color="secondary">Loading performance data...</Text>
|
|
218
|
-
</div>
|
|
219
|
-
);
|
|
220
|
-
}
|
|
216
|
+
const showEmpty = !loading && (noData || !perfData);
|
|
221
217
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
</div>
|
|
232
|
-
);
|
|
233
|
-
}
|
|
218
|
+
const emptyConfig = showEmpty ? {
|
|
219
|
+
icon: <Lightning size={24} weight="regular" style={{ color: 'var(--text-tertiary)' }} />,
|
|
220
|
+
title: "No performance data",
|
|
221
|
+
description: (
|
|
222
|
+
<>
|
|
223
|
+
Run <Box as="code" padding="xs" background="secondary" rounded="sm" style={{ fontSize: '12px', display: 'inline', fontFamily: 'var(--fui-font-mono, monospace)' }}>{BRAND.cliCommand} perf</Box> to measure bundle sizes, then reload the viewer.
|
|
224
|
+
</>
|
|
225
|
+
),
|
|
226
|
+
} : undefined;
|
|
234
227
|
|
|
235
|
-
|
|
236
|
-
const
|
|
228
|
+
// Safe destructure — only used when PanelShell renders children (not loading/empty)
|
|
229
|
+
const { bundleSize = 0, rawSize = 0, complexity = 'lightweight' as const, budgetPercent = 0, overBudget = false, measuredAt = '' } = perfData || {};
|
|
230
|
+
const measuredDate = measuredAt ? new Date(measuredAt).toLocaleString() : '';
|
|
237
231
|
|
|
238
232
|
return (
|
|
239
|
-
<
|
|
233
|
+
<PanelShell loading={loading} empty={emptyConfig}>
|
|
240
234
|
<Stack gap="md">
|
|
241
235
|
{overBudget && (
|
|
242
236
|
<Alert variant="danger">
|
|
@@ -249,7 +243,10 @@ export function PerformancePanel({ componentName }: PerformancePanelProps) {
|
|
|
249
243
|
<Card>
|
|
250
244
|
<Card.Body>
|
|
251
245
|
<Stack gap="xs">
|
|
252
|
-
<
|
|
246
|
+
<Stack direction="row" align="center" gap="xs">
|
|
247
|
+
<Package size={14} weight="regular" style={{ color: 'var(--text-tertiary)' }} />
|
|
248
|
+
<Text size="xs" color="secondary">Gzipped</Text>
|
|
249
|
+
</Stack>
|
|
253
250
|
<Text size="xl" weight="bold">{formatBytes(bundleSize)}</Text>
|
|
254
251
|
</Stack>
|
|
255
252
|
</Card.Body>
|
|
@@ -258,7 +255,10 @@ export function PerformancePanel({ componentName }: PerformancePanelProps) {
|
|
|
258
255
|
<Card>
|
|
259
256
|
<Card.Body>
|
|
260
257
|
<Stack gap="xs">
|
|
261
|
-
<
|
|
258
|
+
<Stack direction="row" align="center" gap="xs">
|
|
259
|
+
<FileCode size={14} weight="regular" style={{ color: 'var(--text-tertiary)' }} />
|
|
260
|
+
<Text size="xs" color="secondary">Raw (minified)</Text>
|
|
261
|
+
</Stack>
|
|
262
262
|
<Text size="xl" weight="bold">{formatBytes(rawSize)}</Text>
|
|
263
263
|
</Stack>
|
|
264
264
|
</Card.Body>
|
|
@@ -267,7 +267,10 @@ export function PerformancePanel({ componentName }: PerformancePanelProps) {
|
|
|
267
267
|
<Card>
|
|
268
268
|
<Card.Body>
|
|
269
269
|
<Stack gap="xs">
|
|
270
|
-
<
|
|
270
|
+
<Stack direction="row" align="center" gap="xs">
|
|
271
|
+
<Star size={14} weight="regular" style={{ color: 'var(--text-tertiary)' }} />
|
|
272
|
+
<Text size="xs" color="secondary">Complexity</Text>
|
|
273
|
+
</Stack>
|
|
271
274
|
<div>
|
|
272
275
|
<Badge variant={tierVariant(complexity)} size="lg">
|
|
273
276
|
{complexity}
|
|
@@ -284,7 +287,7 @@ export function PerformancePanel({ componentName }: PerformancePanelProps) {
|
|
|
284
287
|
</Card.Body>
|
|
285
288
|
</Card>
|
|
286
289
|
|
|
287
|
-
{perfData
|
|
290
|
+
{perfData?.imports && perfData.imports.length > 0 && (
|
|
288
291
|
<Card>
|
|
289
292
|
<Card.Body>
|
|
290
293
|
<ImportBreakdown imports={perfData.imports} rawSize={rawSize} />
|
|
@@ -296,6 +299,6 @@ export function PerformancePanel({ componentName }: PerformancePanelProps) {
|
|
|
296
299
|
Measured {measuredDate}. CSS excluded (JS-only measurement).
|
|
297
300
|
</Text>
|
|
298
301
|
</Stack>
|
|
299
|
-
</
|
|
302
|
+
</PanelShell>
|
|
300
303
|
);
|
|
301
304
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from "react";
|
|
2
|
+
import type { FragmentDefinition, FragmentVariant } from "../../core/index.js";
|
|
3
|
+
import { Box, Stack, Text, Separator, Button, TableOfContents } from "@fragments-sdk/ui";
|
|
4
|
+
import { getVariantSectionId } from "./viewer-utils.js";
|
|
5
|
+
|
|
6
|
+
interface TocHeading {
|
|
7
|
+
id: string;
|
|
8
|
+
text: string;
|
|
9
|
+
level: 2 | 3;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface PreviewAsideProps {
|
|
13
|
+
fragment: FragmentDefinition;
|
|
14
|
+
variants: FragmentVariant[];
|
|
15
|
+
focusedVariantIndex: number;
|
|
16
|
+
activePanel: string;
|
|
17
|
+
onSelectVariant: (index: number) => void;
|
|
18
|
+
onCopyLink: () => void;
|
|
19
|
+
onShowShortcuts: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function buildHeadings(fragment: FragmentDefinition): TocHeading[] {
|
|
23
|
+
const headings: TocHeading[] = [{ id: "setup", text: "Setup", level: 2 }];
|
|
24
|
+
|
|
25
|
+
if (fragment.variants.length > 0) {
|
|
26
|
+
headings.push({ id: "examples", text: "Examples", level: 2 });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
headings.push({ id: "props", text: "Props", level: 2 });
|
|
30
|
+
headings.push({ id: "usage-guidelines", text: "Usage Guidelines", level: 2 });
|
|
31
|
+
headings.push({ id: "when-to-use", text: "When to use", level: 3 });
|
|
32
|
+
headings.push({ id: "when-not-to-use", text: "When not to use", level: 3 });
|
|
33
|
+
|
|
34
|
+
if ((fragment.usage?.guidelines?.length ?? 0) > 0) {
|
|
35
|
+
headings.push({ id: "best-practices", text: "Best practices", level: 3 });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if ((fragment.relations?.length ?? 0) > 0) {
|
|
39
|
+
headings.push({ id: "related-components", text: "Related Components", level: 2 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return headings;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function useActiveHeading(ids: string[]): string | null {
|
|
46
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (ids.length === 0) return;
|
|
50
|
+
|
|
51
|
+
const observer = new IntersectionObserver(
|
|
52
|
+
(entries) => {
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (entry.isIntersecting) {
|
|
55
|
+
setActiveId(entry.target.id);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{ rootMargin: "-80px 0px -60% 0px", threshold: 0 }
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
for (const id of ids) {
|
|
63
|
+
const el = document.getElementById(id);
|
|
64
|
+
if (el) observer.observe(el);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return () => observer.disconnect();
|
|
68
|
+
}, [ids]);
|
|
69
|
+
|
|
70
|
+
return activeId;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function PreviewAside({
|
|
74
|
+
fragment,
|
|
75
|
+
variants,
|
|
76
|
+
focusedVariantIndex,
|
|
77
|
+
activePanel,
|
|
78
|
+
onSelectVariant,
|
|
79
|
+
onCopyLink,
|
|
80
|
+
onShowShortcuts,
|
|
81
|
+
}: PreviewAsideProps) {
|
|
82
|
+
const focusedVariant = variants[focusedVariantIndex] || null;
|
|
83
|
+
|
|
84
|
+
const headings = useMemo(() => buildHeadings(fragment), [fragment]);
|
|
85
|
+
const headingIds = useMemo(() => headings.map((h) => h.id), [headings]);
|
|
86
|
+
const activeId = useActiveHeading(headingIds);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Box padding="md" style={{ position: "sticky", top: "80px" }}>
|
|
90
|
+
<Stack gap="md">
|
|
91
|
+
{/* On This Page — same TOC component as docs site */}
|
|
92
|
+
<TableOfContents>
|
|
93
|
+
{headings.map((heading) => (
|
|
94
|
+
<TableOfContents.Item
|
|
95
|
+
key={heading.id}
|
|
96
|
+
id={heading.id}
|
|
97
|
+
active={activeId === heading.id}
|
|
98
|
+
indent={heading.level === 3}
|
|
99
|
+
>
|
|
100
|
+
{heading.text}
|
|
101
|
+
</TableOfContents.Item>
|
|
102
|
+
))}
|
|
103
|
+
</TableOfContents>
|
|
104
|
+
|
|
105
|
+
{/* Variant list */}
|
|
106
|
+
{variants.length > 0 && (
|
|
107
|
+
<>
|
|
108
|
+
<Separator />
|
|
109
|
+
<Stack gap="xs">
|
|
110
|
+
<Text
|
|
111
|
+
size="xs"
|
|
112
|
+
color="tertiary"
|
|
113
|
+
style={{ textTransform: "uppercase", letterSpacing: "0.08em" }}
|
|
114
|
+
>
|
|
115
|
+
Variants
|
|
116
|
+
</Text>
|
|
117
|
+
{variants.map((variant, index) => {
|
|
118
|
+
const active = index === focusedVariantIndex;
|
|
119
|
+
const anchorId = getVariantSectionId(fragment.meta.name, variant.name);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Button
|
|
123
|
+
key={variant.name}
|
|
124
|
+
as="a"
|
|
125
|
+
variant={active ? "secondary" : "ghost"}
|
|
126
|
+
size="sm"
|
|
127
|
+
href={`#${anchorId}`}
|
|
128
|
+
onClick={(event: React.MouseEvent) => {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
onSelectVariant(index);
|
|
131
|
+
}}
|
|
132
|
+
style={{ justifyContent: "flex-start" }}
|
|
133
|
+
>
|
|
134
|
+
{variant.name}
|
|
135
|
+
</Button>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
</Stack>
|
|
139
|
+
</>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
<Separator />
|
|
143
|
+
<Stack gap="xs">
|
|
144
|
+
<Text size="sm" weight="medium">
|
|
145
|
+
{fragment.meta.name}
|
|
146
|
+
</Text>
|
|
147
|
+
<Text size="xs" color="secondary">
|
|
148
|
+
{focusedVariant
|
|
149
|
+
? `Variant: ${focusedVariant.name}`
|
|
150
|
+
: "Select a variant"}
|
|
151
|
+
</Text>
|
|
152
|
+
<Text size="xs" color="tertiary">
|
|
153
|
+
Active panel: {activePanel}
|
|
154
|
+
</Text>
|
|
155
|
+
</Stack>
|
|
156
|
+
<Separator />
|
|
157
|
+
<Stack gap="xs">
|
|
158
|
+
<Button variant="ghost" size="sm" onClick={onCopyLink}>
|
|
159
|
+
Copy Link
|
|
160
|
+
</Button>
|
|
161
|
+
<Button variant="ghost" size="sm" onClick={onShowShortcuts}>
|
|
162
|
+
Keyboard Shortcuts
|
|
163
|
+
</Button>
|
|
164
|
+
</Stack>
|
|
165
|
+
</Stack>
|
|
166
|
+
</Box>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -191,9 +191,9 @@ function VariantRenderer({
|
|
|
191
191
|
<div
|
|
192
192
|
ref={containerRef}
|
|
193
193
|
style={{
|
|
194
|
-
display:
|
|
195
|
-
width:
|
|
196
|
-
minHeight:
|
|
194
|
+
display: 'block',
|
|
195
|
+
width: '100%',
|
|
196
|
+
minHeight: '100%',
|
|
197
197
|
transition: 'opacity 150ms',
|
|
198
198
|
opacity: content ? 1 : 0,
|
|
199
199
|
}}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import type { PropDefinition } from "../../core/index.js";
|
|
3
|
-
import { Input, Select, Toggle } from "@fragments-sdk/ui";
|
|
3
|
+
import { Box, Stack, Text, Button, Badge, Card, Input, Select, Toggle } from "@fragments-sdk/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
|
-
<
|
|
31
|
+
<Stack gap="lg">
|
|
32
32
|
{propEntries.map(([name, prop]) => (
|
|
33
33
|
<PropControl
|
|
34
34
|
key={name}
|
|
@@ -40,114 +40,60 @@ export function PropsEditor({
|
|
|
40
40
|
/>
|
|
41
41
|
))}
|
|
42
42
|
{hasChanges && (
|
|
43
|
-
<
|
|
44
|
-
onClick={onReset}
|
|
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
|
-
}}
|
|
59
|
-
>
|
|
43
|
+
<Button variant="ghost" size="sm" onClick={onReset}>
|
|
60
44
|
<RefreshIcon style={{ width: 12, height: 12 }} />
|
|
61
45
|
Reset
|
|
62
|
-
</
|
|
46
|
+
</Button>
|
|
63
47
|
)}
|
|
64
|
-
</
|
|
48
|
+
</Stack>
|
|
65
49
|
);
|
|
66
50
|
}
|
|
67
51
|
|
|
68
52
|
return (
|
|
69
|
-
<
|
|
53
|
+
<Card>
|
|
70
54
|
{/* Header */}
|
|
71
|
-
<
|
|
55
|
+
<Button
|
|
56
|
+
variant="ghost"
|
|
57
|
+
fullWidth
|
|
72
58
|
onClick={() => setIsCollapsed(!isCollapsed)}
|
|
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
|
-
}}
|
|
59
|
+
style={{ justifyContent: 'space-between', padding: '12px 16px', borderRadius: 0, borderBottom: '1px solid var(--border)' }}
|
|
86
60
|
>
|
|
87
|
-
<
|
|
88
|
-
<ControlsIcon style={{ width: 16, height: 16
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Modified
|
|
95
|
-
</span>
|
|
96
|
-
)}
|
|
97
|
-
</div>
|
|
98
|
-
<ChevronDownIcon
|
|
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
|
-
}}
|
|
106
|
-
/>
|
|
107
|
-
</button>
|
|
61
|
+
<Stack direction="row" align="center" gap="sm">
|
|
62
|
+
<ControlsIcon style={{ width: 16, height: 16 }} />
|
|
63
|
+
<Text size="sm" weight="medium">Props Editor</Text>
|
|
64
|
+
{hasChanges && <Badge variant="info" size="sm">Modified</Badge>}
|
|
65
|
+
</Stack>
|
|
66
|
+
<ChevronDownIcon style={{ width: 16, height: 16, transition: 'transform 0.15s', transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)' }} />
|
|
67
|
+
</Button>
|
|
108
68
|
|
|
109
69
|
{/* Content */}
|
|
110
70
|
{!isCollapsed && (
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
onClick={onReset}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
{/* Prop controls */}
|
|
137
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
138
|
-
{propEntries.map(([name, prop]) => (
|
|
139
|
-
<PropControl
|
|
140
|
-
key={name}
|
|
141
|
-
name={name}
|
|
142
|
-
prop={prop}
|
|
143
|
-
value={values[name]}
|
|
144
|
-
onChange={(value) => onChange(name, value)}
|
|
145
|
-
/>
|
|
146
|
-
))}
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
71
|
+
<Box padding="md">
|
|
72
|
+
<Stack gap="md">
|
|
73
|
+
{/* Reset button */}
|
|
74
|
+
{hasChanges && (
|
|
75
|
+
<Button variant="ghost" size="sm" onClick={onReset}>
|
|
76
|
+
<RefreshIcon style={{ width: 14, height: 14 }} />
|
|
77
|
+
Reset to defaults
|
|
78
|
+
</Button>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
{/* Prop controls */}
|
|
82
|
+
<Stack gap="sm">
|
|
83
|
+
{propEntries.map(([name, prop]) => (
|
|
84
|
+
<PropControl
|
|
85
|
+
key={name}
|
|
86
|
+
name={name}
|
|
87
|
+
prop={prop}
|
|
88
|
+
value={values[name]}
|
|
89
|
+
onChange={(value) => onChange(name, value)}
|
|
90
|
+
/>
|
|
91
|
+
))}
|
|
92
|
+
</Stack>
|
|
93
|
+
</Stack>
|
|
94
|
+
</Box>
|
|
149
95
|
)}
|
|
150
|
-
</
|
|
96
|
+
</Card>
|
|
151
97
|
);
|
|
152
98
|
}
|
|
153
99
|
|
|
@@ -236,40 +182,38 @@ function PropControl({
|
|
|
236
182
|
|
|
237
183
|
if (compact) {
|
|
238
184
|
return (
|
|
239
|
-
<
|
|
240
|
-
<label style={{
|
|
185
|
+
<Stack direction="row" gap="sm">
|
|
186
|
+
<Text as="label" size="xs" weight="medium" font="mono" style={{ whiteSpace: 'nowrap', minWidth: '100px' }} color="secondary">
|
|
241
187
|
{name}
|
|
242
|
-
</
|
|
188
|
+
</Text>
|
|
243
189
|
{getControlForProp(prop, displayValue, onChange, true)}
|
|
244
|
-
</
|
|
190
|
+
</Stack>
|
|
245
191
|
);
|
|
246
192
|
}
|
|
247
193
|
|
|
248
194
|
return (
|
|
249
|
-
<
|
|
250
|
-
<
|
|
251
|
-
<label
|
|
195
|
+
<Stack gap="sm">
|
|
196
|
+
<Stack direction="row" align="center" gap="sm">
|
|
197
|
+
<Text as="label" size="xs" weight="medium" font="mono">
|
|
252
198
|
{name}
|
|
253
199
|
{prop.required && (
|
|
254
200
|
<span style={{ color: 'var(--color-danger, #ef4444)', marginLeft: '2px' }}>*</span>
|
|
255
201
|
)}
|
|
256
|
-
</
|
|
257
|
-
<
|
|
202
|
+
</Text>
|
|
203
|
+
<Text size="xs" color="tertiary">{prop.type}</Text>
|
|
258
204
|
{prop.controlType && prop.controlType !== prop.type && (
|
|
259
|
-
<
|
|
260
|
-
{prop.controlType}
|
|
261
|
-
</span>
|
|
205
|
+
<Badge size="sm">{prop.controlType}</Badge>
|
|
262
206
|
)}
|
|
263
|
-
</
|
|
207
|
+
</Stack>
|
|
264
208
|
|
|
265
209
|
{getControlForProp(prop, displayValue, onChange, false)}
|
|
266
210
|
|
|
267
211
|
{prop.description && (
|
|
268
|
-
<p style={{
|
|
212
|
+
<Text as="p" size="xs" color="tertiary" style={{ lineHeight: 1.6 }}>
|
|
269
213
|
{prop.description}
|
|
270
|
-
</
|
|
214
|
+
</Text>
|
|
271
215
|
)}
|
|
272
|
-
</
|
|
216
|
+
</Stack>
|
|
273
217
|
);
|
|
274
218
|
}
|
|
275
219
|
|
|
@@ -407,7 +351,7 @@ function FunctionControl({
|
|
|
407
351
|
|
|
408
352
|
if (isEditing) {
|
|
409
353
|
return (
|
|
410
|
-
<
|
|
354
|
+
<Stack gap="sm" style={{ flex: 1 }}>
|
|
411
355
|
<textarea
|
|
412
356
|
value={inputValue}
|
|
413
357
|
onChange={(e) => setInputValue(e.target.value)}
|
|
@@ -427,41 +371,11 @@ function FunctionControl({
|
|
|
427
371
|
placeholder="() => console.log('clicked')"
|
|
428
372
|
autoFocus
|
|
429
373
|
/>
|
|
430
|
-
<
|
|
431
|
-
<
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
}}
|
|
444
|
-
>
|
|
445
|
-
Apply
|
|
446
|
-
</button>
|
|
447
|
-
<button
|
|
448
|
-
onClick={handleCancel}
|
|
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
|
-
}}
|
|
460
|
-
>
|
|
461
|
-
Cancel
|
|
462
|
-
</button>
|
|
463
|
-
</div>
|
|
464
|
-
</div>
|
|
374
|
+
<Stack direction="row" gap="sm">
|
|
375
|
+
<Button variant="primary" size="sm" onClick={handleSave}>Apply</Button>
|
|
376
|
+
<Button variant="ghost" size="sm" onClick={handleCancel}>Cancel</Button>
|
|
377
|
+
</Stack>
|
|
378
|
+
</Stack>
|
|
465
379
|
);
|
|
466
380
|
}
|
|
467
381
|
|
|
@@ -498,7 +412,7 @@ function ColorControl({
|
|
|
498
412
|
presetColors?: string[];
|
|
499
413
|
}) {
|
|
500
414
|
return (
|
|
501
|
-
<
|
|
415
|
+
<Stack direction="row" align="center" gap="sm">
|
|
502
416
|
<input
|
|
503
417
|
type="color"
|
|
504
418
|
value={value || "#000000"}
|
|
@@ -513,7 +427,7 @@ function ColorControl({
|
|
|
513
427
|
style={{ width: '96px', fontSize: '11px', fontFamily: 'monospace' }}
|
|
514
428
|
/>
|
|
515
429
|
{presetColors && presetColors.length > 0 && (
|
|
516
|
-
<
|
|
430
|
+
<Stack direction="row" gap="xs" wrap>
|
|
517
431
|
{presetColors.slice(0, 6).map((color) => (
|
|
518
432
|
<button
|
|
519
433
|
key={color}
|
|
@@ -530,9 +444,9 @@ function ColorControl({
|
|
|
530
444
|
title={color}
|
|
531
445
|
/>
|
|
532
446
|
))}
|
|
533
|
-
</
|
|
447
|
+
</Stack>
|
|
534
448
|
)}
|
|
535
|
-
</
|
|
449
|
+
</Stack>
|
|
536
450
|
);
|
|
537
451
|
}
|
|
538
452
|
|
|
@@ -574,7 +488,7 @@ function RangeControl({
|
|
|
574
488
|
step?: number;
|
|
575
489
|
}) {
|
|
576
490
|
return (
|
|
577
|
-
<
|
|
491
|
+
<Stack direction="row" align="center" gap="sm">
|
|
578
492
|
<input
|
|
579
493
|
type="range"
|
|
580
494
|
value={value ?? min}
|
|
@@ -584,9 +498,9 @@ function RangeControl({
|
|
|
584
498
|
onChange={(e) => onChange(Number(e.target.value))}
|
|
585
499
|
style={{ flex: 1, height: '8px', background: 'var(--bg-secondary)', borderRadius: '8px', cursor: 'pointer', accentColor: 'var(--color-accent)' }}
|
|
586
500
|
/>
|
|
587
|
-
<
|
|
501
|
+
<Text size="xs" font="mono" color="secondary" style={{ minWidth: '3rem', textAlign: 'right' }}>
|
|
588
502
|
{value ?? min}
|
|
589
|
-
</
|
|
590
|
-
</
|
|
503
|
+
</Text>
|
|
504
|
+
</Stack>
|
|
591
505
|
);
|
|
592
506
|
}
|