@fragments-sdk/viewer 0.2.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 (141) hide show
  1. package/LICENSE +84 -0
  2. package/index.html +28 -0
  3. package/package.json +71 -0
  4. package/src/__tests__/a11y-fixes.test.ts +358 -0
  5. package/src/__tests__/jsx-parser.test.ts +502 -0
  6. package/src/__tests__/render-utils.test.ts +232 -0
  7. package/src/__tests__/style-utils.test.ts +404 -0
  8. package/src/app/index.ts +1 -0
  9. package/src/assets/fragments-logo.ts +4 -0
  10. package/src/assets/fragments_logo.png +0 -0
  11. package/src/components/AccessibilityPanel.tsx +1457 -0
  12. package/src/components/ActionCapture.tsx +172 -0
  13. package/src/components/ActionsPanel.tsx +332 -0
  14. package/src/components/AllVariantsPreview.tsx +78 -0
  15. package/src/components/App.tsx +604 -0
  16. package/src/components/BottomPanel.tsx +288 -0
  17. package/src/components/CodePanel.naming.test.tsx +59 -0
  18. package/src/components/CodePanel.tsx +118 -0
  19. package/src/components/CommandPalette.tsx +392 -0
  20. package/src/components/ComponentDocView.tsx +164 -0
  21. package/src/components/ComponentGraph.tsx +380 -0
  22. package/src/components/ComponentHeader.tsx +88 -0
  23. package/src/components/ContractPanel.tsx +241 -0
  24. package/src/components/DeviceMockup.tsx +156 -0
  25. package/src/components/EmptyVariantMessage.tsx +54 -0
  26. package/src/components/ErrorBoundary.tsx +97 -0
  27. package/src/components/FigmaEmbed.tsx +238 -0
  28. package/src/components/FragmentEditor.tsx +525 -0
  29. package/src/components/FragmentRenderer.tsx +61 -0
  30. package/src/components/HeaderSearch.tsx +24 -0
  31. package/src/components/HealthDashboard.tsx +441 -0
  32. package/src/components/HmrStatusIndicator.tsx +61 -0
  33. package/src/components/Icons.tsx +479 -0
  34. package/src/components/InteractionsPanel.tsx +757 -0
  35. package/src/components/IsolatedPreviewFrame.tsx +390 -0
  36. package/src/components/IsolatedRender.tsx +113 -0
  37. package/src/components/KeyboardShortcutsHelp.tsx +53 -0
  38. package/src/components/LandingPage.tsx +420 -0
  39. package/src/components/Layout.tsx +27 -0
  40. package/src/components/LeftSidebar.tsx +472 -0
  41. package/src/components/LoadErrorMessage.tsx +102 -0
  42. package/src/components/MultiViewportPreview.tsx +527 -0
  43. package/src/components/NoVariantsMessage.tsx +59 -0
  44. package/src/components/PanelShell.tsx +161 -0
  45. package/src/components/PerformancePanel.tsx +304 -0
  46. package/src/components/PreviewArea.tsx +254 -0
  47. package/src/components/PreviewAside.tsx +168 -0
  48. package/src/components/PreviewFrameHost.tsx +304 -0
  49. package/src/components/PreviewToolbar.tsx +80 -0
  50. package/src/components/PropsEditor.tsx +506 -0
  51. package/src/components/PropsTable.tsx +111 -0
  52. package/src/components/RelationsSection.tsx +88 -0
  53. package/src/components/ResizablePanel.tsx +271 -0
  54. package/src/components/RightSidebar.tsx +102 -0
  55. package/src/components/RuntimeToolsRegistrar.tsx +17 -0
  56. package/src/components/ScreenshotButton.tsx +90 -0
  57. package/src/components/ShadowPreview.tsx +204 -0
  58. package/src/components/Sidebar.tsx +169 -0
  59. package/src/components/SkeletonLoader.tsx +161 -0
  60. package/src/components/ThemeProvider.tsx +42 -0
  61. package/src/components/Toast.tsx +3 -0
  62. package/src/components/TokenStylePanel.tsx +699 -0
  63. package/src/components/TopToolbar.tsx +159 -0
  64. package/src/components/Untitled +1 -0
  65. package/src/components/UsageSection.tsx +95 -0
  66. package/src/components/VariantMatrix.tsx +391 -0
  67. package/src/components/VariantRenderer.tsx +131 -0
  68. package/src/components/VariantTabs.tsx +40 -0
  69. package/src/components/ViewerHeader.tsx +69 -0
  70. package/src/components/ViewerStateSync.tsx +52 -0
  71. package/src/components/ViewportSelector.tsx +172 -0
  72. package/src/components/WebMCPDevTools.tsx +503 -0
  73. package/src/components/WebMCPIntegration.tsx +47 -0
  74. package/src/components/WebMCPStatusIndicator.tsx +60 -0
  75. package/src/components/_future/CreatePage.tsx +835 -0
  76. package/src/components/viewer-utils.ts +16 -0
  77. package/src/composition-renderer.ts +381 -0
  78. package/src/constants/index.ts +1 -0
  79. package/src/constants/ui.ts +166 -0
  80. package/src/entry.tsx +335 -0
  81. package/src/hooks/index.ts +2 -0
  82. package/src/hooks/useA11yCache.ts +383 -0
  83. package/src/hooks/useA11yService.ts +364 -0
  84. package/src/hooks/useActions.ts +138 -0
  85. package/src/hooks/useAppState.ts +147 -0
  86. package/src/hooks/useCompiledFragments.ts +42 -0
  87. package/src/hooks/useFigmaIntegration.ts +132 -0
  88. package/src/hooks/useHmrStatus.ts +109 -0
  89. package/src/hooks/useKeyboardShortcuts.ts +270 -0
  90. package/src/hooks/usePreviewBridge.ts +347 -0
  91. package/src/hooks/useScrollSpy.ts +78 -0
  92. package/src/hooks/useShadowStyles.ts +221 -0
  93. package/src/hooks/useUrlState.ts +318 -0
  94. package/src/hooks/useViewSettings.ts +111 -0
  95. package/src/intelligence/healthReport.ts +505 -0
  96. package/src/intelligence/styleDrift.ts +340 -0
  97. package/src/intelligence/usageScanner.ts +309 -0
  98. package/src/jsx-parser.ts +486 -0
  99. package/src/preview-frame-entry.tsx +25 -0
  100. package/src/preview-frame.html +148 -0
  101. package/src/render-template.html +68 -0
  102. package/src/render-utils.ts +311 -0
  103. package/src/shared/ComponentDocContent.module.scss +10 -0
  104. package/src/shared/ComponentDocContent.module.scss.d.ts +2 -0
  105. package/src/shared/ComponentDocContent.tsx +274 -0
  106. package/src/shared/DocsHeaderBar.tsx +129 -0
  107. package/src/shared/DocsPageAsideHost.tsx +89 -0
  108. package/src/shared/DocsPageShell.tsx +124 -0
  109. package/src/shared/DocsSearchCommand.tsx +99 -0
  110. package/src/shared/DocsSidebarNav.tsx +66 -0
  111. package/src/shared/PropsTable.module.scss +68 -0
  112. package/src/shared/PropsTable.module.scss.d.ts +2 -0
  113. package/src/shared/PropsTable.tsx +76 -0
  114. package/src/shared/VariantPreviewCard.module.scss +114 -0
  115. package/src/shared/VariantPreviewCard.module.scss.d.ts +2 -0
  116. package/src/shared/VariantPreviewCard.tsx +137 -0
  117. package/src/shared/docs-data/index.ts +32 -0
  118. package/src/shared/docs-data/mcp-configs.ts +72 -0
  119. package/src/shared/docs-data/palettes.ts +75 -0
  120. package/src/shared/docs-data/setup-examples.ts +55 -0
  121. package/src/shared/docs-layout.scss +28 -0
  122. package/src/shared/docs-layout.scss.d.ts +2 -0
  123. package/src/shared/index.ts +34 -0
  124. package/src/shared/types.ts +53 -0
  125. package/src/style-utils.ts +414 -0
  126. package/src/styles/globals.css +278 -0
  127. package/src/types/a11y.ts +197 -0
  128. package/src/utils/a11y-fixes.ts +509 -0
  129. package/src/utils/actionExport.ts +372 -0
  130. package/src/utils/colorSchemes.ts +201 -0
  131. package/src/utils/contrast.ts +246 -0
  132. package/src/utils/detectRelationships.ts +256 -0
  133. package/src/webmcp/__tests__/analytics.test.ts +108 -0
  134. package/src/webmcp/analytics.ts +165 -0
  135. package/src/webmcp/index.ts +3 -0
  136. package/src/webmcp/posthog-bridge.ts +39 -0
  137. package/src/webmcp/runtime-tools.ts +152 -0
  138. package/src/webmcp/scan-utils.ts +135 -0
  139. package/src/webmcp/use-tool-analytics.ts +69 -0
  140. package/src/webmcp/viewer-state.ts +45 -0
  141. package/tsconfig.json +20 -0
@@ -0,0 +1,420 @@
1
+ import { useForm, Controller } from 'react-hook-form';
2
+ import { zodResolver } from '@hookform/resolvers/zod';
3
+ import { z } from 'zod';
4
+ import { useState, useRef, useEffect } from 'react';
5
+ import { HexColorPicker } from 'react-colorful';
6
+ import { Box, Button, Input, Toggle, Stack, Text, Card, Field, Separator, Select } from '@fragments-sdk/ui';
7
+ import { generateColorScheme, type ColorSchemeType } from '../utils/colorSchemes.js';
8
+ import { ChevronDownIcon } from './Icons.js';
9
+
10
+ const formSchema = z.object({
11
+ projectName: z.string().min(2, 'Project name must be at least 2 characters'),
12
+ componentName: z.string().min(2, 'Component name must be at least 2 characters'),
13
+ description: z.string().optional(),
14
+ headlessLibrary: z.enum(['radix', 'base-ui']),
15
+ colorSchemeType: z.enum(['monochromatic', 'complementary', 'analogous', 'triadic', 'split-complementary', 'tetradic']),
16
+ primaryColor: z.string().regex(/^#[0-9A-Fa-f]{6}$/, 'Must be a valid hex color'),
17
+ darkMode: z.boolean(),
18
+ borderRadius: z.enum(['none', 'sm', 'md', 'lg', 'full']),
19
+ });
20
+
21
+ export type LandingFormData = z.infer<typeof formSchema>;
22
+
23
+ interface LandingPageProps {
24
+ onSubmit?: (data: LandingFormData) => void;
25
+ }
26
+
27
+ const SCHEME_OPTIONS: Array<{
28
+ value: ColorSchemeType;
29
+ label: string;
30
+ description: string;
31
+ colorCount: number;
32
+ }> = [
33
+ { value: 'monochromatic', label: 'Monochromatic', description: 'Variations of a single hue', colorCount: 5 },
34
+ { value: 'complementary', label: 'Complementary', description: 'Two opposite colors', colorCount: 2 },
35
+ { value: 'analogous', label: 'Analogous', description: 'Three adjacent colors', colorCount: 3 },
36
+ { value: 'triadic', label: 'Triadic', description: 'Three evenly spaced colors', colorCount: 3 },
37
+ { value: 'split-complementary', label: 'Split Complementary', description: 'Base + two adjacent to complement', colorCount: 3 },
38
+ { value: 'tetradic', label: 'Tetradic (Square)', description: 'Four evenly spaced colors', colorCount: 4 },
39
+ ];
40
+
41
+ function getSchemeKeyColors(baseColor: string, type: ColorSchemeType): string[] {
42
+ const fullPalette = generateColorScheme(baseColor, type);
43
+
44
+ switch (type) {
45
+ case 'monochromatic':
46
+ return fullPalette;
47
+ case 'complementary':
48
+ return [fullPalette[1], fullPalette[3]];
49
+ case 'analogous':
50
+ return [fullPalette[0], fullPalette[2], fullPalette[4]];
51
+ case 'triadic':
52
+ return [fullPalette[0], fullPalette[2], fullPalette[4]];
53
+ case 'split-complementary':
54
+ return [fullPalette[0], fullPalette[2], fullPalette[4]];
55
+ case 'tetradic':
56
+ return [fullPalette[0], fullPalette[1], fullPalette[2], fullPalette[3]];
57
+ default:
58
+ return fullPalette;
59
+ }
60
+ }
61
+
62
+ export function LandingPage({ onSubmit }: LandingPageProps) {
63
+ const [showColorPicker, setShowColorPicker] = useState(false);
64
+ const colorPickerRef = useRef<HTMLDivElement>(null);
65
+
66
+ const {
67
+ register,
68
+ handleSubmit,
69
+ watch,
70
+ control,
71
+ setValue,
72
+ formState: { errors, isSubmitting },
73
+ } = useForm<LandingFormData>({
74
+ resolver: zodResolver(formSchema),
75
+ defaultValues: {
76
+ projectName: '',
77
+ componentName: '',
78
+ description: '',
79
+ headlessLibrary: 'radix',
80
+ colorSchemeType: 'monochromatic',
81
+ primaryColor: '#10a37f',
82
+ darkMode: true,
83
+ borderRadius: 'md',
84
+ },
85
+ });
86
+
87
+ const primaryColor = watch('primaryColor');
88
+ const colorSchemeType = watch('colorSchemeType');
89
+
90
+ const currentPalette = getSchemeKeyColors(primaryColor, colorSchemeType);
91
+ const currentScheme = SCHEME_OPTIONS.find(s => s.value === colorSchemeType);
92
+
93
+ useEffect(() => {
94
+ const handleClickOutside = (event: MouseEvent) => {
95
+ if (colorPickerRef.current && !colorPickerRef.current.contains(event.target as Node)) {
96
+ setShowColorPicker(false);
97
+ }
98
+ };
99
+ document.addEventListener('mousedown', handleClickOutside);
100
+ return () => document.removeEventListener('mousedown', handleClickOutside);
101
+ }, []);
102
+
103
+ const handleFormSubmit = (data: LandingFormData) => {
104
+ onSubmit?.(data);
105
+ };
106
+
107
+ return (
108
+ <Box background="primary" style={{ minHeight: '100vh' }}>
109
+ <div style={{ maxWidth: '672px', margin: '0 auto', padding: '64px 24px' }}>
110
+ {/* Header */}
111
+ <Stack direction="column" gap="sm" style={{ marginBottom: '48px' }}>
112
+ <Text as="h1" size="xl" weight="semibold">Create Component</Text>
113
+ <Text color="secondary">Configure your component settings below.</Text>
114
+ </Stack>
115
+
116
+ <form onSubmit={handleSubmit(handleFormSubmit)}>
117
+ <Stack direction="column" gap="lg">
118
+ {/* Project Name */}
119
+ <Field>
120
+ <Field.Label>Project Name</Field.Label>
121
+ <Field.Control>
122
+ <Input
123
+ {...register('projectName')}
124
+ placeholder="my-design-system"
125
+ style={errors.projectName ? { boxShadow: '0 0 0 1px #ef4444' } : {}}
126
+ />
127
+ </Field.Control>
128
+ {errors.projectName && (
129
+ <Field.Error>{errors.projectName.message}</Field.Error>
130
+ )}
131
+ </Field>
132
+
133
+ {/* Component Name */}
134
+ <Field>
135
+ <Field.Label>Component Name</Field.Label>
136
+ <Field.Control>
137
+ <Input
138
+ {...register('componentName')}
139
+ placeholder="Button"
140
+ style={errors.componentName ? { boxShadow: '0 0 0 1px #ef4444' } : {}}
141
+ />
142
+ </Field.Control>
143
+ {errors.componentName && (
144
+ <Field.Error>{errors.componentName.message}</Field.Error>
145
+ )}
146
+ </Field>
147
+
148
+ {/* Description */}
149
+ <Field>
150
+ <Field.Label>Description</Field.Label>
151
+ <Field.Control>
152
+ <textarea
153
+ {...register('description')}
154
+ rows={2}
155
+ placeholder="A brief description of the component"
156
+ style={{
157
+ width: '100%',
158
+ padding: '12px 16px',
159
+ borderRadius: '12px',
160
+ fontSize: '14px',
161
+ backgroundColor: 'var(--bg-tertiary)',
162
+ color: 'var(--text-primary)',
163
+ border: '1px solid transparent',
164
+ outline: 'none',
165
+ transition: 'all 150ms',
166
+ resize: 'none',
167
+ }}
168
+ onFocus={(e) => { e.currentTarget.style.boxShadow = '0 0 0 1px var(--border-strong)'; }}
169
+ onBlur={(e) => { e.currentTarget.style.boxShadow = 'none'; }}
170
+ />
171
+ </Field.Control>
172
+ </Field>
173
+
174
+ <Separator />
175
+
176
+ {/* Headless Library */}
177
+ <Field>
178
+ <Field.Label>Headless Library</Field.Label>
179
+ <Field.Control>
180
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
181
+ {[
182
+ { value: 'radix', label: 'Radix UI', desc: 'Accessible primitives' },
183
+ { value: 'base-ui', label: 'Base UI', desc: 'Hooks-first approach' },
184
+ ].map((lib) => (
185
+ <label
186
+ key={lib.value}
187
+ style={{
188
+ display: 'flex',
189
+ flexDirection: 'column',
190
+ padding: '16px',
191
+ borderRadius: '12px',
192
+ cursor: 'pointer',
193
+ transition: 'all 150ms',
194
+ border: watch('headlessLibrary') === lib.value
195
+ ? '1px solid var(--color-accent)'
196
+ : '1px solid var(--border)',
197
+ backgroundColor: watch('headlessLibrary') === lib.value
198
+ ? 'rgba(16, 163, 127, 0.05)'
199
+ : 'transparent',
200
+ }}
201
+ >
202
+ <input
203
+ type="radio"
204
+ {...register('headlessLibrary')}
205
+ value={lib.value}
206
+ style={{ position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0,0,0,0)' }}
207
+ />
208
+ <Text size="sm" weight="medium">{lib.label}</Text>
209
+ <Text size="xs" color="tertiary" style={{ marginTop: '2px' }}>{lib.desc}</Text>
210
+ </label>
211
+ ))}
212
+ </div>
213
+ </Field.Control>
214
+ </Field>
215
+
216
+ <Separator />
217
+
218
+ {/* Color Scheme */}
219
+ <Field>
220
+ <Field.Label>Color Scheme</Field.Label>
221
+ <Field.Control>
222
+ <Stack direction="column" gap="md">
223
+ {/* Primary Color */}
224
+ <Stack direction="row" align="center" gap="md">
225
+ <div style={{ position: 'relative' }} ref={colorPickerRef}>
226
+ <button
227
+ type="button"
228
+ onClick={() => setShowColorPicker(!showColorPicker)}
229
+ style={{
230
+ width: '48px',
231
+ height: '48px',
232
+ borderRadius: '8px',
233
+ border: '1px solid var(--border)',
234
+ cursor: 'pointer',
235
+ backgroundColor: primaryColor,
236
+ }}
237
+ />
238
+ {showColorPicker && (
239
+ <div style={{
240
+ position: 'absolute',
241
+ top: '56px',
242
+ left: 0,
243
+ zIndex: 50,
244
+ padding: '12px',
245
+ borderRadius: '12px',
246
+ backgroundColor: 'var(--bg-elevated)',
247
+ border: '1px solid var(--border)',
248
+ boxShadow: 'var(--shadow-lg)',
249
+ }}>
250
+ <Controller
251
+ name="primaryColor"
252
+ control={control}
253
+ render={({ field }) => (
254
+ <HexColorPicker color={field.value} onChange={field.onChange} />
255
+ )}
256
+ />
257
+ <Input
258
+ value={primaryColor}
259
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue('primaryColor', e.target.value)}
260
+ size="sm"
261
+ style={{ width: '100%', marginTop: '12px', fontFamily: 'monospace' }}
262
+ />
263
+ </div>
264
+ )}
265
+ </div>
266
+ <div style={{ flex: 1 }}>
267
+ <Input
268
+ value={primaryColor}
269
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue('primaryColor', e.target.value)}
270
+ style={{ fontFamily: 'monospace' }}
271
+ />
272
+ </div>
273
+ </Stack>
274
+
275
+ {/* Scheme Type Dropdown */}
276
+ <Select
277
+ value={colorSchemeType}
278
+ onValueChange={(value: string) => setValue('colorSchemeType', value as ColorSchemeType)}
279
+ >
280
+ <Select.Trigger>
281
+ <Stack direction="row" align="center" gap="sm">
282
+ <div style={{ display: 'flex', gap: '2px' }}>
283
+ {getSchemeKeyColors(primaryColor, colorSchemeType).map((color, i) => (
284
+ <div
285
+ key={i}
286
+ style={{ width: '16px', height: '16px', borderRadius: '2px', backgroundColor: color }}
287
+ />
288
+ ))}
289
+ </div>
290
+ <span>{currentScheme?.label}</span>
291
+ <Text as="span" color="tertiary">-- {currentScheme?.description}</Text>
292
+ </Stack>
293
+ </Select.Trigger>
294
+ <Select.Content>
295
+ {SCHEME_OPTIONS.map((scheme) => (
296
+ <Select.Item key={scheme.value} value={scheme.value}>
297
+ <Stack direction="row" align="center" gap="sm">
298
+ <div style={{ display: 'flex', gap: '2px', flexShrink: 0 }}>
299
+ {getSchemeKeyColors(primaryColor, scheme.value).map((color, i) => (
300
+ <div
301
+ key={i}
302
+ style={{ width: '20px', height: '20px', borderRadius: '2px', backgroundColor: color }}
303
+ />
304
+ ))}
305
+ </div>
306
+ <div>
307
+ <Text size="sm">{scheme.label}</Text>
308
+ <Text size="xs" color="tertiary">{scheme.description}</Text>
309
+ </div>
310
+ </Stack>
311
+ </Select.Item>
312
+ ))}
313
+ </Select.Content>
314
+ </Select>
315
+
316
+ {/* Current Palette Preview */}
317
+ <div>
318
+ <div style={{ display: 'flex', gap: '6px' }}>
319
+ {currentPalette.map((color, i) => (
320
+ <div
321
+ key={i}
322
+ style={{
323
+ flex: 1,
324
+ height: '48px',
325
+ borderRadius: '8px',
326
+ transition: 'background-color 150ms',
327
+ backgroundColor: color,
328
+ }}
329
+ />
330
+ ))}
331
+ </div>
332
+ <Text size="xs" color="tertiary" style={{ marginTop: '8px' }}>
333
+ {currentScheme?.colorCount} color {currentScheme?.label.toLowerCase()} palette
334
+ </Text>
335
+ </div>
336
+ </Stack>
337
+ </Field.Control>
338
+ </Field>
339
+
340
+ <Separator />
341
+
342
+ {/* Style Options */}
343
+ <Stack direction="column" gap="lg">
344
+ {/* Border Radius */}
345
+ <Stack direction="row" align="center" justify="between">
346
+ <div>
347
+ <Text size="sm">Border Radius</Text>
348
+ <Text size="xs" color="tertiary" style={{ marginTop: '2px' }}>Component corner style</Text>
349
+ </div>
350
+ <div style={{
351
+ display: 'flex',
352
+ gap: '4px',
353
+ backgroundColor: 'var(--bg-tertiary)',
354
+ padding: '4px',
355
+ borderRadius: '8px',
356
+ }}>
357
+ {(['none', 'sm', 'md', 'lg', 'full'] as const).map((radius) => (
358
+ <label
359
+ key={radius}
360
+ style={{
361
+ padding: '6px 12px',
362
+ fontSize: '12px',
363
+ fontWeight: 500,
364
+ borderRadius: '6px',
365
+ cursor: 'pointer',
366
+ transition: 'all 150ms',
367
+ color: watch('borderRadius') === radius ? 'var(--text-primary)' : 'var(--text-tertiary)',
368
+ backgroundColor: watch('borderRadius') === radius ? 'var(--bg-primary)' : 'transparent',
369
+ boxShadow: watch('borderRadius') === radius ? 'var(--shadow-sm)' : 'none',
370
+ }}
371
+ >
372
+ <input
373
+ type="radio"
374
+ {...register('borderRadius')}
375
+ value={radius}
376
+ style={{ position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0,0,0,0)' }}
377
+ />
378
+ {radius}
379
+ </label>
380
+ ))}
381
+ </div>
382
+ </Stack>
383
+
384
+ {/* Dark Mode */}
385
+ <Stack direction="row" align="center" justify="between">
386
+ <div>
387
+ <Text size="sm">Dark Mode</Text>
388
+ <Text size="xs" color="tertiary" style={{ marginTop: '2px' }}>Include dark theme styles</Text>
389
+ </div>
390
+ <Controller
391
+ name="darkMode"
392
+ control={control}
393
+ render={({ field }) => (
394
+ <Toggle
395
+ checked={field.value}
396
+ onCheckedChange={field.onChange}
397
+ />
398
+ )}
399
+ />
400
+ </Stack>
401
+ </Stack>
402
+
403
+ <Separator />
404
+
405
+ {/* Submit */}
406
+ <Button
407
+ type="submit"
408
+ variant="primary"
409
+ size="lg"
410
+ fullWidth
411
+ disabled={isSubmitting}
412
+ >
413
+ {isSubmitting ? 'Creating...' : 'Create Component'}
414
+ </Button>
415
+ </Stack>
416
+ </form>
417
+ </div>
418
+ </Box>
419
+ );
420
+ }
@@ -0,0 +1,27 @@
1
+ import type { ReactNode } from 'react';
2
+ import { DocsPageShell } from '../shared/DocsPageShell';
3
+
4
+ interface LayoutProps {
5
+ leftSidebar: ReactNode;
6
+ header: ReactNode;
7
+ children: ReactNode;
8
+ aside?: ReactNode;
9
+ }
10
+
11
+ export function Layout({ leftSidebar, header, children, aside }: LayoutProps) {
12
+ return (
13
+ <DocsPageShell
14
+ header={header}
15
+ sidebar={leftSidebar}
16
+ sidebarWidth="260px"
17
+ sidebarCollapsible="icon"
18
+ sidebarAriaLabel="Preview sidebar"
19
+ mainAriaLabel="Preview content"
20
+ mainPadding="none"
21
+ aside={aside}
22
+ asideWidth="240px"
23
+ >
24
+ {children}
25
+ </DocsPageShell>
26
+ );
27
+ }