@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
|
@@ -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;
|
|
46
|
+
return fullPalette;
|
|
49
47
|
case 'complementary':
|
|
50
|
-
return [fullPalette[1], fullPalette[3]];
|
|
48
|
+
return [fullPalette[1], fullPalette[3]];
|
|
51
49
|
case 'analogous':
|
|
52
|
-
return [fullPalette[0], fullPalette[2], fullPalette[4]];
|
|
50
|
+
return [fullPalette[0], fullPalette[2], fullPalette[4]];
|
|
53
51
|
case 'triadic':
|
|
54
|
-
return [fullPalette[0], fullPalette[2], fullPalette[4]];
|
|
52
|
+
return [fullPalette[0], fullPalette[2], fullPalette[4]];
|
|
55
53
|
case 'split-complementary':
|
|
56
|
-
return [fullPalette[0], fullPalette[2], fullPalette[4]];
|
|
54
|
+
return [fullPalette[0], fullPalette[2], fullPalette[4]];
|
|
57
55
|
case 'tetradic':
|
|
58
|
-
return [fullPalette[0], fullPalette[1], fullPalette[2], fullPalette[3]];
|
|
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
|
|
119
|
-
<div
|
|
109
|
+
<div style={{ minHeight: '100vh', backgroundColor: 'var(--bg-primary)' }}>
|
|
110
|
+
<div style={{ maxWidth: '672px', margin: '0 auto', padding: '64px 24px' }}>
|
|
120
111
|
{/* Header */}
|
|
121
|
-
<
|
|
122
|
-
<h1
|
|
123
|
-
|
|
124
|
-
|
|
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)}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<
|
|
134
|
-
Project Name
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
<div>
|
|
233
|
-
<label className="block text-sm text-primary mb-4">
|
|
234
|
-
Color Scheme
|
|
235
|
-
</label>
|
|
217
|
+
<Separator />
|
|
236
218
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
296
|
-
|
|
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
|
-
<
|
|
301
|
-
|
|
333
|
+
<Text size="xs" color="tertiary" style={{ marginTop: '8px' }}>
|
|
334
|
+
{currentScheme?.colorCount} color {currentScheme?.label.toLowerCase()} palette
|
|
335
|
+
</Text>
|
|
302
336
|
</div>
|
|
303
|
-
|
|
304
|
-
|
|
337
|
+
</Stack>
|
|
338
|
+
</Field.Control>
|
|
339
|
+
</Field>
|
|
305
340
|
|
|
306
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
<
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
</label>
|
|
418
|
-
</div>
|
|
419
|
-
</div>
|
|
401
|
+
</Stack>
|
|
402
|
+
</Stack>
|
|
420
403
|
|
|
421
|
-
|
|
404
|
+
<Separator />
|
|
422
405
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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>
|