@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.
Files changed (124) hide show
  1. package/dist/bin.js +996 -79
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
  4. package/dist/chunk-6JBGU74P.js.map +1 -0
  5. package/dist/chunk-7OPWMLOE.js +1625 -0
  6. package/dist/chunk-7OPWMLOE.js.map +1 -0
  7. package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
  8. package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
  9. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
  12. package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
  13. package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
  14. package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
  15. package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +15 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
  20. package/dist/mcp-bin.js +8 -220
  21. package/dist/mcp-bin.js.map +1 -1
  22. package/dist/scan-WY23TJCP.js +12 -0
  23. package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
  24. package/dist/static-viewer-GBR7YNF3.js +12 -0
  25. package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
  26. package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
  27. package/dist/viewer-SUFOISZM.js +1822 -0
  28. package/dist/viewer-SUFOISZM.js.map +1 -0
  29. package/package.json +6 -5
  30. package/src/bin.ts +31 -0
  31. package/src/build.ts +147 -13
  32. package/src/cli-commands.ts +18 -0
  33. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  34. package/src/commands/a11y-report.ts +625 -0
  35. package/src/commands/a11y.ts +168 -14
  36. package/src/commands/build.ts +16 -0
  37. package/src/commands/graph.ts +274 -0
  38. package/src/core/auto-props.ts +464 -0
  39. package/src/core/composition.ts +64 -1
  40. package/src/core/graph-extractor.test.ts +542 -0
  41. package/src/core/graph-extractor.ts +601 -0
  42. package/src/core/importAnalyzer.ts +5 -0
  43. package/src/core/schema.ts +2 -0
  44. package/src/core/types.ts +3 -1
  45. package/src/index.ts +4 -0
  46. package/src/mcp/server.ts +13 -220
  47. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  48. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  49. package/src/theme/contrast.test.ts +331 -0
  50. package/src/theme/contrast.ts +246 -0
  51. package/src/theme/generator.ts +213 -1
  52. package/src/theme/index.ts +16 -0
  53. package/src/theme/types.ts +51 -0
  54. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  55. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  56. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  57. package/src/viewer/components/ActionCapture.tsx +1 -1
  58. package/src/viewer/components/ActionsPanel.tsx +142 -183
  59. package/src/viewer/components/App.tsx +276 -183
  60. package/src/viewer/components/BottomPanel.tsx +40 -80
  61. package/src/viewer/components/CodePanel.tsx +9 -87
  62. package/src/viewer/components/CommandPalette.tsx +117 -74
  63. package/src/viewer/components/ComponentGraph.tsx +143 -126
  64. package/src/viewer/components/ComponentHeader.tsx +46 -43
  65. package/src/viewer/components/ContractPanel.tsx +124 -117
  66. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  67. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  68. package/src/viewer/components/FragmentEditor.tsx +126 -63
  69. package/src/viewer/components/HealthDashboard.tsx +146 -171
  70. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  71. package/src/viewer/components/Icons.tsx +151 -98
  72. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  73. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  74. package/src/viewer/components/IsolatedRender.tsx +12 -6
  75. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  76. package/src/viewer/components/LandingPage.tsx +285 -305
  77. package/src/viewer/components/Layout.tsx +12 -10
  78. package/src/viewer/components/LeftSidebar.tsx +103 -155
  79. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  80. package/src/viewer/components/PreviewArea.tsx +113 -44
  81. package/src/viewer/components/PreviewFrameHost.tsx +36 -6
  82. package/src/viewer/components/PreviewPane.tsx +2 -3
  83. package/src/viewer/components/PreviewToolbar.tsx +109 -105
  84. package/src/viewer/components/PropsEditor.tsx +154 -74
  85. package/src/viewer/components/PropsTable.tsx +95 -82
  86. package/src/viewer/components/RelationsSection.tsx +71 -40
  87. package/src/viewer/components/ResizablePanel.tsx +158 -55
  88. package/src/viewer/components/RightSidebar.tsx +46 -56
  89. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  90. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  91. package/src/viewer/components/StoryRenderer.tsx +4 -11
  92. package/src/viewer/components/Toast.tsx +3 -67
  93. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  94. package/src/viewer/components/UsageSection.tsx +26 -26
  95. package/src/viewer/components/VariantMatrix.tsx +140 -47
  96. package/src/viewer/components/VariantTabs.tsx +24 -68
  97. package/src/viewer/components/ViewportSelector.tsx +121 -114
  98. package/src/viewer/constants/ui.ts +23 -22
  99. package/src/viewer/entry.tsx +8 -3
  100. package/src/viewer/index.ts +3 -6
  101. package/src/viewer/preview-frame.html +43 -18
  102. package/src/viewer/server.ts +7 -16
  103. package/src/viewer/styles/globals.css +46 -85
  104. package/src/viewer/utils/a11y-fixes.ts +53 -30
  105. package/dist/chunk-ICAIQ57V.js.map +0 -1
  106. package/dist/chunk-U4GQ2JTD.js +0 -832
  107. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  108. package/dist/scan-ESEXV7LF.js +0 -12
  109. package/dist/static-viewer-O37MJ5B6.js +0 -12
  110. package/dist/viewer-YDGFDTK5.js +0 -11104
  111. package/dist/viewer-YDGFDTK5.js.map +0 -1
  112. package/src/viewer/postcss.config.js +0 -6
  113. package/src/viewer/tailwind.config.js +0 -37
  114. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  115. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  116. /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
  117. /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
  118. /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
  119. /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
  120. /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
  121. /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
  122. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
  123. /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
  124. /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
@@ -2,8 +2,8 @@ import { useForm, Controller } from 'react-hook-form';
2
2
  import { zodResolver } from '@hookform/resolvers/zod';
3
3
  import { z } from 'zod';
4
4
  import { useState, useRef, useEffect } from 'react';
5
- import clsx from 'clsx';
6
5
  import { HexColorPicker } from 'react-colorful';
6
+ import { Button, Input, Toggle, Stack, Text, Card, Field, Separator, Select } from '@fragments/ui';
7
7
  import { generateColorScheme, type ColorSchemeType } from '../utils/colorSchemes.js';
8
8
  import { ChevronDownIcon } from './Icons.js';
9
9
 
@@ -24,7 +24,6 @@ interface LandingPageProps {
24
24
  onSubmit?: (data: LandingFormData) => void;
25
25
  }
26
26
 
27
- // Scheme options with color counts
28
27
  const SCHEME_OPTIONS: Array<{
29
28
  value: ColorSchemeType;
30
29
  label: string;
@@ -39,23 +38,22 @@ const SCHEME_OPTIONS: Array<{
39
38
  { value: 'tetradic', label: 'Tetradic (Square)', description: 'Four evenly spaced colors', colorCount: 4 },
40
39
  ];
41
40
 
42
- // Get the key colors for a scheme (not all variations, just the distinct hues)
43
41
  function getSchemeKeyColors(baseColor: string, type: ColorSchemeType): string[] {
44
42
  const fullPalette = generateColorScheme(baseColor, type);
45
43
 
46
44
  switch (type) {
47
45
  case 'monochromatic':
48
- return fullPalette; // All 5 variations
46
+ return fullPalette;
49
47
  case 'complementary':
50
- return [fullPalette[1], fullPalette[3]]; // Base and complement
48
+ return [fullPalette[1], fullPalette[3]];
51
49
  case 'analogous':
52
- return [fullPalette[0], fullPalette[2], fullPalette[4]]; // -30°, base, +30°
50
+ return [fullPalette[0], fullPalette[2], fullPalette[4]];
53
51
  case 'triadic':
54
- return [fullPalette[0], fullPalette[2], fullPalette[4]]; // 0°, 120°, 240°
52
+ return [fullPalette[0], fullPalette[2], fullPalette[4]];
55
53
  case 'split-complementary':
56
- return [fullPalette[0], fullPalette[2], fullPalette[4]]; // Base, +150°, +210°
54
+ return [fullPalette[0], fullPalette[2], fullPalette[4]];
57
55
  case 'tetradic':
58
- return [fullPalette[0], fullPalette[1], fullPalette[2], fullPalette[3]]; // 0°, 90°, 180°, 270°
56
+ return [fullPalette[0], fullPalette[1], fullPalette[2], fullPalette[3]];
59
57
  default:
60
58
  return fullPalette;
61
59
  }
@@ -63,9 +61,7 @@ function getSchemeKeyColors(baseColor: string, type: ColorSchemeType): string[]
63
61
 
64
62
  export function LandingPage({ onSubmit }: LandingPageProps) {
65
63
  const [showColorPicker, setShowColorPicker] = useState(false);
66
- const [showSchemeDropdown, setShowSchemeDropdown] = useState(false);
67
64
  const colorPickerRef = useRef<HTMLDivElement>(null);
68
- const schemeDropdownRef = useRef<HTMLDivElement>(null);
69
65
 
70
66
  const {
71
67
  register,
@@ -91,19 +87,14 @@ export function LandingPage({ onSubmit }: LandingPageProps) {
91
87
  const primaryColor = watch('primaryColor');
92
88
  const colorSchemeType = watch('colorSchemeType');
93
89
 
94
- // Generate current palette based on selected color and scheme
95
90
  const currentPalette = getSchemeKeyColors(primaryColor, colorSchemeType);
96
91
  const currentScheme = SCHEME_OPTIONS.find(s => s.value === colorSchemeType);
97
92
 
98
- // Close dropdowns when clicking outside
99
93
  useEffect(() => {
100
94
  const handleClickOutside = (event: MouseEvent) => {
101
95
  if (colorPickerRef.current && !colorPickerRef.current.contains(event.target as Node)) {
102
96
  setShowColorPicker(false);
103
97
  }
104
- if (schemeDropdownRef.current && !schemeDropdownRef.current.contains(event.target as Node)) {
105
- setShowSchemeDropdown(false);
106
- }
107
98
  };
108
99
  document.addEventListener('mousedown', handleClickOutside);
109
100
  return () => document.removeEventListener('mousedown', handleClickOutside);
@@ -115,325 +106,314 @@ export function LandingPage({ onSubmit }: LandingPageProps) {
115
106
  };
116
107
 
117
108
  return (
118
- <div className="min-h-screen bg-[--bg-primary]">
119
- <div className="max-w-2xl mx-auto px-6 py-16">
109
+ <div style={{ minHeight: '100vh', backgroundColor: 'var(--bg-primary)' }}>
110
+ <div style={{ maxWidth: '672px', margin: '0 auto', padding: '64px 24px' }}>
120
111
  {/* Header */}
121
- <div className="mb-12">
122
- <h1 className="text-2xl font-semibold text-primary mb-2">
123
- Create Component
124
- </h1>
125
- <p className="text-secondary">
126
- Configure your component settings below.
127
- </p>
128
- </div>
112
+ <Stack direction="column" gap="sm" style={{ marginBottom: '48px' }}>
113
+ <Text as="h1" size="xl" weight="semibold">Create Component</Text>
114
+ <Text color="secondary">Configure your component settings below.</Text>
115
+ </Stack>
129
116
 
130
- <form onSubmit={handleSubmit(handleFormSubmit)} className="space-y-8">
131
- {/* Project Name */}
132
- <div>
133
- <label className="block text-sm text-primary mb-2">
134
- Project Name
135
- </label>
136
- <input
137
- {...register('projectName')}
138
- placeholder="my-design-system"
139
- className={clsx(
140
- 'w-full px-4 py-3 rounded-xl text-sm',
141
- 'bg-[--bg-tertiary] text-primary placeholder:text-tertiary',
142
- 'border border-transparent',
143
- 'focus:outline-none focus:ring-1 focus:ring-[--border-strong]',
144
- 'transition-all',
145
- errors.projectName && 'ring-1 ring-red-500'
146
- )}
147
- />
148
- {errors.projectName && (
149
- <p className="mt-2 text-xs text-red-400">{errors.projectName.message}</p>
150
- )}
151
- </div>
152
-
153
- {/* Component Name */}
154
- <div>
155
- <label className="block text-sm text-primary mb-2">
156
- Component Name
157
- </label>
158
- <input
159
- {...register('componentName')}
160
- placeholder="Button"
161
- className={clsx(
162
- 'w-full px-4 py-3 rounded-xl text-sm',
163
- 'bg-[--bg-tertiary] text-primary placeholder:text-tertiary',
164
- 'border border-transparent',
165
- 'focus:outline-none focus:ring-1 focus:ring-[--border-strong]',
166
- 'transition-all',
167
- errors.componentName && 'ring-1 ring-red-500'
117
+ <form onSubmit={handleSubmit(handleFormSubmit)}>
118
+ <Stack direction="column" gap="lg">
119
+ {/* Project Name */}
120
+ <Field>
121
+ <Field.Label>Project Name</Field.Label>
122
+ <Field.Control>
123
+ <Input
124
+ {...register('projectName')}
125
+ placeholder="my-design-system"
126
+ style={errors.projectName ? { boxShadow: '0 0 0 1px #ef4444' } : {}}
127
+ />
128
+ </Field.Control>
129
+ {errors.projectName && (
130
+ <Field.Error>{errors.projectName.message}</Field.Error>
168
131
  )}
169
- />
170
- {errors.componentName && (
171
- <p className="mt-2 text-xs text-red-400">{errors.componentName.message}</p>
172
- )}
173
- </div>
132
+ </Field>
174
133
 
175
- {/* Description */}
176
- <div>
177
- <label className="block text-sm text-primary mb-2">
178
- Description
179
- </label>
180
- <textarea
181
- {...register('description')}
182
- rows={2}
183
- placeholder="A brief description of the component"
184
- className={clsx(
185
- 'w-full px-4 py-3 rounded-xl text-sm resize-none',
186
- 'bg-[--bg-tertiary] text-primary placeholder:text-tertiary',
187
- 'border border-transparent',
188
- 'focus:outline-none focus:ring-1 focus:ring-[--border-strong]',
189
- 'transition-all'
134
+ {/* Component Name */}
135
+ <Field>
136
+ <Field.Label>Component Name</Field.Label>
137
+ <Field.Control>
138
+ <Input
139
+ {...register('componentName')}
140
+ placeholder="Button"
141
+ style={errors.componentName ? { boxShadow: '0 0 0 1px #ef4444' } : {}}
142
+ />
143
+ </Field.Control>
144
+ {errors.componentName && (
145
+ <Field.Error>{errors.componentName.message}</Field.Error>
190
146
  )}
191
- />
192
- </div>
147
+ </Field>
193
148
 
194
- <div className="h-px bg-[--border]" />
149
+ {/* Description */}
150
+ <Field>
151
+ <Field.Label>Description</Field.Label>
152
+ <Field.Control>
153
+ <textarea
154
+ {...register('description')}
155
+ rows={2}
156
+ placeholder="A brief description of the component"
157
+ style={{
158
+ width: '100%',
159
+ padding: '12px 16px',
160
+ borderRadius: '12px',
161
+ fontSize: '14px',
162
+ backgroundColor: 'var(--bg-tertiary)',
163
+ color: 'var(--text-primary)',
164
+ border: '1px solid transparent',
165
+ outline: 'none',
166
+ transition: 'all 150ms',
167
+ resize: 'none',
168
+ }}
169
+ onFocus={(e) => { e.currentTarget.style.boxShadow = '0 0 0 1px var(--border-strong)'; }}
170
+ onBlur={(e) => { e.currentTarget.style.boxShadow = 'none'; }}
171
+ />
172
+ </Field.Control>
173
+ </Field>
195
174
 
196
- {/* Headless Library */}
197
- <div>
198
- <label className="block text-sm text-primary mb-4">
199
- Headless Library
200
- </label>
201
- <div className="grid grid-cols-2 gap-3">
202
- {[
203
- { value: 'radix', label: 'Radix UI', desc: 'Accessible primitives' },
204
- { value: 'base-ui', label: 'Base UI', desc: 'Hooks-first approach' },
205
- ].map((lib) => (
206
- <label
207
- key={lib.value}
208
- className={clsx(
209
- 'flex flex-col p-4 rounded-xl cursor-pointer transition-all',
210
- 'border',
211
- watch('headlessLibrary') === lib.value
212
- ? 'border-[--color-accent] bg-[--color-accent]/5'
213
- : 'border-[--border] hover:border-[--border-strong]'
214
- )}
215
- >
216
- <input
217
- type="radio"
218
- {...register('headlessLibrary')}
219
- value={lib.value}
220
- className="sr-only"
221
- />
222
- <span className="text-sm font-medium text-primary">{lib.label}</span>
223
- <span className="text-xs text-tertiary mt-0.5">{lib.desc}</span>
224
- </label>
225
- ))}
226
- </div>
227
- </div>
175
+ <Separator />
228
176
 
229
- <div className="h-px bg-[--border]" />
177
+ {/* Headless Library */}
178
+ <Field>
179
+ <Field.Label>Headless Library</Field.Label>
180
+ <Field.Control>
181
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
182
+ {[
183
+ { value: 'radix', label: 'Radix UI', desc: 'Accessible primitives' },
184
+ { value: 'base-ui', label: 'Base UI', desc: 'Hooks-first approach' },
185
+ ].map((lib) => (
186
+ <label
187
+ key={lib.value}
188
+ style={{
189
+ display: 'flex',
190
+ flexDirection: 'column',
191
+ padding: '16px',
192
+ borderRadius: '12px',
193
+ cursor: 'pointer',
194
+ transition: 'all 150ms',
195
+ border: watch('headlessLibrary') === lib.value
196
+ ? '1px solid var(--color-accent)'
197
+ : '1px solid var(--border)',
198
+ backgroundColor: watch('headlessLibrary') === lib.value
199
+ ? 'rgba(16, 163, 127, 0.05)'
200
+ : 'transparent',
201
+ }}
202
+ >
203
+ <input
204
+ type="radio"
205
+ {...register('headlessLibrary')}
206
+ value={lib.value}
207
+ style={{ position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0,0,0,0)' }}
208
+ />
209
+ <Text size="sm" weight="medium">{lib.label}</Text>
210
+ <Text size="xs" color="tertiary" style={{ marginTop: '2px' }}>{lib.desc}</Text>
211
+ </label>
212
+ ))}
213
+ </div>
214
+ </Field.Control>
215
+ </Field>
230
216
 
231
- {/* Color Scheme */}
232
- <div>
233
- <label className="block text-sm text-primary mb-4">
234
- Color Scheme
235
- </label>
217
+ <Separator />
236
218
 
237
- <div className="space-y-4">
238
- {/* Primary Color */}
239
- <div className="flex items-center gap-4">
240
- <div className="relative" ref={colorPickerRef}>
241
- <button
242
- type="button"
243
- onClick={() => setShowColorPicker(!showColorPicker)}
244
- className="w-12 h-12 rounded-lg border border-[--border] hover:border-[--border-strong] transition-colors"
245
- style={{ backgroundColor: primaryColor }}
246
- />
247
- {showColorPicker && (
248
- <div className="absolute top-14 left-0 z-50 p-3 rounded-xl bg-[--bg-elevated] border border-[--border] shadow-xl">
249
- <Controller
250
- name="primaryColor"
251
- control={control}
252
- render={({ field }) => (
253
- <HexColorPicker color={field.value} onChange={field.onChange} />
254
- )}
219
+ {/* Color Scheme */}
220
+ <Field>
221
+ <Field.Label>Color Scheme</Field.Label>
222
+ <Field.Control>
223
+ <Stack direction="column" gap="md">
224
+ {/* Primary Color */}
225
+ <Stack direction="row" align="center" gap="md">
226
+ <div style={{ position: 'relative' }} ref={colorPickerRef}>
227
+ <button
228
+ type="button"
229
+ onClick={() => setShowColorPicker(!showColorPicker)}
230
+ style={{
231
+ width: '48px',
232
+ height: '48px',
233
+ borderRadius: '8px',
234
+ border: '1px solid var(--border)',
235
+ cursor: 'pointer',
236
+ backgroundColor: primaryColor,
237
+ }}
255
238
  />
256
- <input
257
- type="text"
239
+ {showColorPicker && (
240
+ <div style={{
241
+ position: 'absolute',
242
+ top: '56px',
243
+ left: 0,
244
+ zIndex: 50,
245
+ padding: '12px',
246
+ borderRadius: '12px',
247
+ backgroundColor: 'var(--bg-elevated)',
248
+ border: '1px solid var(--border)',
249
+ boxShadow: 'var(--shadow-lg)',
250
+ }}>
251
+ <Controller
252
+ name="primaryColor"
253
+ control={control}
254
+ render={({ field }) => (
255
+ <HexColorPicker color={field.value} onChange={field.onChange} />
256
+ )}
257
+ />
258
+ <Input
259
+ value={primaryColor}
260
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue('primaryColor', e.target.value)}
261
+ size="sm"
262
+ style={{ width: '100%', marginTop: '12px', fontFamily: 'monospace' }}
263
+ />
264
+ </div>
265
+ )}
266
+ </div>
267
+ <div style={{ flex: 1 }}>
268
+ <Input
258
269
  value={primaryColor}
259
- onChange={(e) => setValue('primaryColor', e.target.value)}
260
- className="w-full mt-3 px-3 py-2 rounded-lg text-xs font-mono bg-[--bg-tertiary] text-primary border border-transparent focus:outline-none focus:ring-1 focus:ring-[--border-strong]"
270
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue('primaryColor', e.target.value)}
271
+ style={{ fontFamily: 'monospace' }}
261
272
  />
262
273
  </div>
263
- )}
264
- </div>
265
- <div className="flex-1">
266
- <input
267
- type="text"
268
- value={primaryColor}
269
- onChange={(e) => setValue('primaryColor', e.target.value)}
270
- className="w-full px-4 py-3 rounded-xl text-sm font-mono bg-[--bg-tertiary] text-primary border border-transparent focus:outline-none focus:ring-1 focus:ring-[--border-strong]"
271
- />
272
- </div>
273
- </div>
274
+ </Stack>
274
275
 
275
- {/* Custom Scheme Type Dropdown */}
276
- <div className="relative" ref={schemeDropdownRef}>
277
- <button
278
- type="button"
279
- onClick={() => setShowSchemeDropdown(!showSchemeDropdown)}
280
- className={clsx(
281
- 'w-full px-4 py-3 rounded-xl text-sm text-left',
282
- 'bg-[--bg-tertiary] text-primary',
283
- 'border border-transparent',
284
- 'focus:outline-none focus:ring-1 focus:ring-[--border-strong]',
285
- 'flex items-center justify-between',
286
- showSchemeDropdown && 'ring-1 ring-[--border-strong]'
287
- )}
288
- >
289
- <div className="flex items-center gap-3">
290
- {/* Mini palette preview */}
291
- <div className="flex gap-0.5">
292
- {getSchemeKeyColors(primaryColor, colorSchemeType).map((color, i) => (
276
+ {/* Scheme Type Dropdown */}
277
+ <Select
278
+ value={colorSchemeType}
279
+ onValueChange={(value: string) => setValue('colorSchemeType', value as ColorSchemeType)}
280
+ >
281
+ <Select.Trigger>
282
+ <Stack direction="row" align="center" gap="sm">
283
+ <div style={{ display: 'flex', gap: '2px' }}>
284
+ {getSchemeKeyColors(primaryColor, colorSchemeType).map((color, i) => (
285
+ <div
286
+ key={i}
287
+ style={{ width: '16px', height: '16px', borderRadius: '2px', backgroundColor: color }}
288
+ />
289
+ ))}
290
+ </div>
291
+ <span>{currentScheme?.label}</span>
292
+ <Text as="span" color="tertiary">-- {currentScheme?.description}</Text>
293
+ </Stack>
294
+ </Select.Trigger>
295
+ <Select.Content>
296
+ {SCHEME_OPTIONS.map((scheme) => (
297
+ <Select.Item key={scheme.value} value={scheme.value}>
298
+ <Stack direction="row" align="center" gap="sm">
299
+ <div style={{ display: 'flex', gap: '2px', flexShrink: 0 }}>
300
+ {getSchemeKeyColors(primaryColor, scheme.value).map((color, i) => (
301
+ <div
302
+ key={i}
303
+ style={{ width: '20px', height: '20px', borderRadius: '2px', backgroundColor: color }}
304
+ />
305
+ ))}
306
+ </div>
307
+ <div>
308
+ <Text size="sm">{scheme.label}</Text>
309
+ <Text size="xs" color="tertiary">{scheme.description}</Text>
310
+ </div>
311
+ </Stack>
312
+ </Select.Item>
313
+ ))}
314
+ </Select.Content>
315
+ </Select>
316
+
317
+ {/* Current Palette Preview */}
318
+ <div>
319
+ <div style={{ display: 'flex', gap: '6px' }}>
320
+ {currentPalette.map((color, i) => (
293
321
  <div
294
322
  key={i}
295
- className="w-4 h-4 rounded-sm first:rounded-l last:rounded-r"
296
- style={{ backgroundColor: color }}
323
+ style={{
324
+ flex: 1,
325
+ height: '48px',
326
+ borderRadius: '8px',
327
+ transition: 'background-color 150ms',
328
+ backgroundColor: color,
329
+ }}
297
330
  />
298
331
  ))}
299
332
  </div>
300
- <span>{currentScheme?.label}</span>
301
- <span className="text-tertiary">— {currentScheme?.description}</span>
333
+ <Text size="xs" color="tertiary" style={{ marginTop: '8px' }}>
334
+ {currentScheme?.colorCount} color {currentScheme?.label.toLowerCase()} palette
335
+ </Text>
302
336
  </div>
303
- <ChevronDownIcon className={clsx('w-4 h-4 text-tertiary transition-transform', showSchemeDropdown && 'rotate-180')} />
304
- </button>
337
+ </Stack>
338
+ </Field.Control>
339
+ </Field>
305
340
 
306
- {showSchemeDropdown && (
307
- <div className="absolute top-full left-0 right-0 mt-2 z-40 py-2 rounded-xl bg-[--bg-elevated] border border-[--border] shadow-xl">
308
- {SCHEME_OPTIONS.map((scheme) => {
309
- const schemeColors = getSchemeKeyColors(primaryColor, scheme.value);
310
- const isSelected = colorSchemeType === scheme.value;
311
-
312
- return (
313
- <button
314
- key={scheme.value}
315
- type="button"
316
- onClick={() => {
317
- setValue('colorSchemeType', scheme.value);
318
- setShowSchemeDropdown(false);
319
- }}
320
- className={clsx(
321
- 'w-full px-4 py-3 text-left flex items-center gap-3',
322
- 'hover:bg-[--bg-hover] transition-colors',
323
- isSelected && 'bg-[--bg-tertiary]'
324
- )}
325
- >
326
- {/* Color preview swatches */}
327
- <div className="flex gap-0.5 shrink-0">
328
- {schemeColors.map((color, i) => (
329
- <div
330
- key={i}
331
- className="w-5 h-5 rounded-sm first:rounded-l last:rounded-r"
332
- style={{ backgroundColor: color }}
333
- />
334
- ))}
335
- </div>
336
- <div className="flex-1 min-w-0">
337
- <div className="flex items-center gap-2">
338
- {isSelected && <span className="text-[--color-accent]">✓</span>}
339
- <span className={clsx('text-sm', isSelected ? 'text-primary font-medium' : 'text-primary')}>
340
- {scheme.label}
341
- </span>
342
- </div>
343
- <span className="text-xs text-tertiary">{scheme.description}</span>
344
- </div>
345
- </button>
346
- );
347
- })}
348
- </div>
349
- )}
350
- </div>
341
+ <Separator />
351
342
 
352
- {/* Current Palette Preview */}
353
- <div>
354
- <div className="flex gap-1.5">
355
- {currentPalette.map((color, i) => (
356
- <div
357
- key={i}
358
- className="flex-1 h-12 rounded-lg first:rounded-l-xl last:rounded-r-xl transition-colors"
359
- style={{ backgroundColor: color }}
360
- />
343
+ {/* Style Options */}
344
+ <Stack direction="column" gap="lg">
345
+ {/* Border Radius */}
346
+ <Stack direction="row" align="center" justify="between">
347
+ <div>
348
+ <Text size="sm">Border Radius</Text>
349
+ <Text size="xs" color="tertiary" style={{ marginTop: '2px' }}>Component corner style</Text>
350
+ </div>
351
+ <div style={{
352
+ display: 'flex',
353
+ gap: '4px',
354
+ backgroundColor: 'var(--bg-tertiary)',
355
+ padding: '4px',
356
+ borderRadius: '8px',
357
+ }}>
358
+ {(['none', 'sm', 'md', 'lg', 'full'] as const).map((radius) => (
359
+ <label
360
+ key={radius}
361
+ style={{
362
+ padding: '6px 12px',
363
+ fontSize: '12px',
364
+ fontWeight: 500,
365
+ borderRadius: '6px',
366
+ cursor: 'pointer',
367
+ transition: 'all 150ms',
368
+ color: watch('borderRadius') === radius ? 'var(--text-primary)' : 'var(--text-tertiary)',
369
+ backgroundColor: watch('borderRadius') === radius ? 'var(--bg-primary)' : 'transparent',
370
+ boxShadow: watch('borderRadius') === radius ? 'var(--shadow-sm)' : 'none',
371
+ }}
372
+ >
373
+ <input
374
+ type="radio"
375
+ {...register('borderRadius')}
376
+ value={radius}
377
+ style={{ position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0,0,0,0)' }}
378
+ />
379
+ {radius}
380
+ </label>
361
381
  ))}
362
382
  </div>
363
- <p className="text-xs text-tertiary mt-2">
364
- {currentScheme?.colorCount} color {currentScheme?.label.toLowerCase()} palette
365
- </p>
366
- </div>
367
- </div>
368
- </div>
369
-
370
- <div className="h-px bg-[--border]" />
383
+ </Stack>
371
384
 
372
- {/* Style Options */}
373
- <div className="space-y-6">
374
- {/* Border Radius */}
375
- <div className="flex items-center justify-between">
376
- <div>
377
- <span className="text-sm text-primary">Border Radius</span>
378
- <p className="text-xs text-tertiary mt-0.5">Component corner style</p>
379
- </div>
380
- <div className="flex gap-1 bg-[--bg-tertiary] p-1 rounded-lg">
381
- {(['none', 'sm', 'md', 'lg', 'full'] as const).map((radius) => (
382
- <label
383
- key={radius}
384
- className={clsx(
385
- 'px-3 py-1.5 text-xs font-medium rounded-md cursor-pointer transition-all',
386
- watch('borderRadius') === radius
387
- ? 'bg-[--bg-primary] text-primary shadow-sm'
388
- : 'text-tertiary hover:text-secondary'
389
- )}
390
- >
391
- <input
392
- type="radio"
393
- {...register('borderRadius')}
394
- value={radius}
395
- className="sr-only"
385
+ {/* Dark Mode */}
386
+ <Stack direction="row" align="center" justify="between">
387
+ <div>
388
+ <Text size="sm">Dark Mode</Text>
389
+ <Text size="xs" color="tertiary" style={{ marginTop: '2px' }}>Include dark theme styles</Text>
390
+ </div>
391
+ <Controller
392
+ name="darkMode"
393
+ control={control}
394
+ render={({ field }) => (
395
+ <Toggle
396
+ checked={field.value}
397
+ onCheckedChange={field.onChange}
396
398
  />
397
- {radius}
398
- </label>
399
- ))}
400
- </div>
401
- </div>
402
-
403
- {/* Dark Mode */}
404
- <div className="flex items-center justify-between">
405
- <div>
406
- <span className="text-sm text-primary">Dark Mode</span>
407
- <p className="text-xs text-tertiary mt-0.5">Include dark theme styles</p>
408
- </div>
409
- <label className="relative cursor-pointer">
410
- <input
411
- type="checkbox"
412
- {...register('darkMode')}
413
- className="sr-only peer"
399
+ )}
414
400
  />
415
- <div className="w-11 h-6 bg-[--bg-tertiary] rounded-full peer-checked:bg-[--color-accent] transition-colors" />
416
- <div className="absolute left-0.5 top-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform peer-checked:translate-x-5" />
417
- </label>
418
- </div>
419
- </div>
401
+ </Stack>
402
+ </Stack>
420
403
 
421
- <div className="h-px bg-[--border]" />
404
+ <Separator />
422
405
 
423
- {/* Submit */}
424
- <button
425
- type="submit"
426
- disabled={isSubmitting}
427
- className={clsx(
428
- 'w-full py-3 px-6 rounded-xl text-sm font-medium transition-all',
429
- 'bg-[--color-accent] text-white',
430
- 'hover:brightness-110',
431
- 'focus:outline-none focus:ring-2 focus:ring-[--color-accent] focus:ring-offset-2 focus:ring-offset-[--bg-primary]',
432
- 'disabled:opacity-50 disabled:cursor-not-allowed'
433
- )}
434
- >
435
- {isSubmitting ? 'Creating...' : 'Create Component'}
436
- </button>
406
+ {/* Submit */}
407
+ <Button
408
+ type="submit"
409
+ variant="primary"
410
+ size="lg"
411
+ fullWidth
412
+ disabled={isSubmitting}
413
+ >
414
+ {isSubmitting ? 'Creating...' : 'Create Component'}
415
+ </Button>
416
+ </Stack>
437
417
  </form>
438
418
  </div>
439
419
  </div>