@fragments-sdk/cli 0.2.2

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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. package/src/viewer/vite-plugin.ts +2143 -0
@@ -0,0 +1,836 @@
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, useMemo } from 'react';
5
+ import clsx from 'clsx';
6
+ import { HexColorPicker } from 'react-colorful';
7
+ import { generateColorScheme, type ColorSchemeType, getContrastColor } from '../../utils/colorSchemes.js';
8
+
9
+ const formSchema = z.object({
10
+ projectName: z.string().min(2, 'Project name must be at least 2 characters'),
11
+ headlessLibrary: z.enum(['radix', 'base-ui']),
12
+ colorSchemeType: z.enum(['monochromatic', 'complementary', 'analogous', 'triadic', 'split-complementary', 'tetradic']),
13
+ primaryColor: z.string().regex(/^#[0-9A-Fa-f]{6}$/, 'Must be a valid hex color'),
14
+ darkMode: z.boolean(),
15
+ borderRadius: z.enum(['none', 'sm', 'md', 'lg', 'full']),
16
+ });
17
+
18
+ export type CreateFormData = z.infer<typeof formSchema>;
19
+
20
+ // Scheme options with color counts
21
+ const SCHEME_OPTIONS: Array<{
22
+ value: ColorSchemeType;
23
+ label: string;
24
+ description: string;
25
+ colorCount: number;
26
+ }> = [
27
+ { value: 'monochromatic', label: 'Monochromatic', description: 'Variations of a single hue', colorCount: 5 },
28
+ { value: 'complementary', label: 'Complementary', description: 'Two opposite colors', colorCount: 2 },
29
+ { value: 'analogous', label: 'Analogous', description: 'Three adjacent colors', colorCount: 3 },
30
+ { value: 'triadic', label: 'Triadic', description: 'Three evenly spaced colors', colorCount: 3 },
31
+ { value: 'split-complementary', label: 'Split Complementary', description: 'Base + two adjacent to complement', colorCount: 3 },
32
+ { value: 'tetradic', label: 'Tetradic (Square)', description: 'Four evenly spaced colors', colorCount: 4 },
33
+ ];
34
+
35
+ // Border radius values
36
+ const RADIUS_VALUES: Record<string, string> = {
37
+ none: '0px',
38
+ sm: '4px',
39
+ md: '8px',
40
+ lg: '12px',
41
+ full: '9999px',
42
+ };
43
+
44
+ // Get the key colors for a scheme
45
+ function getSchemeKeyColors(baseColor: string, type: ColorSchemeType): string[] {
46
+ const fullPalette = generateColorScheme(baseColor, type);
47
+
48
+ switch (type) {
49
+ case 'monochromatic':
50
+ return fullPalette;
51
+ case 'complementary':
52
+ return [fullPalette[1], fullPalette[3]];
53
+ case 'analogous':
54
+ return [fullPalette[0], fullPalette[2], fullPalette[4]];
55
+ case 'triadic':
56
+ return [fullPalette[0], fullPalette[2], fullPalette[4]];
57
+ case 'split-complementary':
58
+ return [fullPalette[0], fullPalette[2], fullPalette[4]];
59
+ case 'tetradic':
60
+ return [fullPalette[0], fullPalette[1], fullPalette[2], fullPalette[3]];
61
+ default:
62
+ return fullPalette;
63
+ }
64
+ }
65
+
66
+ export function CreatePage() {
67
+ const [showColorPicker, setShowColorPicker] = useState(false);
68
+ const [showSchemeDropdown, setShowSchemeDropdown] = useState(false);
69
+ const [showExportModal, setShowExportModal] = useState(false);
70
+ const [previewDarkMode, setPreviewDarkMode] = useState(false);
71
+ const colorPickerRef = useRef<HTMLDivElement>(null);
72
+ const schemeDropdownRef = useRef<HTMLDivElement>(null);
73
+
74
+ const navigateBack = () => {
75
+ window.location.hash = '/';
76
+ };
77
+
78
+ const {
79
+ register,
80
+ handleSubmit,
81
+ watch,
82
+ control,
83
+ setValue,
84
+ formState: { errors },
85
+ } = useForm<CreateFormData>({
86
+ resolver: zodResolver(formSchema),
87
+ defaultValues: {
88
+ projectName: 'my-design-system',
89
+ headlessLibrary: 'radix',
90
+ colorSchemeType: 'monochromatic',
91
+ primaryColor: '#10a37f',
92
+ darkMode: true,
93
+ borderRadius: 'md',
94
+ },
95
+ });
96
+
97
+ const formValues = watch();
98
+ const { primaryColor, colorSchemeType, borderRadius, darkMode } = formValues;
99
+
100
+ // Generate current palette
101
+ const currentPalette = useMemo(
102
+ () => getSchemeKeyColors(primaryColor, colorSchemeType),
103
+ [primaryColor, colorSchemeType]
104
+ );
105
+ const currentScheme = SCHEME_OPTIONS.find(s => s.value === colorSchemeType);
106
+
107
+ // Generate CSS variables based on form values
108
+ const cssVariables = useMemo(() => {
109
+ const palette = generateColorScheme(primaryColor, colorSchemeType);
110
+ return {
111
+ '--color-primary': palette[0] || primaryColor,
112
+ '--color-primary-hover': palette[1] || primaryColor,
113
+ '--color-secondary': palette[2] || '#6b7280',
114
+ '--color-accent': primaryColor,
115
+ '--color-accent-hover': palette[1] || primaryColor,
116
+ '--radius': RADIUS_VALUES[borderRadius],
117
+ '--radius-sm': borderRadius === 'none' ? '0px' : '4px',
118
+ '--radius-md': borderRadius === 'none' ? '0px' : '8px',
119
+ '--radius-lg': borderRadius === 'none' ? '0px' : '12px',
120
+ };
121
+ }, [primaryColor, colorSchemeType, borderRadius]);
122
+
123
+ // Close dropdowns when clicking outside
124
+ useEffect(() => {
125
+ const handleClickOutside = (event: MouseEvent) => {
126
+ if (colorPickerRef.current && !colorPickerRef.current.contains(event.target as Node)) {
127
+ setShowColorPicker(false);
128
+ }
129
+ if (schemeDropdownRef.current && !schemeDropdownRef.current.contains(event.target as Node)) {
130
+ setShowSchemeDropdown(false);
131
+ }
132
+ };
133
+ document.addEventListener('mousedown', handleClickOutside);
134
+ return () => document.removeEventListener('mousedown', handleClickOutside);
135
+ }, []);
136
+
137
+ const handleFormSubmit = (data: CreateFormData) => {
138
+ setShowExportModal(true);
139
+ console.log('Form submitted:', data);
140
+ };
141
+
142
+ const generateExportConfig = () => {
143
+ const config = {
144
+ name: formValues.projectName,
145
+ headlessLibrary: formValues.headlessLibrary,
146
+ theme: {
147
+ colors: {
148
+ primary: primaryColor,
149
+ scheme: colorSchemeType,
150
+ palette: currentPalette,
151
+ },
152
+ borderRadius: borderRadius,
153
+ darkMode: darkMode,
154
+ },
155
+ };
156
+ return JSON.stringify(config, null, 2);
157
+ };
158
+
159
+ const generateCssVariables = () => {
160
+ const palette = generateColorScheme(primaryColor, colorSchemeType);
161
+ let css = `/* ${formValues.projectName} - Generated by Segments */
162
+ :root {
163
+ /* Primary Colors */
164
+ --color-primary: ${palette[0]};
165
+ --color-primary-foreground: ${getContrastColor(palette[0])};
166
+ --color-secondary: ${palette[2] || palette[1]};
167
+ --color-secondary-foreground: ${getContrastColor(palette[2] || palette[1])};
168
+ --color-accent: ${primaryColor};
169
+ --color-accent-foreground: ${getContrastColor(primaryColor)};
170
+
171
+ /* Palette */
172
+ ${currentPalette.map((c, i) => ` --palette-${i + 1}: ${c};`).join('\n')}
173
+
174
+ /* Border Radius */
175
+ --radius: ${RADIUS_VALUES[borderRadius]};
176
+ --radius-sm: ${borderRadius === 'none' ? '0' : '0.25rem'};
177
+ --radius-md: ${borderRadius === 'none' ? '0' : '0.5rem'};
178
+ --radius-lg: ${borderRadius === 'none' ? '0' : '0.75rem'};
179
+ --radius-full: 9999px;
180
+
181
+ /* Light Mode Surfaces */
182
+ --bg-primary: #ffffff;
183
+ --bg-secondary: #f9fafb;
184
+ --bg-tertiary: #f3f4f6;
185
+ --text-primary: #111827;
186
+ --text-secondary: #4b5563;
187
+ --text-tertiary: #9ca3af;
188
+ --border: #e5e7eb;
189
+ --border-strong: #d1d5db;
190
+ }`;
191
+
192
+ if (darkMode) {
193
+ css += `
194
+
195
+ .dark {
196
+ /* Dark Mode Surfaces */
197
+ --bg-primary: #111827;
198
+ --bg-secondary: #1f2937;
199
+ --bg-tertiary: #374151;
200
+ --text-primary: #f9fafb;
201
+ --text-secondary: #d1d5db;
202
+ --text-tertiary: #9ca3af;
203
+ --border: #374151;
204
+ --border-strong: #4b5563;
205
+ }`;
206
+ }
207
+
208
+ return css;
209
+ };
210
+
211
+ const generateTailwindConfig = () => {
212
+ return `// tailwind.config.js - Generated by Segments
213
+ /** @type {import('tailwindcss').Config} */
214
+ export default {
215
+ darkMode: 'class',
216
+ content: ['./src/**/*.{js,ts,jsx,tsx}'],
217
+ theme: {
218
+ extend: {
219
+ colors: {
220
+ primary: {
221
+ DEFAULT: '${primaryColor}',
222
+ foreground: '${getContrastColor(primaryColor)}',
223
+ },
224
+ secondary: {
225
+ DEFAULT: '${currentPalette[1] || primaryColor}',
226
+ foreground: '${getContrastColor(currentPalette[1] || primaryColor)}',
227
+ },
228
+ accent: {
229
+ DEFAULT: '${currentPalette[2] || primaryColor}',
230
+ foreground: '${getContrastColor(currentPalette[2] || primaryColor)}',
231
+ },
232
+ // Full palette
233
+ ${currentPalette.map((c, i) => ` palette${i + 1}: '${c}',`).join('\n')}
234
+ },
235
+ borderRadius: {
236
+ DEFAULT: '${RADIUS_VALUES[borderRadius]}',
237
+ },
238
+ },
239
+ },
240
+ plugins: [],
241
+ };`;
242
+ };
243
+
244
+ return (
245
+ <div className="min-h-screen bg-[--bg-primary] flex">
246
+ {/* Main Form Section */}
247
+ <div className="flex-1 overflow-y-auto">
248
+ <div className="max-w-2xl mx-auto px-6 py-16">
249
+ {/* Header */}
250
+ <div className="mb-12">
251
+ <button
252
+ onClick={navigateBack}
253
+ className="flex items-center gap-1.5 text-sm text-tertiary hover:text-primary mb-4 -ml-1 transition-colors"
254
+ >
255
+ <BackIcon className="w-4 h-4" />
256
+ Back to viewer
257
+ </button>
258
+ <h1 className="text-2xl font-semibold text-primary mb-2">
259
+ Create Your Design System
260
+ </h1>
261
+ <p className="text-secondary">
262
+ Configure your components and export to use in your project.
263
+ </p>
264
+ </div>
265
+
266
+ <form onSubmit={handleSubmit(handleFormSubmit)} className="space-y-8">
267
+ {/* Project Name */}
268
+ <div>
269
+ <label className="block text-sm text-primary mb-2">
270
+ Project Name
271
+ </label>
272
+ <input
273
+ {...register('projectName')}
274
+ placeholder="my-design-system"
275
+ className={clsx(
276
+ 'w-full px-4 py-3 rounded-xl text-sm',
277
+ 'bg-[--bg-tertiary] text-primary placeholder:text-tertiary',
278
+ 'border border-transparent',
279
+ 'focus:outline-none focus:ring-1 focus:ring-[--border-strong]',
280
+ 'transition-all',
281
+ errors.projectName && 'ring-1 ring-red-500'
282
+ )}
283
+ />
284
+ </div>
285
+
286
+ <div className="h-px bg-[--border]" />
287
+
288
+ {/* Headless Library */}
289
+ <div>
290
+ <label className="block text-sm text-primary mb-4">
291
+ Headless Library
292
+ </label>
293
+ <div className="grid grid-cols-2 gap-3">
294
+ {[
295
+ { value: 'radix', label: 'Radix UI', desc: 'Accessible primitives' },
296
+ { value: 'base-ui', label: 'Base UI', desc: 'Hooks-first approach' },
297
+ ].map((lib) => (
298
+ <label
299
+ key={lib.value}
300
+ className={clsx(
301
+ 'flex flex-col p-4 rounded-xl cursor-pointer transition-all',
302
+ 'border',
303
+ watch('headlessLibrary') === lib.value
304
+ ? 'border-[--color-accent] bg-[--color-accent]/5'
305
+ : 'border-[--border] hover:border-[--border-strong]'
306
+ )}
307
+ >
308
+ <input
309
+ type="radio"
310
+ {...register('headlessLibrary')}
311
+ value={lib.value}
312
+ className="sr-only"
313
+ />
314
+ <span className="text-sm font-medium text-primary">{lib.label}</span>
315
+ <span className="text-xs text-tertiary mt-0.5">{lib.desc}</span>
316
+ </label>
317
+ ))}
318
+ </div>
319
+ </div>
320
+
321
+ <div className="h-px bg-[--border]" />
322
+
323
+ {/* Color Scheme */}
324
+ <div>
325
+ <label className="block text-sm text-primary mb-4">
326
+ Color Scheme
327
+ </label>
328
+
329
+ <div className="space-y-4">
330
+ {/* Primary Color */}
331
+ <div className="flex items-center gap-4">
332
+ <div className="relative" ref={colorPickerRef}>
333
+ <button
334
+ type="button"
335
+ onClick={() => setShowColorPicker(!showColorPicker)}
336
+ className="w-12 h-12 rounded-lg border border-[--border] hover:border-[--border-strong] transition-colors"
337
+ style={{ backgroundColor: primaryColor }}
338
+ />
339
+ {showColorPicker && (
340
+ <div className="absolute top-14 left-0 z-50 p-3 rounded-xl bg-[--bg-elevated] border border-[--border] shadow-xl">
341
+ <Controller
342
+ name="primaryColor"
343
+ control={control}
344
+ render={({ field }) => (
345
+ <HexColorPicker color={field.value} onChange={field.onChange} />
346
+ )}
347
+ />
348
+ <input
349
+ type="text"
350
+ value={primaryColor}
351
+ onChange={(e) => setValue('primaryColor', e.target.value)}
352
+ 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]"
353
+ />
354
+ </div>
355
+ )}
356
+ </div>
357
+ <div className="flex-1">
358
+ <input
359
+ type="text"
360
+ value={primaryColor}
361
+ onChange={(e) => setValue('primaryColor', e.target.value)}
362
+ 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]"
363
+ />
364
+ </div>
365
+ </div>
366
+
367
+ {/* Custom Scheme Type Dropdown */}
368
+ <div className="relative" ref={schemeDropdownRef}>
369
+ <button
370
+ type="button"
371
+ onClick={() => setShowSchemeDropdown(!showSchemeDropdown)}
372
+ className={clsx(
373
+ 'w-full px-4 py-3 rounded-xl text-sm text-left',
374
+ 'bg-[--bg-tertiary] text-primary',
375
+ 'border border-transparent',
376
+ 'focus:outline-none focus:ring-1 focus:ring-[--border-strong]',
377
+ 'flex items-center justify-between',
378
+ showSchemeDropdown && 'ring-1 ring-[--border-strong]'
379
+ )}
380
+ >
381
+ <div className="flex items-center gap-3">
382
+ <div className="flex gap-0.5">
383
+ {getSchemeKeyColors(primaryColor, colorSchemeType).map((color, i) => (
384
+ <div
385
+ key={i}
386
+ className="w-4 h-4 rounded-sm first:rounded-l last:rounded-r"
387
+ style={{ backgroundColor: color }}
388
+ />
389
+ ))}
390
+ </div>
391
+ <span>{currentScheme?.label}</span>
392
+ <span className="text-tertiary">— {currentScheme?.description}</span>
393
+ </div>
394
+ <ChevronIcon className={clsx('w-4 h-4 text-tertiary transition-transform', showSchemeDropdown && 'rotate-180')} />
395
+ </button>
396
+
397
+ {showSchemeDropdown && (
398
+ <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 max-h-80 overflow-y-auto">
399
+ {SCHEME_OPTIONS.map((scheme) => {
400
+ const schemeColors = getSchemeKeyColors(primaryColor, scheme.value);
401
+ const isSelected = colorSchemeType === scheme.value;
402
+
403
+ return (
404
+ <button
405
+ key={scheme.value}
406
+ type="button"
407
+ onClick={() => {
408
+ setValue('colorSchemeType', scheme.value);
409
+ setShowSchemeDropdown(false);
410
+ }}
411
+ className={clsx(
412
+ 'w-full px-4 py-3 text-left flex items-center gap-3',
413
+ 'hover:bg-[--bg-hover] transition-colors',
414
+ isSelected && 'bg-[--bg-tertiary]'
415
+ )}
416
+ >
417
+ <div className="flex gap-0.5 shrink-0">
418
+ {schemeColors.map((color, i) => (
419
+ <div
420
+ key={i}
421
+ className="w-5 h-5 rounded-sm first:rounded-l last:rounded-r"
422
+ style={{ backgroundColor: color }}
423
+ />
424
+ ))}
425
+ </div>
426
+ <div className="flex-1 min-w-0">
427
+ <div className="flex items-center gap-2">
428
+ {isSelected && <span className="text-[--color-accent]">✓</span>}
429
+ <span className={clsx('text-sm', isSelected ? 'text-primary font-medium' : 'text-primary')}>
430
+ {scheme.label}
431
+ </span>
432
+ </div>
433
+ <span className="text-xs text-tertiary">{scheme.description}</span>
434
+ </div>
435
+ </button>
436
+ );
437
+ })}
438
+ </div>
439
+ )}
440
+ </div>
441
+
442
+ {/* Current Palette Preview */}
443
+ <div>
444
+ <div className="flex gap-1.5">
445
+ {currentPalette.map((color, i) => (
446
+ <div
447
+ key={i}
448
+ className="flex-1 h-12 rounded-lg first:rounded-l-xl last:rounded-r-xl transition-colors"
449
+ style={{ backgroundColor: color }}
450
+ />
451
+ ))}
452
+ </div>
453
+ <p className="text-xs text-tertiary mt-2">
454
+ {currentScheme?.colorCount} color {currentScheme?.label.toLowerCase()} palette
455
+ </p>
456
+ </div>
457
+ </div>
458
+ </div>
459
+
460
+ <div className="h-px bg-[--border]" />
461
+
462
+ {/* Style Options */}
463
+ <div className="space-y-6">
464
+ <div className="flex items-center justify-between">
465
+ <div>
466
+ <span className="text-sm text-primary">Border Radius</span>
467
+ <p className="text-xs text-tertiary mt-0.5">Component corner style</p>
468
+ </div>
469
+ <div className="flex gap-1 bg-[--bg-tertiary] p-1 rounded-lg">
470
+ {(['none', 'sm', 'md', 'lg', 'full'] as const).map((radius) => (
471
+ <label
472
+ key={radius}
473
+ className={clsx(
474
+ 'px-3 py-1.5 text-xs font-medium rounded-md cursor-pointer transition-all',
475
+ watch('borderRadius') === radius
476
+ ? 'bg-[--bg-primary] text-primary shadow-sm'
477
+ : 'text-tertiary hover:text-secondary'
478
+ )}
479
+ >
480
+ <input
481
+ type="radio"
482
+ {...register('borderRadius')}
483
+ value={radius}
484
+ className="sr-only"
485
+ />
486
+ {radius}
487
+ </label>
488
+ ))}
489
+ </div>
490
+ </div>
491
+
492
+ <div className="flex items-center justify-between">
493
+ <div>
494
+ <span className="text-sm text-primary">Dark Mode</span>
495
+ <p className="text-xs text-tertiary mt-0.5">Include dark theme styles</p>
496
+ </div>
497
+ <label className="relative cursor-pointer">
498
+ <input
499
+ type="checkbox"
500
+ {...register('darkMode')}
501
+ className="sr-only peer"
502
+ />
503
+ <div className="w-11 h-6 bg-[--bg-tertiary] rounded-full peer-checked:bg-[--color-accent] transition-colors" />
504
+ <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" />
505
+ </label>
506
+ </div>
507
+ </div>
508
+
509
+ <div className="h-px bg-[--border]" />
510
+
511
+ {/* Submit */}
512
+ <button
513
+ type="submit"
514
+ className={clsx(
515
+ 'w-full py-3 px-6 rounded-xl text-sm font-medium transition-all',
516
+ 'bg-[--color-accent] text-white',
517
+ 'hover:brightness-110',
518
+ 'focus:outline-none focus:ring-2 focus:ring-[--color-accent] focus:ring-offset-2 focus:ring-offset-[--bg-primary]'
519
+ )}
520
+ >
521
+ Export Design System
522
+ </button>
523
+ </form>
524
+ </div>
525
+ </div>
526
+
527
+ {/* Preview Sidebar */}
528
+ <div className="hidden lg:block w-96 border-l border-[--border] bg-[--bg-secondary] overflow-y-auto">
529
+ <div className="sticky top-0 p-6">
530
+ <div className="flex items-center justify-between mb-6">
531
+ <h2 className="text-sm font-medium text-primary">Live Preview</h2>
532
+ <button
533
+ onClick={() => setPreviewDarkMode(!previewDarkMode)}
534
+ className={clsx(
535
+ 'flex items-center gap-2 px-3 py-1.5 text-xs font-medium rounded-lg transition-all',
536
+ previewDarkMode
537
+ ? 'bg-gray-800 text-white'
538
+ : 'bg-white text-gray-800 border border-gray-200'
539
+ )}
540
+ >
541
+ {previewDarkMode ? <MoonIcon className="w-3.5 h-3.5" /> : <SunIcon className="w-3.5 h-3.5" />}
542
+ {previewDarkMode ? 'Dark' : 'Light'}
543
+ </button>
544
+ </div>
545
+
546
+ {/* Preview Container with custom CSS vars */}
547
+ <div
548
+ className={clsx(
549
+ 'space-y-8 p-4 rounded-xl transition-colors',
550
+ previewDarkMode ? 'bg-gray-900' : 'bg-white border border-gray-200'
551
+ )}
552
+ style={cssVariables as React.CSSProperties}
553
+ >
554
+ {/* Button Variants */}
555
+ <div>
556
+ <h3 className={clsx('text-xs font-medium uppercase tracking-wider mb-3', previewDarkMode ? 'text-gray-400' : 'text-gray-500')}>Buttons</h3>
557
+ <div className="space-y-3">
558
+ <button
559
+ className="w-full px-4 py-2.5 text-sm font-medium transition-all"
560
+ style={{
561
+ backgroundColor: primaryColor,
562
+ color: getContrastColor(primaryColor),
563
+ borderRadius: RADIUS_VALUES[borderRadius],
564
+ }}
565
+ >
566
+ Primary Button
567
+ </button>
568
+ <button
569
+ className="w-full px-4 py-2.5 text-sm font-medium border transition-all"
570
+ style={{
571
+ backgroundColor: 'transparent',
572
+ borderColor: primaryColor,
573
+ color: primaryColor,
574
+ borderRadius: RADIUS_VALUES[borderRadius],
575
+ }}
576
+ >
577
+ Secondary Button
578
+ </button>
579
+ <button
580
+ className="w-full px-4 py-2.5 text-sm font-medium transition-all"
581
+ style={{
582
+ backgroundColor: 'transparent',
583
+ color: primaryColor,
584
+ borderRadius: RADIUS_VALUES[borderRadius],
585
+ }}
586
+ >
587
+ Ghost Button
588
+ </button>
589
+ <button
590
+ className="w-full px-4 py-2.5 text-sm font-medium transition-all"
591
+ style={{
592
+ backgroundColor: '#ef4444',
593
+ color: '#ffffff',
594
+ borderRadius: RADIUS_VALUES[borderRadius],
595
+ }}
596
+ >
597
+ Danger Button
598
+ </button>
599
+ </div>
600
+ </div>
601
+
602
+ {/* Input */}
603
+ <div>
604
+ <h3 className={clsx('text-xs font-medium uppercase tracking-wider mb-3', previewDarkMode ? 'text-gray-400' : 'text-gray-500')}>Input</h3>
605
+ <input
606
+ type="text"
607
+ placeholder="Enter your email"
608
+ className={clsx(
609
+ 'w-full px-4 py-2.5 text-sm border focus:outline-none transition-all',
610
+ previewDarkMode
611
+ ? 'bg-gray-800 text-white placeholder:text-gray-500 border-gray-700'
612
+ : 'bg-gray-50 text-gray-900 placeholder:text-gray-400 border-gray-200'
613
+ )}
614
+ style={{
615
+ borderRadius: RADIUS_VALUES[borderRadius],
616
+ }}
617
+ onFocus={(e) => {
618
+ e.target.style.borderColor = primaryColor;
619
+ }}
620
+ onBlur={(e) => {
621
+ e.target.style.borderColor = previewDarkMode ? '#374151' : '#e5e7eb';
622
+ }}
623
+ />
624
+ </div>
625
+
626
+ {/* Card */}
627
+ <div>
628
+ <h3 className={clsx('text-xs font-medium uppercase tracking-wider mb-3', previewDarkMode ? 'text-gray-400' : 'text-gray-500')}>Card</h3>
629
+ <div
630
+ className={clsx(
631
+ 'p-4 border',
632
+ previewDarkMode ? 'bg-gray-800 border-gray-700' : 'bg-gray-50 border-gray-200'
633
+ )}
634
+ style={{ borderRadius: RADIUS_VALUES[borderRadius] }}
635
+ >
636
+ <h4 className={clsx('text-sm font-medium mb-1', previewDarkMode ? 'text-white' : 'text-gray-900')}>Card Title</h4>
637
+ <p className={clsx('text-xs mb-3', previewDarkMode ? 'text-gray-400' : 'text-gray-500')}>This is a sample card component with your selected styles.</p>
638
+ <button
639
+ className="px-3 py-1.5 text-xs font-medium transition-all"
640
+ style={{
641
+ backgroundColor: primaryColor,
642
+ color: getContrastColor(primaryColor),
643
+ borderRadius: RADIUS_VALUES[borderRadius],
644
+ }}
645
+ >
646
+ Action
647
+ </button>
648
+ </div>
649
+ </div>
650
+
651
+ {/* Badge */}
652
+ <div>
653
+ <h3 className={clsx('text-xs font-medium uppercase tracking-wider mb-3', previewDarkMode ? 'text-gray-400' : 'text-gray-500')}>Badges</h3>
654
+ <div className="flex flex-wrap gap-2">
655
+ {currentPalette.slice(0, 4).map((color, i) => (
656
+ <span
657
+ key={i}
658
+ className="px-2.5 py-1 text-xs font-medium"
659
+ style={{
660
+ backgroundColor: color + '20',
661
+ color: color,
662
+ borderRadius: borderRadius === 'full' ? '9999px' : RADIUS_VALUES['sm'],
663
+ }}
664
+ >
665
+ Badge {i + 1}
666
+ </span>
667
+ ))}
668
+ </div>
669
+ </div>
670
+
671
+ {/* Color Palette */}
672
+ <div>
673
+ <h3 className={clsx('text-xs font-medium uppercase tracking-wider mb-3', previewDarkMode ? 'text-gray-400' : 'text-gray-500')}>Your Palette</h3>
674
+ <div className="flex gap-2">
675
+ {currentPalette.map((color, i) => (
676
+ <div key={i} className="flex-1 text-center">
677
+ <div
678
+ className="aspect-square rounded-lg mb-1"
679
+ style={{ backgroundColor: color }}
680
+ />
681
+ <span className={clsx('text-[10px] font-mono', previewDarkMode ? 'text-gray-500' : 'text-gray-400')}>{color.slice(1, 7)}</span>
682
+ </div>
683
+ ))}
684
+ </div>
685
+ </div>
686
+ </div>
687
+ </div>
688
+ </div>
689
+
690
+ {/* Export Modal */}
691
+ {showExportModal && (
692
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
693
+ <div className="w-full max-w-2xl max-h-[80vh] overflow-hidden rounded-2xl bg-[--bg-elevated] border border-[--border] shadow-2xl">
694
+ <div className="flex items-center justify-between p-4 border-b border-[--border]">
695
+ <h2 className="text-lg font-semibold text-primary">Export Design System</h2>
696
+ <button
697
+ onClick={() => setShowExportModal(false)}
698
+ className="p-2 rounded-lg hover:bg-[--bg-hover] text-tertiary hover:text-primary transition-colors"
699
+ >
700
+ <CloseIcon className="w-5 h-5" />
701
+ </button>
702
+ </div>
703
+
704
+ <div className="p-4 overflow-y-auto max-h-[60vh] space-y-6">
705
+ {/* CLI Command */}
706
+ <div>
707
+ <h3 className="text-sm font-medium text-primary mb-2">Quick Start</h3>
708
+ <div className="flex items-center gap-2 p-3 rounded-lg bg-[--bg-tertiary] font-mono text-sm">
709
+ <span className="text-tertiary">$</span>
710
+ <code className="text-primary flex-1">npx segment-ui init {formValues.projectName}</code>
711
+ <button
712
+ onClick={() => navigator.clipboard.writeText(`npx segment-ui init ${formValues.projectName}`)}
713
+ className="px-2 py-1 text-xs rounded bg-[--bg-hover] text-secondary hover:text-primary transition-colors"
714
+ >
715
+ Copy
716
+ </button>
717
+ </div>
718
+ </div>
719
+
720
+ {/* CSS Variables */}
721
+ <div>
722
+ <div className="flex items-center justify-between mb-2">
723
+ <h3 className="text-sm font-medium text-primary">CSS Variables</h3>
724
+ <button
725
+ onClick={() => navigator.clipboard.writeText(generateCssVariables())}
726
+ className="px-2 py-1 text-xs rounded bg-[--bg-hover] text-secondary hover:text-primary transition-colors"
727
+ >
728
+ Copy
729
+ </button>
730
+ </div>
731
+ <pre className="p-4 rounded-lg bg-[--bg-tertiary] text-xs font-mono text-secondary overflow-x-auto whitespace-pre">
732
+ {generateCssVariables()}
733
+ </pre>
734
+ </div>
735
+
736
+ {/* Tailwind Config */}
737
+ <div>
738
+ <div className="flex items-center justify-between mb-2">
739
+ <h3 className="text-sm font-medium text-primary">tailwind.config.js</h3>
740
+ <button
741
+ onClick={() => navigator.clipboard.writeText(generateTailwindConfig())}
742
+ className="px-2 py-1 text-xs rounded bg-[--bg-hover] text-secondary hover:text-primary transition-colors"
743
+ >
744
+ Copy
745
+ </button>
746
+ </div>
747
+ <pre className="p-4 rounded-lg bg-[--bg-tertiary] text-xs font-mono text-secondary overflow-x-auto whitespace-pre">
748
+ {generateTailwindConfig()}
749
+ </pre>
750
+ </div>
751
+
752
+ {/* Config JSON */}
753
+ <div>
754
+ <div className="flex items-center justify-between mb-2">
755
+ <h3 className="text-sm font-medium text-primary">segment.config.json</h3>
756
+ <button
757
+ onClick={() => navigator.clipboard.writeText(generateExportConfig())}
758
+ className="px-2 py-1 text-xs rounded bg-[--bg-hover] text-secondary hover:text-primary transition-colors"
759
+ >
760
+ Copy
761
+ </button>
762
+ </div>
763
+ <pre className="p-4 rounded-lg bg-[--bg-tertiary] text-xs font-mono text-secondary overflow-x-auto whitespace-pre">
764
+ {generateExportConfig()}
765
+ </pre>
766
+ </div>
767
+ </div>
768
+
769
+ <div className="p-4 border-t border-[--border] flex justify-end gap-3">
770
+ <button
771
+ onClick={() => setShowExportModal(false)}
772
+ className="px-4 py-2 text-sm font-medium rounded-lg border border-[--border] text-secondary hover:text-primary transition-colors"
773
+ >
774
+ Close
775
+ </button>
776
+ <button
777
+ onClick={() => {
778
+ const blob = new Blob([generateExportConfig()], { type: 'application/json' });
779
+ const url = URL.createObjectURL(blob);
780
+ const a = document.createElement('a');
781
+ a.href = url;
782
+ a.download = 'segment.config.json';
783
+ a.click();
784
+ }}
785
+ className="px-4 py-2 text-sm font-medium rounded-lg text-white transition-colors"
786
+ style={{ backgroundColor: primaryColor }}
787
+ >
788
+ Download Config
789
+ </button>
790
+ </div>
791
+ </div>
792
+ </div>
793
+ )}
794
+ </div>
795
+ );
796
+ }
797
+
798
+ function ChevronIcon({ className }: { className?: string }) {
799
+ return (
800
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
801
+ <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
802
+ </svg>
803
+ );
804
+ }
805
+
806
+ function CloseIcon({ className }: { className?: string }) {
807
+ return (
808
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
809
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
810
+ </svg>
811
+ );
812
+ }
813
+
814
+ function BackIcon({ className }: { className?: string }) {
815
+ return (
816
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
817
+ <path strokeLinecap="round" strokeLinejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
818
+ </svg>
819
+ );
820
+ }
821
+
822
+ function SunIcon({ className }: { className?: string }) {
823
+ return (
824
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
825
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
826
+ </svg>
827
+ );
828
+ }
829
+
830
+ function MoonIcon({ className }: { className?: string }) {
831
+ return (
832
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
833
+ <path strokeLinecap="round" strokeLinejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
834
+ </svg>
835
+ );
836
+ }