@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.
Files changed (128) hide show
  1. package/dist/bin.js +517 -77
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-WI6SLMSO.js → chunk-5GT62FCB.js} +2 -2
  4. package/dist/{chunk-CJEGT3WD.js → chunk-BW3ZATBW.js} +20 -3
  5. package/dist/chunk-BW3ZATBW.js.map +1 -0
  6. package/dist/{chunk-2JIKCJX3.js → chunk-D7372LQX.js} +13 -6
  7. package/dist/chunk-D7372LQX.js.map +1 -0
  8. package/dist/chunk-EZYXYWNF.js +131 -0
  9. package/dist/chunk-EZYXYWNF.js.map +1 -0
  10. package/dist/{chunk-NGIMCIK2.js → chunk-GF6OVPIN.js} +2 -2
  11. package/dist/{chunk-GOVI6COW.js → chunk-NVSPGSKB.js} +12 -4
  12. package/dist/chunk-NVSPGSKB.js.map +1 -0
  13. package/dist/core/index.d.ts +105 -3
  14. package/dist/core/index.js +12 -2
  15. package/dist/{defineFragment-D0UTve-I.d.ts → defineFragment-CBMS7Bab.d.ts} +21 -1
  16. package/dist/generate-LQA2R7FN.js +461 -0
  17. package/dist/generate-LQA2R7FN.js.map +1 -0
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +5 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/{init-KFYN37ZY.js → init-2GEGVIUQ.js} +14 -76
  22. package/dist/init-2GEGVIUQ.js.map +1 -0
  23. package/dist/mcp-bin.js +4 -3
  24. package/dist/mcp-bin.js.map +1 -1
  25. package/dist/{scan-65RH3QMM.js → scan-JGS65S7P.js} +6 -5
  26. package/dist/{service-A5GIGGGK.js → service-XP2EAJXD.js} +4 -3
  27. package/dist/{static-viewer-NSODM5VX.js → static-viewer-XCS7UJTO.js} +4 -3
  28. package/dist/storyFilters-3LUYAFZF.js +15 -0
  29. package/dist/storyFilters-3LUYAFZF.js.map +1 -0
  30. package/dist/{test-RPWZAYSJ.js → test-TD6TJNVY.js} +3 -3
  31. package/dist/{tokens-NIXSZRX7.js → tokens-2EXPCVP3.js} +5 -4
  32. package/dist/{tokens-NIXSZRX7.js.map → tokens-2EXPCVP3.js.map} +1 -1
  33. package/dist/{viewer-HZK4BSDK.js → viewer-RFA2KVBG.js} +249 -22
  34. package/dist/viewer-RFA2KVBG.js.map +1 -0
  35. package/package.json +2 -2
  36. package/src/bin.ts +26 -0
  37. package/src/build.ts +12 -2
  38. package/src/commands/build.ts +16 -2
  39. package/src/commands/doctor.ts +498 -0
  40. package/src/commands/generate.ts +383 -68
  41. package/src/commands/init-framework.ts +1 -1
  42. package/src/commands/init.ts +9 -51
  43. package/src/core/config.ts +15 -2
  44. package/src/core/generators/typescript-extractor.ts +10 -0
  45. package/src/core/index.ts +15 -0
  46. package/src/core/schema.ts +10 -2
  47. package/src/core/storyFilters.test.ts +350 -0
  48. package/src/core/storyFilters.ts +253 -0
  49. package/src/core/types.ts +22 -0
  50. package/src/migrate/converter.ts +9 -1
  51. package/src/migrate/parser.ts +2 -0
  52. package/src/migrate/types.ts +2 -0
  53. package/src/setup.ts +69 -24
  54. package/src/viewer/__tests__/viewer-integration.test.ts +1 -1
  55. package/src/viewer/components/AccessibilityPanel.tsx +305 -312
  56. package/src/viewer/components/ActionsPanel.tsx +31 -29
  57. package/src/viewer/components/AllVariantsPreview.tsx +78 -0
  58. package/src/viewer/components/App.tsx +187 -740
  59. package/src/viewer/components/BottomPanel.tsx +228 -132
  60. package/src/viewer/components/CodePanel.tsx +1 -1
  61. package/src/viewer/components/CommandPalette.tsx +7 -10
  62. package/src/viewer/components/ComponentDocView.tsx +164 -0
  63. package/src/viewer/components/ComponentGraph.tsx +111 -142
  64. package/src/viewer/components/ContractPanel.tsx +6 -6
  65. package/src/viewer/components/EmptyVariantMessage.tsx +54 -0
  66. package/src/viewer/components/FigmaEmbed.tsx +20 -18
  67. package/src/viewer/components/FragmentEditor.tsx +92 -115
  68. package/src/viewer/components/HeaderSearch.tsx +24 -0
  69. package/src/viewer/components/HealthDashboard.tsx +16 -2
  70. package/src/viewer/components/Icons.tsx +9 -0
  71. package/src/viewer/components/InteractionsPanel.tsx +101 -117
  72. package/src/viewer/components/IsolatedPreviewFrame.tsx +1 -0
  73. package/src/viewer/components/LandingPage.tsx +3 -3
  74. package/src/viewer/components/LeftSidebar.tsx +141 -63
  75. package/src/viewer/components/LoadErrorMessage.tsx +102 -0
  76. package/src/viewer/components/MultiViewportPreview.tsx +61 -142
  77. package/src/viewer/components/NoVariantsMessage.tsx +59 -0
  78. package/src/viewer/components/PanelShell.tsx +161 -0
  79. package/src/viewer/components/PerformancePanel.tsx +31 -28
  80. package/src/viewer/components/PreviewArea.tsx +1 -1
  81. package/src/viewer/components/PreviewAside.tsx +168 -0
  82. package/src/viewer/components/PreviewFrameHost.tsx +3 -3
  83. package/src/viewer/components/PropsEditor.tsx +70 -156
  84. package/src/viewer/components/ResizablePanel.tsx +103 -263
  85. package/src/viewer/components/RightSidebar.tsx +3 -9
  86. package/src/viewer/components/SkeletonLoader.tsx +13 -13
  87. package/src/viewer/components/TokenStylePanel.tsx +182 -209
  88. package/src/viewer/components/TopToolbar.tsx +159 -0
  89. package/src/viewer/components/VariantMatrix.tsx +42 -86
  90. package/src/viewer/components/VariantTabs.tsx +3 -3
  91. package/src/viewer/components/ViewerHeader.tsx +69 -0
  92. package/src/viewer/components/WebMCPDevTools.tsx +17 -23
  93. package/src/viewer/components/viewer-utils.ts +16 -0
  94. package/src/viewer/entry.tsx +5 -0
  95. package/src/viewer/hooks/useAppState.ts +27 -4
  96. package/src/viewer/hooks/usePreviewBridge.ts +2 -2
  97. package/src/viewer/preview-frame.html +6 -12
  98. package/src/viewer/server.ts +184 -6
  99. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +10 -0
  100. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +2 -0
  101. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +274 -0
  102. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +5 -0
  103. package/src/viewer/vendor/shared/src/PropsTable.module.scss +68 -0
  104. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +2 -0
  105. package/src/viewer/vendor/shared/src/PropsTable.tsx +76 -0
  106. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +122 -0
  107. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +2 -0
  108. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +134 -0
  109. package/src/viewer/vendor/shared/src/docs-data/index.ts +32 -0
  110. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +72 -0
  111. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +75 -0
  112. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +55 -0
  113. package/src/viewer/vendor/shared/src/index.ts +8 -0
  114. package/src/viewer/vendor/shared/src/types.ts +12 -0
  115. package/src/viewer/vite-plugin.ts +109 -4
  116. package/dist/chunk-2JIKCJX3.js.map +0 -1
  117. package/dist/chunk-CJEGT3WD.js.map +0 -1
  118. package/dist/chunk-GOVI6COW.js.map +0 -1
  119. package/dist/generate-35OIMW4Y.js +0 -252
  120. package/dist/generate-35OIMW4Y.js.map +0 -1
  121. package/dist/init-KFYN37ZY.js.map +0 -1
  122. package/dist/viewer-HZK4BSDK.js.map +0 -1
  123. /package/dist/{chunk-WI6SLMSO.js.map → chunk-5GT62FCB.js.map} +0 -0
  124. /package/dist/{chunk-NGIMCIK2.js.map → chunk-GF6OVPIN.js.map} +0 -0
  125. /package/dist/{scan-65RH3QMM.js.map → scan-JGS65S7P.js.map} +0 -0
  126. /package/dist/{service-A5GIGGGK.js.map → service-XP2EAJXD.js.map} +0 -0
  127. /package/dist/{static-viewer-NSODM5VX.js.map → static-viewer-XCS7UJTO.js.map} +0 -0
  128. /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, EmptyState, Alert, Collapsible } from '@fragments-sdk/ui';
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
- if (loading) {
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
- if (noData || !perfData) {
223
- return (
224
- <div style={{ padding: '16px' }}>
225
- <EmptyState>
226
- <EmptyState.Title>No performance data</EmptyState.Title>
227
- <EmptyState.Description>
228
- Run <code>{BRAND.cliCommand} perf</code> to measure bundle sizes, then reload.
229
- </EmptyState.Description>
230
- </EmptyState>
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
- const { bundleSize, rawSize, complexity, budgetPercent, overBudget, measuredAt } = perfData;
236
- const measuredDate = new Date(measuredAt).toLocaleString();
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
- <div style={{ padding: '16px' }}>
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
- <Text size="sm" color="secondary">Gzipped</Text>
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
- <Text size="sm" color="secondary">Raw (minified)</Text>
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
- <Text size="sm" color="secondary">Complexity</Text>
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.imports && perfData.imports.length > 0 && (
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
- </div>
302
+ </PanelShell>
300
303
  );
301
304
  }
@@ -423,7 +423,7 @@ export function PreviewArea({
423
423
  theme={previewTheme}
424
424
  width="100%"
425
425
  height="100%"
426
- minHeight={viewportHeight || 200}
426
+ minHeight={viewportHeight || 400}
427
427
  previewKey={previewKey}
428
428
  />
429
429
  </div>
@@ -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: mode === 'full-bleed' ? 'block' : 'inline-block',
195
- width: mode === 'full-bleed' ? '100%' : undefined,
196
- minHeight: mode === 'full-bleed' ? '100vh' : undefined,
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
- <div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
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
- <button
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
- </button>
46
+ </Button>
63
47
  )}
64
- </div>
48
+ </Stack>
65
49
  );
66
50
  }
67
51
 
68
52
  return (
69
- <div style={{ border: '1px solid var(--border)', borderRadius: '12px', overflow: 'hidden', background: 'var(--bg-elevated)' }}>
53
+ <Card>
70
54
  {/* Header */}
71
- <button
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
- <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)' }}>
90
- Props Editor
91
- </span>
92
- {hasChanges && (
93
- <span style={{ padding: '2px 6px', fontSize: '10px', fontWeight: 500, background: 'var(--color-accent)', color: '#fff', borderRadius: '4px' }}>
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
- <div style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: '16px' }}>
112
- {/* Reset button */}
113
- {hasChanges && (
114
- <button
115
- onClick={onReset}
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
- }}
130
- >
131
- <RefreshIcon style={{ width: 14, height: 14 }} />
132
- Reset to defaults
133
- </button>
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
- </div>
96
+ </Card>
151
97
  );
152
98
  }
153
99
 
@@ -236,40 +182,38 @@ function PropControl({
236
182
 
237
183
  if (compact) {
238
184
  return (
239
- <div style={{ display: 'flex', gap: '8px' }}>
240
- <label style={{ fontSize: '11px', fontWeight: 500, color: 'var(--text-secondary)', fontFamily: 'monospace', whiteSpace: 'nowrap', minWidth: '100px' }}>
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
- </label>
188
+ </Text>
243
189
  {getControlForProp(prop, displayValue, onChange, true)}
244
- </div>
190
+ </Stack>
245
191
  );
246
192
  }
247
193
 
248
194
  return (
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' }}>
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
- </label>
257
- <span style={{ fontSize: '11px', color: 'var(--text-tertiary)' }}>{prop.type}</span>
202
+ </Text>
203
+ <Text size="xs" color="tertiary">{prop.type}</Text>
258
204
  {prop.controlType && prop.controlType !== prop.type && (
259
- <span style={{ fontSize: '10px', color: 'var(--text-tertiary)', background: 'var(--bg-secondary)', padding: '2px 6px', borderRadius: '4px' }}>
260
- {prop.controlType}
261
- </span>
205
+ <Badge size="sm">{prop.controlType}</Badge>
262
206
  )}
263
- </div>
207
+ </Stack>
264
208
 
265
209
  {getControlForProp(prop, displayValue, onChange, false)}
266
210
 
267
211
  {prop.description && (
268
- <p style={{ fontSize: '11px', color: 'var(--text-tertiary)', lineHeight: 1.6 }}>
212
+ <Text as="p" size="xs" color="tertiary" style={{ lineHeight: 1.6 }}>
269
213
  {prop.description}
270
- </p>
214
+ </Text>
271
215
  )}
272
- </div>
216
+ </Stack>
273
217
  );
274
218
  }
275
219
 
@@ -407,7 +351,7 @@ function FunctionControl({
407
351
 
408
352
  if (isEditing) {
409
353
  return (
410
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', flex: 1 }}>
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
- <div style={{ display: 'flex', gap: '6px' }}>
431
- <button
432
- onClick={handleSave}
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
- }}
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
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
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
- <div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
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
- </div>
447
+ </Stack>
534
448
  )}
535
- </div>
449
+ </Stack>
536
450
  );
537
451
  }
538
452
 
@@ -574,7 +488,7 @@ function RangeControl({
574
488
  step?: number;
575
489
  }) {
576
490
  return (
577
- <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
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
- <span style={{ fontSize: '11px', fontFamily: 'monospace', color: 'var(--text-secondary)', minWidth: '3rem', textAlign: 'right' }}>
501
+ <Text size="xs" font="mono" color="secondary" style={{ minWidth: '3rem', textAlign: 'right' }}>
588
502
  {value ?? min}
589
- </span>
590
- </div>
503
+ </Text>
504
+ </Stack>
591
505
  );
592
506
  }