@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.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- 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
|
+
}
|