gradient-forge 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +3 -0
- package/.github/FUNDING.yml +2 -0
- package/README.md +140 -0
- package/app/docs/page.tsx +417 -0
- package/app/gallery/page.tsx +398 -0
- package/app/globals.css +1155 -0
- package/app/layout.tsx +36 -0
- package/app/page.tsx +600 -0
- package/app/showcase/page.tsx +730 -0
- package/app/studio/page.tsx +1310 -0
- package/cli/index.mjs +1141 -0
- package/cli/templates/theme-context.tsx +120 -0
- package/cli/templates/theme-engine.ts +237 -0
- package/cli/templates/themes.css +512 -0
- package/components/site/component-showcase.tsx +623 -0
- package/components/site/site-data.ts +103 -0
- package/components/site/site-header.tsx +270 -0
- package/components/templates/blog.tsx +198 -0
- package/components/templates/components-showcase.tsx +298 -0
- package/components/templates/dashboard.tsx +246 -0
- package/components/templates/ecommerce.tsx +199 -0
- package/components/templates/mail.tsx +275 -0
- package/components/templates/saas-landing.tsx +169 -0
- package/components/theme/studio-code-panel.tsx +485 -0
- package/components/theme/theme-context.tsx +120 -0
- package/components/theme/theme-engine.ts +237 -0
- package/components/theme/theme-exporter.tsx +369 -0
- package/components/theme/theme-panel.tsx +268 -0
- package/components/theme/token-export-utils.ts +1211 -0
- package/components/ui/animated.tsx +55 -0
- package/components/ui/avatar.tsx +38 -0
- package/components/ui/badge.tsx +32 -0
- package/components/ui/button.tsx +65 -0
- package/components/ui/card.tsx +56 -0
- package/components/ui/checkbox.tsx +19 -0
- package/components/ui/command-palette.tsx +245 -0
- package/components/ui/gsap-animated.tsx +436 -0
- package/components/ui/input.tsx +17 -0
- package/components/ui/select.tsx +176 -0
- package/components/ui/skeleton.tsx +102 -0
- package/components/ui/switch.tsx +43 -0
- package/components/ui/tabs.tsx +115 -0
- package/components/ui/toast.tsx +119 -0
- package/gradient-forge/theme-context.tsx +119 -0
- package/gradient-forge/theme-engine.ts +236 -0
- package/gradient-forge/themes.css +556 -0
- package/lib/animations.ts +50 -0
- package/lib/gsap.ts +426 -0
- package/lib/utils.ts +6 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +6 -0
- package/package.json +53 -0
- package/postcss.config.mjs +5 -0
- package/tailwind.config.ts +15 -0
- package/tsconfig.json +43 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,1310 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useRef } from "react";
|
|
4
|
+
import gsap from "gsap";
|
|
5
|
+
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
|
6
|
+
import { useThemeContext } from "@/components/theme/theme-context";
|
|
7
|
+
import { NITRO_ALL_THEMES, type ThemeId } from "@/components/theme/theme-engine";
|
|
8
|
+
import { getThemeTokens } from "@/components/theme/token-export-utils";
|
|
9
|
+
import { SiteHeader } from "@/components/site/site-header";
|
|
10
|
+
import { Button } from "@/components/ui/button";
|
|
11
|
+
import { Badge } from "@/components/ui/badge";
|
|
12
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
13
|
+
import { Input } from "@/components/ui/input";
|
|
14
|
+
import { Switch } from "@/components/ui/switch";
|
|
15
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
16
|
+
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
17
|
+
import {
|
|
18
|
+
Tabs,
|
|
19
|
+
TabsContent,
|
|
20
|
+
TabsList,
|
|
21
|
+
TabsTrigger
|
|
22
|
+
} from "@/components/ui/tabs";
|
|
23
|
+
import { useToast } from "@/components/ui/toast";
|
|
24
|
+
import {
|
|
25
|
+
AnimatedSection,
|
|
26
|
+
StaggerContainer,
|
|
27
|
+
MagneticButton,
|
|
28
|
+
FloatingElement
|
|
29
|
+
} from "@/components/ui/gsap-animated";
|
|
30
|
+
import {
|
|
31
|
+
Palette,
|
|
32
|
+
Layers,
|
|
33
|
+
Code2,
|
|
34
|
+
Download,
|
|
35
|
+
Zap,
|
|
36
|
+
Check,
|
|
37
|
+
Copy,
|
|
38
|
+
Smartphone,
|
|
39
|
+
Tablet,
|
|
40
|
+
Monitor,
|
|
41
|
+
Sparkles,
|
|
42
|
+
Eye,
|
|
43
|
+
Settings,
|
|
44
|
+
Sun,
|
|
45
|
+
Moon,
|
|
46
|
+
ChevronRight,
|
|
47
|
+
Search,
|
|
48
|
+
Bell,
|
|
49
|
+
User,
|
|
50
|
+
Mail,
|
|
51
|
+
Loader2,
|
|
52
|
+
AlertCircle,
|
|
53
|
+
Info,
|
|
54
|
+
CheckCircle2,
|
|
55
|
+
X,
|
|
56
|
+
Trash2,
|
|
57
|
+
Plus,
|
|
58
|
+
MoreHorizontal,
|
|
59
|
+
Layout,
|
|
60
|
+
Type,
|
|
61
|
+
Image as ImageIcon,
|
|
62
|
+
Component,
|
|
63
|
+
Palette as PaletteIcon,
|
|
64
|
+
Wand2,
|
|
65
|
+
FileCode,
|
|
66
|
+
Download as DownloadIcon,
|
|
67
|
+
Share2,
|
|
68
|
+
Moon as MoonIcon,
|
|
69
|
+
Sun as SunIcon
|
|
70
|
+
} from "lucide-react";
|
|
71
|
+
import { cn } from "@/lib/utils";
|
|
72
|
+
|
|
73
|
+
if (typeof window !== "undefined") {
|
|
74
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Export formats
|
|
78
|
+
const exportFormats = [
|
|
79
|
+
{ id: "css", label: "CSS", icon: FileCode },
|
|
80
|
+
{ id: "scss", label: "SCSS", icon: FileCode },
|
|
81
|
+
{ id: "json", label: "JSON", icon: FileCode },
|
|
82
|
+
{ id: "tailwind", label: "Tailwind", icon: FileCode },
|
|
83
|
+
{ id: "w3c", label: "W3C Tokens", icon: FileCode },
|
|
84
|
+
{ id: "figma", label: "Figma", icon: FileCode },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// Component preview sections
|
|
88
|
+
const previewSections = [
|
|
89
|
+
{ id: "overview", label: "Overview", icon: Layout },
|
|
90
|
+
{ id: "buttons", label: "Buttons", icon: Component },
|
|
91
|
+
{ id: "inputs", label: "Inputs", icon: Type },
|
|
92
|
+
{ id: "cards", label: "Cards", icon: ImageIcon },
|
|
93
|
+
{ id: "feedback", label: "Feedback", icon: PaletteIcon },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
// Core theme files with their content
|
|
97
|
+
const coreFilesData: Record<string, { filename: string; description: string; extension: string; content: string }> = {
|
|
98
|
+
"theme-engine": {
|
|
99
|
+
filename: "components/theme/theme-engine.ts",
|
|
100
|
+
description: "Core theme logic & definitions",
|
|
101
|
+
extension: "ts",
|
|
102
|
+
content: `export const NITRO_PUBLIC_THEMES = [
|
|
103
|
+
{ id: "theme-nitro-mint-apple", label: "Mint Apple", preview: "radial-gradient(circle at 12% 10%, #b2ffe1 0%, transparent 42%), linear-gradient(145deg, #2d8e74 0%, #70c76a 46%, #d5ef91 100%)" },
|
|
104
|
+
{ id: "theme-nitro-citrus-sherbert", label: "Citrus Sherbert", preview: "radial-gradient(circle at 10% 10%, #ffd37d 0%, transparent 42%), linear-gradient(145deg, #e67d35 0%, #f7bb54 46%, #fff0a3 100%)" },
|
|
105
|
+
{ id: "theme-nitro-retro-raincloud", label: "Retro Raincloud", preview: "radial-gradient(circle at 12% 10%, #b3c6dc 0%, transparent 42%), linear-gradient(145deg, #4e6077 0%, #6f86a0 46%, #98aec3 100%)" },
|
|
106
|
+
{ id: "theme-nitro-hanami", label: "Hanami", preview: "radial-gradient(circle at 12% 10%, #ffd5e7 0%, transparent 42%), linear-gradient(145deg, #995382 0%, #c77ca9 46%, #f0b7ce 100%)" },
|
|
107
|
+
{ id: "theme-nitro-sunrise", label: "Sunrise", preview: "radial-gradient(circle at 12% 10%, #ffb596 0%, transparent 42%), linear-gradient(145deg, #e25263 0%, #ef8a57 46%, #ffd07a 100%)" },
|
|
108
|
+
{ id: "theme-nitro-cotton-candy", label: "Cotton Candy", preview: "radial-gradient(circle at 12% 10%, #c8e9ff 0%, transparent 42%), linear-gradient(145deg, #5aa2ff 0%, #9b78f0 46%, #f39bca 100%)" },
|
|
109
|
+
{ id: "theme-nitro-lofi-vibes", label: "Lofi Vibes", preview: "radial-gradient(circle at 10% 10%, #8e97c6 0%, transparent 42%), linear-gradient(145deg, #3f476c 0%, #59608f 46%, #7a6f9f 100%)" },
|
|
110
|
+
{ id: "theme-nitro-desert-khaki", label: "Desert Khaki", preview: "radial-gradient(circle at 10% 10%, #d5bf92 0%, transparent 42%), linear-gradient(145deg, #6d5c49 0%, #8f7a5d 46%, #b49f76 100%)" },
|
|
111
|
+
{ id: "theme-nitro-sunset", label: "Sunset", preview: "radial-gradient(circle at 10% 10%, #d66a82 0%, transparent 42%), linear-gradient(145deg, #3f1b4d 0%, #8c335f 46%, #f4874f 100%)" },
|
|
112
|
+
{ id: "theme-nitro-chroma-glow", label: "Chroma Glow", preview: "radial-gradient(circle at 10% 10%, #7f8dff 0%, transparent 42%), linear-gradient(145deg, #2d3eff 0%, #a726fa 46%, #00c7ff 100%)" },
|
|
113
|
+
{ id: "theme-nitro-forest", label: "Forest", preview: "radial-gradient(circle at 10% 10%, #56be87 0%, transparent 42%), linear-gradient(145deg, #163f2e 0%, #246b49 46%, #59a86c 100%)" },
|
|
114
|
+
{ id: "theme-nitro-crimson", label: "Crimson", preview: "radial-gradient(circle at 10% 10%, #c23956 0%, transparent 42%), linear-gradient(145deg, #2d050f 0%, #681126 46%, #a82435 100%)" },
|
|
115
|
+
{ id: "theme-nitro-midnight-blurple", label: "Midnight Blurple", preview: "radial-gradient(circle at 10% 10%, #6d6eff 0%, transparent 42%), linear-gradient(145deg, #0f1232 0%, #25366f 46%, #5757dc 100%)" },
|
|
116
|
+
{ id: "theme-nitro-mars", label: "Mars", preview: "radial-gradient(circle at 10% 10%, #cc654c 0%, transparent 42%), linear-gradient(145deg, #2e140f 0%, #5e261d 46%, #9c422f 100%)" },
|
|
117
|
+
{ id: "theme-nitro-dusk", label: "Dusk", preview: "radial-gradient(circle at 10% 10%, #9475c1 0%, transparent 42%), linear-gradient(145deg, #1b1632 0%, #3b2d5b 46%, #745495 100%)" },
|
|
118
|
+
{ id: "theme-nitro-under-the-sea", label: "Under The Sea", preview: "radial-gradient(circle at 10% 10%, #3ea6b7 0%, transparent 42%), linear-gradient(145deg, #0b2242 0%, #0d4f69 46%, #2b848e 100%)" },
|
|
119
|
+
{ id: "theme-nitro-retro-storm", label: "Retro Storm", preview: "radial-gradient(circle at 10% 10%, #7e95ab 0%, transparent 42%), linear-gradient(145deg, #1d2b3a 0%, #354657 46%, #55667a 100%)" },
|
|
120
|
+
{ id: "theme-nitro-neon-nights", label: "Neon Nights", preview: "radial-gradient(circle at 10% 10%, #d13eff 0%, transparent 42%), linear-gradient(145deg, #05061a 0%, #180f52 46%, #00bde6 100%)" },
|
|
121
|
+
{ id: "theme-nitro-strawberry-lemonade", label: "Strawberry Lemonade", preview: "radial-gradient(circle at 10% 10%, #ff8aa8 0%, transparent 42%), linear-gradient(145deg, #8f1847 0%, #cc3f5e 46%, #efc141 100%)" },
|
|
122
|
+
{ id: "theme-nitro-aurora", label: "Aurora", preview: "radial-gradient(circle at 10% 10%, #41cbb1 0%, transparent 42%), linear-gradient(145deg, #083142 0%, #1b7e74 46%, #5fbf75 100%)" },
|
|
123
|
+
{ id: "theme-nitro-sepia", label: "Sepia", preview: "radial-gradient(circle at 10% 10%, #b99672 0%, transparent 42%), linear-gradient(145deg, #35261d 0%, #5d4636 46%, #927454 100%)" },
|
|
124
|
+
] as const;
|
|
125
|
+
|
|
126
|
+
export const MEMORY_LANE_THEME = {
|
|
127
|
+
id: "theme-nitro-memory-lane",
|
|
128
|
+
label: "Memory Lane",
|
|
129
|
+
preview: "radial-gradient(circle at 10% 10%, #ba8fd4 0%, transparent 42%), linear-gradient(145deg, #462d42 0%, #684b75 35%, #4a7199 68%, #77ad9f 100%)",
|
|
130
|
+
} as const;
|
|
131
|
+
|
|
132
|
+
export const NITRO_ALL_THEMES = [...NITRO_PUBLIC_THEMES, MEMORY_LANE_THEME] as const;
|
|
133
|
+
export type ThemeId = (typeof NITRO_ALL_THEMES)[number]["id"];
|
|
134
|
+
export type ColorMode = "dark" | "light";
|
|
135
|
+
|
|
136
|
+
const DEFAULT_THEME: ThemeId = "theme-nitro-midnight-blurple";
|
|
137
|
+
const DEFAULT_COLOR_MODE: ColorMode = "dark";
|
|
138
|
+
const THEME_STORAGE_KEY = "shadcn-gradient.theme";
|
|
139
|
+
const COLOR_MODE_STORAGE_KEY = "shadcn-gradient.color-mode";
|
|
140
|
+
|
|
141
|
+
export const normalizeTheme = (theme?: string | null): ThemeId => {
|
|
142
|
+
if (!theme) return DEFAULT_THEME;
|
|
143
|
+
return theme as ThemeId;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const normalizeColorMode = (mode?: string | null): ColorMode => {
|
|
147
|
+
if (mode === "light") return "light";
|
|
148
|
+
return DEFAULT_COLOR_MODE;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const getStoredTheme = (): ThemeId => {
|
|
152
|
+
if (typeof window === "undefined") return DEFAULT_THEME;
|
|
153
|
+
return normalizeTheme(localStorage.getItem(THEME_STORAGE_KEY));
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const getStoredColorMode = (): ColorMode => {
|
|
157
|
+
if (typeof window === "undefined") return DEFAULT_COLOR_MODE;
|
|
158
|
+
return normalizeColorMode(localStorage.getItem(COLOR_MODE_STORAGE_KEY));
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const applyTheme = (theme?: string | null, mode?: ColorMode) => {
|
|
162
|
+
if (typeof window === "undefined") return;
|
|
163
|
+
const normalizedTheme = normalizeTheme(theme);
|
|
164
|
+
const normalizedMode = normalizeColorMode(mode ?? getStoredColorMode());
|
|
165
|
+
const root = document.documentElement;
|
|
166
|
+
|
|
167
|
+
Array.from(root.classList).forEach((className) => {
|
|
168
|
+
if (className === "dark" || className === "light" || className.startsWith("theme-nitro-")) {
|
|
169
|
+
root.classList.remove(className);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
root.classList.add(normalizedMode, normalizedTheme);
|
|
174
|
+
root.setAttribute("data-theme", normalizedTheme);
|
|
175
|
+
root.setAttribute("data-color-mode", normalizedMode);
|
|
176
|
+
localStorage.setItem(THEME_STORAGE_KEY, normalizedTheme);
|
|
177
|
+
localStorage.setItem(COLOR_MODE_STORAGE_KEY, normalizedMode);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const defaultTheme = DEFAULT_THEME;
|
|
181
|
+
export const defaultColorMode = DEFAULT_COLOR_MODE;`
|
|
182
|
+
},
|
|
183
|
+
"theme-context": {
|
|
184
|
+
filename: "components/theme/theme-context.tsx",
|
|
185
|
+
description: "React context provider",
|
|
186
|
+
extension: "tsx",
|
|
187
|
+
content: `"use client";
|
|
188
|
+
|
|
189
|
+
import {
|
|
190
|
+
type ColorMode,
|
|
191
|
+
type ThemeId,
|
|
192
|
+
NITRO_ALL_THEMES,
|
|
193
|
+
applyTheme,
|
|
194
|
+
defaultColorMode,
|
|
195
|
+
defaultTheme,
|
|
196
|
+
getStoredColorMode,
|
|
197
|
+
getStoredTheme,
|
|
198
|
+
} from "@/components/theme/theme-engine";
|
|
199
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
200
|
+
|
|
201
|
+
type ThemeContextValue = {
|
|
202
|
+
themeId: ThemeId;
|
|
203
|
+
colorMode: ColorMode;
|
|
204
|
+
setThemeId: (themeId: ThemeId) => void;
|
|
205
|
+
setColorMode: (mode: ColorMode) => void;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
209
|
+
|
|
210
|
+
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
|
|
211
|
+
const [themeId, setThemeIdState] = useState<ThemeId>(defaultTheme);
|
|
212
|
+
const [colorMode, setColorModeState] = useState<ColorMode>(defaultColorMode);
|
|
213
|
+
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
const storedTheme = getStoredTheme();
|
|
216
|
+
const storedMode = getStoredColorMode();
|
|
217
|
+
setThemeIdState(storedTheme);
|
|
218
|
+
setColorModeState(storedMode);
|
|
219
|
+
applyTheme(storedTheme, storedMode);
|
|
220
|
+
}, []);
|
|
221
|
+
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
applyTheme(themeId, colorMode);
|
|
224
|
+
}, [themeId, colorMode]);
|
|
225
|
+
|
|
226
|
+
const setThemeId = (nextTheme: ThemeId) => {
|
|
227
|
+
setThemeIdState(nextTheme);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const setColorMode = (mode: ColorMode) => {
|
|
231
|
+
setColorModeState(mode);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<ThemeContext.Provider value={{ themeId, colorMode, setThemeId, setColorMode }}>
|
|
236
|
+
{children}
|
|
237
|
+
</ThemeContext.Provider>
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export const useThemeContext = () => {
|
|
242
|
+
const ctx = useContext(ThemeContext);
|
|
243
|
+
if (!ctx) throw new Error("useThemeContext must be used within ThemeProvider");
|
|
244
|
+
return ctx;
|
|
245
|
+
};`
|
|
246
|
+
},
|
|
247
|
+
"token-export-utils": {
|
|
248
|
+
filename: "components/theme/token-export-utils.ts",
|
|
249
|
+
description: "Export utilities for tokens",
|
|
250
|
+
extension: "ts",
|
|
251
|
+
content: `import { NITRO_ALL_THEMES, type ThemeId } from "./theme-engine";
|
|
252
|
+
|
|
253
|
+
const themeTokens: Record<string, Record<string, string>> = {
|
|
254
|
+
"theme-nitro-midnight-blurple": {
|
|
255
|
+
"--background": "235 26% 11%",
|
|
256
|
+
"--foreground": "235 30% 95%",
|
|
257
|
+
"--card": "235 22% 12%",
|
|
258
|
+
"--primary": "241 92% 70%",
|
|
259
|
+
"--accent": "210 92% 65%",
|
|
260
|
+
"--ring": "241 92% 70%",
|
|
261
|
+
"--app-surface-tint": "hsl(241 92% 70% / 0.1)",
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export type ExportFormat = "css" | "json" | "tailwind";
|
|
266
|
+
|
|
267
|
+
export interface ExportOptions {
|
|
268
|
+
format: ExportFormat;
|
|
269
|
+
themeId: ThemeId;
|
|
270
|
+
colorMode?: "dark" | "light";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface ExportResult {
|
|
274
|
+
content: string;
|
|
275
|
+
filename: string;
|
|
276
|
+
mimeType: string;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export const getThemeTokens = (themeId: ThemeId): Record<string, string> => {
|
|
280
|
+
return themeTokens[themeId] ?? themeTokens["theme-nitro-midnight-blurple"];
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
export const generateCSSExport = (options: ExportOptions): ExportResult => {
|
|
284
|
+
const { themeId } = options;
|
|
285
|
+
const tokens = getThemeTokens(themeId);
|
|
286
|
+
const cssVariables = Object.entries(tokens)
|
|
287
|
+
.map(([key, value]) => \` \${key}: \${value};\`)
|
|
288
|
+
.join("\\n");
|
|
289
|
+
|
|
290
|
+
const content = \`/* Gradient Forge Theme */
|
|
291
|
+
.\${themeId} {
|
|
292
|
+
\${cssVariables}
|
|
293
|
+
}\`;
|
|
294
|
+
|
|
295
|
+
return { content, filename: \`\${themeId}.css\`, mimeType: "text/css" };
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const exportTokens = (options: ExportOptions): ExportResult => {
|
|
299
|
+
return generateCSSExport(options);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
export const downloadFile = (result: ExportResult): void => {
|
|
303
|
+
const blob = new Blob([result.content], { type: result.mimeType });
|
|
304
|
+
const url = URL.createObjectURL(blob);
|
|
305
|
+
const link = document.createElement("a");
|
|
306
|
+
link.href = url;
|
|
307
|
+
link.download = result.filename;
|
|
308
|
+
document.body.appendChild(link);
|
|
309
|
+
link.click();
|
|
310
|
+
document.body.removeChild(link);
|
|
311
|
+
URL.revokeObjectURL(url);
|
|
312
|
+
};`
|
|
313
|
+
},
|
|
314
|
+
"globals-css": {
|
|
315
|
+
filename: "app/globals.css",
|
|
316
|
+
description: "CSS variables & theme classes",
|
|
317
|
+
extension: "css",
|
|
318
|
+
content: `@import "tailwindcss";
|
|
319
|
+
|
|
320
|
+
@custom-variant dark (&:is(.dark *));
|
|
321
|
+
|
|
322
|
+
@theme {
|
|
323
|
+
--color-background: hsl(var(--background));
|
|
324
|
+
--color-foreground: hsl(var(--foreground));
|
|
325
|
+
--color-card: hsl(var(--card));
|
|
326
|
+
--color-primary: hsl(var(--primary));
|
|
327
|
+
--color-accent: hsl(var(--accent));
|
|
328
|
+
--radius-lg: var(--radius);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
@layer base {
|
|
332
|
+
body {
|
|
333
|
+
background-color: hsl(var(--background) / 0.9);
|
|
334
|
+
background-image: var(--app-gradient);
|
|
335
|
+
color: hsl(var(--foreground));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
@layer base {
|
|
340
|
+
:root {
|
|
341
|
+
--background: 0 0% 100%;
|
|
342
|
+
--foreground: 0 0% 3.9%;
|
|
343
|
+
--card: 0 0% 100%;
|
|
344
|
+
--primary: 0 0% 9%;
|
|
345
|
+
--radius: 0.9rem;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.dark {
|
|
349
|
+
--background: 0 0% 3.9%;
|
|
350
|
+
--foreground: 0 0% 98%;
|
|
351
|
+
--app-gradient: radial-gradient(1100px 560px at -10% -20%, hsl(250 90% 66% / 0.28), transparent 60%);
|
|
352
|
+
--app-surface-tint: hsl(250 85% 65% / 0.08);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.theme-nitro-midnight-blurple {
|
|
356
|
+
--background: 235 26% 11%;
|
|
357
|
+
--primary: 241 92% 70%;
|
|
358
|
+
--app-gradient: radial-gradient(1050px 560px at -10% -20%, hsl(246 92% 66% / 0.3), transparent 60%);
|
|
359
|
+
--app-surface-tint: hsl(241 92% 70% / 0.1);
|
|
360
|
+
}
|
|
361
|
+
}`
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
export default function StudioPage() {
|
|
366
|
+
const [activeSection, setActiveSection] = useState("overview");
|
|
367
|
+
const [deviceView, setDeviceView] = useState<"mobile" | "tablet" | "desktop">("desktop");
|
|
368
|
+
const [selectedFormat, setSelectedFormat] = useState("css");
|
|
369
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
370
|
+
const [showCssVariables, setShowCssVariables] = useState(true);
|
|
371
|
+
const [previewColorMode, setPreviewColorMode] = useState<"light" | "dark">("light");
|
|
372
|
+
const [selectedCoreFile, setSelectedCoreFile] = useState<string | null>(null);
|
|
373
|
+
const { showToast } = useToast();
|
|
374
|
+
const { themeId, setThemeId, colorMode, setColorMode } = useThemeContext();
|
|
375
|
+
const previewRef = useRef<HTMLDivElement>(null);
|
|
376
|
+
const codeRef = useRef<HTMLPreElement>(null);
|
|
377
|
+
|
|
378
|
+
const currentTheme = NITRO_ALL_THEMES.find((t: { id: string }) => t.id === themeId) || NITRO_ALL_THEMES[0];
|
|
379
|
+
const flatTokens = getThemeTokens(themeId);
|
|
380
|
+
|
|
381
|
+
// Transform flat tokens into the expected structure for export functions
|
|
382
|
+
const tokens = {
|
|
383
|
+
colors: Object.fromEntries(
|
|
384
|
+
Object.entries(flatTokens).map(([key, value]) => [
|
|
385
|
+
key.replace(/^--/, ''), // Remove leading --
|
|
386
|
+
value
|
|
387
|
+
])
|
|
388
|
+
)
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Filter themes based on search
|
|
392
|
+
const filteredThemes = NITRO_ALL_THEMES.filter((theme: { label: string }) =>
|
|
393
|
+
theme.label.toLowerCase().includes(searchQuery.toLowerCase())
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// Animate preview cards on section change
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
const preview = previewRef.current;
|
|
399
|
+
if (!preview) return;
|
|
400
|
+
|
|
401
|
+
const cards = preview.querySelectorAll(".preview-item");
|
|
402
|
+
gsap.fromTo(
|
|
403
|
+
cards,
|
|
404
|
+
{ opacity: 0, y: 20, scale: 0.95 },
|
|
405
|
+
{
|
|
406
|
+
opacity: 1,
|
|
407
|
+
y: 0,
|
|
408
|
+
scale: 1,
|
|
409
|
+
duration: 0.4,
|
|
410
|
+
stagger: 0.05,
|
|
411
|
+
ease: "back.out(1.5)",
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
return () => {
|
|
416
|
+
ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
|
|
417
|
+
};
|
|
418
|
+
}, [activeSection, deviceView, themeId]);
|
|
419
|
+
|
|
420
|
+
const handleCopyCode = () => {
|
|
421
|
+
const code = generateExportCode(selectedFormat, tokens, themeId);
|
|
422
|
+
navigator.clipboard.writeText(code);
|
|
423
|
+
showToast("Code copied to clipboard!", "success");
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const handleDownload = () => {
|
|
427
|
+
const code = generateExportCode(selectedFormat, tokens, themeId);
|
|
428
|
+
const blob = new Blob([code], { type: "text/plain" });
|
|
429
|
+
const url = URL.createObjectURL(blob);
|
|
430
|
+
const a = document.createElement("a");
|
|
431
|
+
a.href = url;
|
|
432
|
+
a.download = `${themeId}-tokens.${getFileExtension(selectedFormat)}`;
|
|
433
|
+
document.body.appendChild(a);
|
|
434
|
+
a.click();
|
|
435
|
+
document.body.removeChild(a);
|
|
436
|
+
URL.revokeObjectURL(url);
|
|
437
|
+
showToast("File downloaded!", "success");
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
const handleCopyAll = () => {
|
|
441
|
+
const allTokens = NITRO_ALL_THEMES.map((theme: { id: ThemeId; label: string }) => ({
|
|
442
|
+
theme: theme.label,
|
|
443
|
+
tokens: getThemeTokens(theme.id)
|
|
444
|
+
}));
|
|
445
|
+
navigator.clipboard.writeText(JSON.stringify(allTokens, null, 2));
|
|
446
|
+
showToast("All themes copied to clipboard!", "success");
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const getDeviceClasses = () => {
|
|
450
|
+
switch (deviceView) {
|
|
451
|
+
case "mobile":
|
|
452
|
+
return "max-w-[375px]";
|
|
453
|
+
case "tablet":
|
|
454
|
+
return "max-w-[768px]";
|
|
455
|
+
default:
|
|
456
|
+
return "";
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
return (
|
|
461
|
+
<main className="min-h-screen bg-background">
|
|
462
|
+
<SiteHeader />
|
|
463
|
+
|
|
464
|
+
{/* Main Studio Layout */}
|
|
465
|
+
<div className="flex h-[calc(100vh-64px)] overflow-hidden">
|
|
466
|
+
|
|
467
|
+
{/* Left Sidebar - Theme List */}
|
|
468
|
+
<aside className="w-72 border-r border-border/50 bg-background/95 backdrop-blur-xl flex flex-col hidden lg:flex">
|
|
469
|
+
<div className="p-4 border-b border-border/50">
|
|
470
|
+
<div className="flex items-center justify-between mb-3">
|
|
471
|
+
<h2 className="font-semibold text-sm flex items-center gap-2">
|
|
472
|
+
<Palette className="h-4 w-4 text-primary" />
|
|
473
|
+
Themes
|
|
474
|
+
</h2>
|
|
475
|
+
<Badge variant="glass" className="text-xs">{NITRO_ALL_THEMES.length}</Badge>
|
|
476
|
+
</div>
|
|
477
|
+
<div className="relative">
|
|
478
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
479
|
+
<Input
|
|
480
|
+
placeholder="Search themes..."
|
|
481
|
+
value={searchQuery}
|
|
482
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
483
|
+
className="pl-9 h-9 text-sm"
|
|
484
|
+
/>
|
|
485
|
+
</div>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<div className="flex-1 overflow-y-auto p-2 space-y-1">
|
|
489
|
+
<div className="space-y-1">
|
|
490
|
+
{filteredThemes.map((theme) => (
|
|
491
|
+
<button
|
|
492
|
+
key={theme.id}
|
|
493
|
+
onClick={() => setThemeId(theme.id)}
|
|
494
|
+
className={cn(
|
|
495
|
+
"w-full flex items-center gap-3 p-3 rounded-xl text-left transition-all duration-200 group",
|
|
496
|
+
themeId === theme.id
|
|
497
|
+
? "bg-primary/10 border border-primary/30 shadow-sm"
|
|
498
|
+
: "hover:bg-muted/50 border border-transparent"
|
|
499
|
+
)}
|
|
500
|
+
>
|
|
501
|
+
<div
|
|
502
|
+
className="w-10 h-10 rounded-lg shadow-sm shrink-0"
|
|
503
|
+
style={{ background: theme.preview }}
|
|
504
|
+
/>
|
|
505
|
+
<div className="flex-1 min-w-0">
|
|
506
|
+
<p className={cn(
|
|
507
|
+
"font-medium text-sm truncate",
|
|
508
|
+
themeId === theme.id && "text-primary"
|
|
509
|
+
)}>
|
|
510
|
+
{theme.label}
|
|
511
|
+
</p>
|
|
512
|
+
<p className="text-xs text-muted-foreground truncate">
|
|
513
|
+
Gradient theme
|
|
514
|
+
</p>
|
|
515
|
+
</div>
|
|
516
|
+
{themeId === theme.id && (
|
|
517
|
+
<Check className="h-4 w-4 text-primary shrink-0" />
|
|
518
|
+
)}
|
|
519
|
+
</button>
|
|
520
|
+
))}
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
|
|
524
|
+
<div className="p-4 border-t border-border/50 space-y-3">
|
|
525
|
+
<div className="flex items-center justify-between">
|
|
526
|
+
<span className="text-xs font-medium text-muted-foreground">Preview Mode</span>
|
|
527
|
+
<div className="flex items-center gap-1 bg-muted/50 rounded-full p-1">
|
|
528
|
+
<button
|
|
529
|
+
onClick={() => setPreviewColorMode("light")}
|
|
530
|
+
className={cn(
|
|
531
|
+
"p-1.5 rounded-full transition-all",
|
|
532
|
+
previewColorMode === "light" ? "bg-background shadow-sm" : "text-muted-foreground"
|
|
533
|
+
)}
|
|
534
|
+
>
|
|
535
|
+
<SunIcon className="h-3.5 w-3.5" />
|
|
536
|
+
</button>
|
|
537
|
+
<button
|
|
538
|
+
onClick={() => setPreviewColorMode("dark")}
|
|
539
|
+
className={cn(
|
|
540
|
+
"p-1.5 rounded-full transition-all",
|
|
541
|
+
previewColorMode === "dark" ? "bg-background shadow-sm" : "text-muted-foreground"
|
|
542
|
+
)}
|
|
543
|
+
>
|
|
544
|
+
<MoonIcon className="h-3.5 w-3.5" />
|
|
545
|
+
</button>
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
</aside>
|
|
550
|
+
|
|
551
|
+
{/* Center - Live Preview */}
|
|
552
|
+
<section className="flex-1 flex flex-col overflow-hidden">
|
|
553
|
+
{/* Toolbar */}
|
|
554
|
+
<div className="h-14 border-b border-border/50 flex items-center justify-between px-4 bg-background/95 backdrop-blur-xl shrink-0">
|
|
555
|
+
<div className="flex items-center gap-2">
|
|
556
|
+
<h1 className="font-semibold text-sm hidden sm:block">Component Preview</h1>
|
|
557
|
+
<div className="h-4 w-px bg-border hidden sm:block" />
|
|
558
|
+
<div className="flex items-center gap-1 bg-muted/50 rounded-full p-1">
|
|
559
|
+
{[
|
|
560
|
+
{ id: "mobile", icon: Smartphone, label: "Mobile" },
|
|
561
|
+
{ id: "tablet", icon: Tablet, label: "Tablet" },
|
|
562
|
+
{ id: "desktop", icon: Monitor, label: "Desktop" },
|
|
563
|
+
].map(({ id, icon: Icon, label }) => (
|
|
564
|
+
<button
|
|
565
|
+
key={id}
|
|
566
|
+
onClick={() => setDeviceView(id as typeof deviceView)}
|
|
567
|
+
className={cn(
|
|
568
|
+
"p-1.5 rounded-full transition-all",
|
|
569
|
+
deviceView === id
|
|
570
|
+
? "bg-background shadow-sm text-foreground"
|
|
571
|
+
: "text-muted-foreground hover:text-foreground"
|
|
572
|
+
)}
|
|
573
|
+
title={label}
|
|
574
|
+
>
|
|
575
|
+
<Icon className="h-4 w-4" />
|
|
576
|
+
</button>
|
|
577
|
+
))}
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
580
|
+
|
|
581
|
+
<div className="flex items-center gap-2">
|
|
582
|
+
<Badge variant="glass" className="gap-1.5 text-xs">
|
|
583
|
+
<Sparkles className="h-3 w-3" />
|
|
584
|
+
{currentTheme.label}
|
|
585
|
+
</Badge>
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
|
|
589
|
+
{/* Preview Content */}
|
|
590
|
+
<div className="flex-1 overflow-y-auto p-4 sm:p-6">
|
|
591
|
+
<div className={cn("mx-auto transition-all duration-300", getDeviceClasses())}>
|
|
592
|
+
{/* Section Tabs */}
|
|
593
|
+
<div className="flex items-center gap-1 mb-6 overflow-x-auto pb-2">
|
|
594
|
+
{previewSections.map(({ id, label, icon: Icon }) => (
|
|
595
|
+
<button
|
|
596
|
+
key={id}
|
|
597
|
+
onClick={() => setActiveSection(id)}
|
|
598
|
+
className={cn(
|
|
599
|
+
"flex items-center gap-2 px-3 py-2 rounded-full text-xs font-medium transition-all whitespace-nowrap",
|
|
600
|
+
activeSection === id
|
|
601
|
+
? "bg-primary text-primary-foreground shadow-lg shadow-primary/20"
|
|
602
|
+
: "bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
603
|
+
)}
|
|
604
|
+
>
|
|
605
|
+
<Icon className="h-3.5 w-3.5" />
|
|
606
|
+
{label}
|
|
607
|
+
</button>
|
|
608
|
+
))}
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
{/* Preview Area */}
|
|
612
|
+
<div
|
|
613
|
+
ref={previewRef}
|
|
614
|
+
className="bg-card/50 rounded-2xl border border-border/50 p-4 sm:p-6 min-h-[500px]"
|
|
615
|
+
style={{ backgroundColor: previewColorMode === "dark" ? "#0a0a0a" : undefined }}
|
|
616
|
+
>
|
|
617
|
+
{activeSection === "overview" && <OverviewPreview />}
|
|
618
|
+
{activeSection === "buttons" && <ButtonsPreview />}
|
|
619
|
+
{activeSection === "inputs" && <InputsPreview />}
|
|
620
|
+
{activeSection === "cards" && <CardsPreview />}
|
|
621
|
+
{activeSection === "feedback" && <FeedbackPreview />}
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
</section>
|
|
626
|
+
|
|
627
|
+
{/* Right Sidebar - Export Panel */}
|
|
628
|
+
<aside className="w-80 border-l border-border/50 bg-background/95 backdrop-blur-xl flex flex-col hidden xl:flex">
|
|
629
|
+
<div className="p-4 border-b border-border/50">
|
|
630
|
+
<h2 className="font-semibold text-sm flex items-center gap-2 mb-3">
|
|
631
|
+
<Code2 className="h-4 w-4 text-primary" />
|
|
632
|
+
Export Tokens
|
|
633
|
+
</h2>
|
|
634
|
+
<div className="flex items-center gap-1 bg-muted/50 rounded-lg p-1">
|
|
635
|
+
{exportFormats.map(({ id, label, icon: Icon }) => (
|
|
636
|
+
<button
|
|
637
|
+
key={id}
|
|
638
|
+
onClick={() => setSelectedFormat(id)}
|
|
639
|
+
className={cn(
|
|
640
|
+
"flex-1 flex items-center justify-center gap-1.5 py-1.5 px-2 rounded-md text-xs font-medium transition-all",
|
|
641
|
+
selectedFormat === id
|
|
642
|
+
? "bg-background shadow-sm text-foreground"
|
|
643
|
+
: "text-muted-foreground hover:text-foreground"
|
|
644
|
+
)}
|
|
645
|
+
title={label}
|
|
646
|
+
>
|
|
647
|
+
<Icon className="h-3.5 w-3.5" />
|
|
648
|
+
<span className="hidden 2xl:inline">{label}</span>
|
|
649
|
+
</button>
|
|
650
|
+
))}
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
|
|
654
|
+
<div className="flex-1 overflow-hidden flex flex-col">
|
|
655
|
+
{/* Core Files Documentation */}
|
|
656
|
+
<div className="px-4 py-3 border-b border-border/50 bg-muted/20">
|
|
657
|
+
<p className="text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-2">Core Theme Files - Click to View & Copy</p>
|
|
658
|
+
<div className="space-y-1.5">
|
|
659
|
+
{Object.entries(coreFilesData).map(([key, file]) => (
|
|
660
|
+
<button
|
|
661
|
+
key={key}
|
|
662
|
+
onClick={() => setSelectedCoreFile(selectedCoreFile === key ? null : key)}
|
|
663
|
+
className={cn(
|
|
664
|
+
"w-full flex items-center gap-2 text-xs p-1.5 rounded border transition-all text-left",
|
|
665
|
+
selectedCoreFile === key
|
|
666
|
+
? "bg-primary/10 border-primary/30"
|
|
667
|
+
: "bg-background/50 border-border/30 hover:bg-background/80"
|
|
668
|
+
)}
|
|
669
|
+
>
|
|
670
|
+
<FileCode className={cn("h-3 w-3 shrink-0", selectedCoreFile === key ? "text-primary" : "text-muted-foreground")} />
|
|
671
|
+
<div className="flex-1 min-w-0">
|
|
672
|
+
<p className={cn("font-medium truncate", selectedCoreFile === key && "text-primary")}>{file.filename}</p>
|
|
673
|
+
<p className="text-[10px] text-muted-foreground truncate">{file.description}</p>
|
|
674
|
+
</div>
|
|
675
|
+
{selectedCoreFile === key && <Check className="h-3 w-3 text-primary shrink-0" />}
|
|
676
|
+
</button>
|
|
677
|
+
))}
|
|
678
|
+
</div>
|
|
679
|
+
{selectedCoreFile && (
|
|
680
|
+
<div className="mt-3 p-2 bg-black/90 rounded border border-primary/20">
|
|
681
|
+
<div className="flex items-center justify-between mb-2">
|
|
682
|
+
<span className="text-[10px] text-primary font-medium">{coreFilesData[selectedCoreFile].filename}</span>
|
|
683
|
+
<button
|
|
684
|
+
onClick={() => {
|
|
685
|
+
navigator.clipboard.writeText(coreFilesData[selectedCoreFile].content);
|
|
686
|
+
showToast("File copied to clipboard!", "success");
|
|
687
|
+
}}
|
|
688
|
+
className="text-[10px] px-2 py-1 bg-primary/20 hover:bg-primary/30 text-primary rounded transition-colors"
|
|
689
|
+
>
|
|
690
|
+
Copy File
|
|
691
|
+
</button>
|
|
692
|
+
</div>
|
|
693
|
+
<pre className="text-[10px] text-green-400 overflow-auto max-h-[200px] whitespace-pre-wrap break-all">
|
|
694
|
+
{coreFilesData[selectedCoreFile].content}
|
|
695
|
+
</pre>
|
|
696
|
+
</div>
|
|
697
|
+
)}
|
|
698
|
+
</div>
|
|
699
|
+
|
|
700
|
+
<div className="flex items-center justify-between px-4 py-2 border-b border-border/50">
|
|
701
|
+
<span className="text-xs font-medium text-muted-foreground">
|
|
702
|
+
{selectedCoreFile ? `Viewing: ${coreFilesData[selectedCoreFile].filename}` : "Generated Code"}
|
|
703
|
+
</span>
|
|
704
|
+
<div className="flex items-center gap-1">
|
|
705
|
+
<button
|
|
706
|
+
onClick={() => {
|
|
707
|
+
const content = selectedCoreFile
|
|
708
|
+
? coreFilesData[selectedCoreFile].content
|
|
709
|
+
: generateExportCode(selectedFormat, tokens, themeId);
|
|
710
|
+
navigator.clipboard.writeText(content);
|
|
711
|
+
showToast(selectedCoreFile ? "File copied!" : "Code copied!", "success");
|
|
712
|
+
}}
|
|
713
|
+
className="p-1.5 rounded-md hover:bg-muted transition-colors"
|
|
714
|
+
title="Copy code"
|
|
715
|
+
>
|
|
716
|
+
<Copy className="h-3.5 w-3.5 text-muted-foreground" />
|
|
717
|
+
</button>
|
|
718
|
+
{!selectedCoreFile && (
|
|
719
|
+
<button
|
|
720
|
+
onClick={handleDownload}
|
|
721
|
+
className="p-1.5 rounded-md hover:bg-muted transition-colors"
|
|
722
|
+
title="Download file"
|
|
723
|
+
>
|
|
724
|
+
<DownloadIcon className="h-3.5 w-3.5 text-muted-foreground" />
|
|
725
|
+
</button>
|
|
726
|
+
)}
|
|
727
|
+
</div>
|
|
728
|
+
</div>
|
|
729
|
+
<div className="flex-1 overflow-auto p-4">
|
|
730
|
+
<pre
|
|
731
|
+
ref={codeRef}
|
|
732
|
+
className={cn(
|
|
733
|
+
"text-xs whitespace-pre-wrap break-all",
|
|
734
|
+
selectedCoreFile ? "text-green-400 font-mono" : "font-mono text-muted-foreground"
|
|
735
|
+
)}
|
|
736
|
+
>
|
|
737
|
+
{selectedCoreFile
|
|
738
|
+
? coreFilesData[selectedCoreFile].content
|
|
739
|
+
: generateExportCode(selectedFormat, tokens, themeId)}
|
|
740
|
+
</pre>
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
743
|
+
|
|
744
|
+
<div className="p-4 border-t border-border/50 space-y-3">
|
|
745
|
+
<div className="flex items-center justify-between">
|
|
746
|
+
<span className="text-xs text-muted-foreground">Show CSS Variables</span>
|
|
747
|
+
<Switch
|
|
748
|
+
checked={showCssVariables}
|
|
749
|
+
onCheckedChange={setShowCssVariables}
|
|
750
|
+
/>
|
|
751
|
+
</div>
|
|
752
|
+
<Button
|
|
753
|
+
variant="outline"
|
|
754
|
+
className="w-full gap-2 text-xs border-primary/30 hover:bg-primary/10"
|
|
755
|
+
onClick={() => {
|
|
756
|
+
const allFilesContent = Object.entries(coreFilesData)
|
|
757
|
+
.map(([key, file]) => `// ===== ${file.filename} =====\\n\\n${file.content}`)
|
|
758
|
+
.join("\\n\\n\\n");
|
|
759
|
+
navigator.clipboard.writeText(allFilesContent);
|
|
760
|
+
showToast("All core files copied! Paste them into your project.", "success");
|
|
761
|
+
}}
|
|
762
|
+
>
|
|
763
|
+
<FileCode className="h-3.5 w-3.5" />
|
|
764
|
+
Copy All Core Files
|
|
765
|
+
</Button>
|
|
766
|
+
<Button
|
|
767
|
+
variant="outline"
|
|
768
|
+
className="w-full gap-2 text-xs"
|
|
769
|
+
onClick={handleCopyAll}
|
|
770
|
+
>
|
|
771
|
+
<Copy className="h-3.5 w-3.5" />
|
|
772
|
+
Copy All Themes
|
|
773
|
+
</Button>
|
|
774
|
+
<Button
|
|
775
|
+
className="w-full gap-2 text-xs"
|
|
776
|
+
onClick={handleDownload}
|
|
777
|
+
>
|
|
778
|
+
<DownloadIcon className="h-3.5 w-3.5" />
|
|
779
|
+
Download Tokens
|
|
780
|
+
</Button>
|
|
781
|
+
</div>
|
|
782
|
+
</aside>
|
|
783
|
+
</div>
|
|
784
|
+
</main>
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Preview Components
|
|
789
|
+
function OverviewPreview() {
|
|
790
|
+
return (
|
|
791
|
+
<div className="space-y-6 preview-item">
|
|
792
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
793
|
+
<Card className="preview-item border-border/40 bg-card/70">
|
|
794
|
+
<CardHeader className="pb-3">
|
|
795
|
+
<div className="flex items-center gap-3">
|
|
796
|
+
<div className="h-10 w-10 rounded-xl bg-primary/20 flex items-center justify-center">
|
|
797
|
+
<Zap className="h-5 w-5 text-primary" />
|
|
798
|
+
</div>
|
|
799
|
+
<div>
|
|
800
|
+
<CardTitle className="text-sm">Quick Actions</CardTitle>
|
|
801
|
+
<CardDescription className="text-xs">Common tasks</CardDescription>
|
|
802
|
+
</div>
|
|
803
|
+
</div>
|
|
804
|
+
</CardHeader>
|
|
805
|
+
<CardContent className="space-y-2">
|
|
806
|
+
<Button size="sm" className="w-full text-xs gap-2">
|
|
807
|
+
<Plus className="h-3.5 w-3.5" />
|
|
808
|
+
Create New
|
|
809
|
+
</Button>
|
|
810
|
+
<Button variant="outline" size="sm" className="w-full text-xs gap-2">
|
|
811
|
+
<Layout className="h-3.5 w-3.5" />
|
|
812
|
+
View Dashboard
|
|
813
|
+
</Button>
|
|
814
|
+
</CardContent>
|
|
815
|
+
</Card>
|
|
816
|
+
|
|
817
|
+
<Card className="preview-item border-border/40 bg-card/70">
|
|
818
|
+
<CardHeader className="pb-3">
|
|
819
|
+
<div className="flex items-center gap-3">
|
|
820
|
+
<div className="h-10 w-10 rounded-xl bg-secondary/20 flex items-center justify-center">
|
|
821
|
+
<User className="h-5 w-5 text-secondary" />
|
|
822
|
+
</div>
|
|
823
|
+
<div>
|
|
824
|
+
<CardTitle className="text-sm">Profile</CardTitle>
|
|
825
|
+
<CardDescription className="text-xs">User settings</CardDescription>
|
|
826
|
+
</div>
|
|
827
|
+
</div>
|
|
828
|
+
</CardHeader>
|
|
829
|
+
<CardContent className="space-y-3">
|
|
830
|
+
<div className="flex items-center gap-3">
|
|
831
|
+
<Avatar className="h-10 w-10">
|
|
832
|
+
<AvatarFallback className="bg-primary/20 text-primary text-sm">JD</AvatarFallback>
|
|
833
|
+
</Avatar>
|
|
834
|
+
<div className="flex-1">
|
|
835
|
+
<p className="text-sm font-medium">John Doe</p>
|
|
836
|
+
<p className="text-xs text-muted-foreground">john@example.com</p>
|
|
837
|
+
</div>
|
|
838
|
+
</div>
|
|
839
|
+
<Button variant="outline" size="sm" className="w-full text-xs">
|
|
840
|
+
Edit Profile
|
|
841
|
+
</Button>
|
|
842
|
+
</CardContent>
|
|
843
|
+
</Card>
|
|
844
|
+
|
|
845
|
+
<Card className="preview-item border-border/40 bg-card/70">
|
|
846
|
+
<CardHeader className="pb-3">
|
|
847
|
+
<div className="flex items-center gap-3">
|
|
848
|
+
<div className="h-10 w-10 rounded-xl bg-accent/20 flex items-center justify-center">
|
|
849
|
+
<Bell className="h-5 w-5 text-accent" />
|
|
850
|
+
</div>
|
|
851
|
+
<div>
|
|
852
|
+
<CardTitle className="text-sm">Notifications</CardTitle>
|
|
853
|
+
<CardDescription className="text-xs">3 unread</CardDescription>
|
|
854
|
+
</div>
|
|
855
|
+
</div>
|
|
856
|
+
</CardHeader>
|
|
857
|
+
<CardContent className="space-y-2">
|
|
858
|
+
<div className="flex items-center gap-2 text-xs">
|
|
859
|
+
<div className="h-2 w-2 rounded-full bg-primary animate-pulse" />
|
|
860
|
+
<span className="flex-1">New theme available</span>
|
|
861
|
+
<span className="text-muted-foreground">2m</span>
|
|
862
|
+
</div>
|
|
863
|
+
<div className="flex items-center gap-2 text-xs">
|
|
864
|
+
<div className="h-2 w-2 rounded-full bg-secondary" />
|
|
865
|
+
<span className="flex-1">Export completed</span>
|
|
866
|
+
<span className="text-muted-foreground">1h</span>
|
|
867
|
+
</div>
|
|
868
|
+
<Button variant="ghost" size="sm" className="w-full text-xs">
|
|
869
|
+
View All
|
|
870
|
+
</Button>
|
|
871
|
+
</CardContent>
|
|
872
|
+
</Card>
|
|
873
|
+
</div>
|
|
874
|
+
|
|
875
|
+
<Card className="preview-item border-border/40 bg-card/70">
|
|
876
|
+
<CardHeader>
|
|
877
|
+
<CardTitle className="text-sm">Theme Preview</CardTitle>
|
|
878
|
+
<CardDescription className="text-xs">
|
|
879
|
+
See how your theme looks across different components
|
|
880
|
+
</CardDescription>
|
|
881
|
+
</CardHeader>
|
|
882
|
+
<CardContent>
|
|
883
|
+
<div className="flex flex-wrap gap-2">
|
|
884
|
+
<Badge>Default</Badge>
|
|
885
|
+
<Badge variant="outline">Outline</Badge>
|
|
886
|
+
<Badge variant="glass">Glass</Badge>
|
|
887
|
+
<Badge className="gap-1"><Check className="h-3 w-3" /> Success</Badge>
|
|
888
|
+
</div>
|
|
889
|
+
<div className="flex flex-wrap gap-2 mt-4">
|
|
890
|
+
<Button size="sm">Primary</Button>
|
|
891
|
+
<Button size="sm" variant="secondary">Secondary</Button>
|
|
892
|
+
<Button size="sm" variant="outline">Outline</Button>
|
|
893
|
+
<Button size="sm" variant="ghost">Ghost</Button>
|
|
894
|
+
<Button size="sm" variant="glow">Glow</Button>
|
|
895
|
+
</div>
|
|
896
|
+
</CardContent>
|
|
897
|
+
</Card>
|
|
898
|
+
</div>
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function ButtonsPreview() {
|
|
903
|
+
return (
|
|
904
|
+
<div className="space-y-6">
|
|
905
|
+
<div className="preview-item">
|
|
906
|
+
<h3 className="text-sm font-medium mb-3">Button Variants</h3>
|
|
907
|
+
<div className="flex flex-wrap gap-2">
|
|
908
|
+
<Button size="sm">Default</Button>
|
|
909
|
+
<Button size="sm" variant="secondary">Secondary</Button>
|
|
910
|
+
<Button size="sm" variant="outline">Outline</Button>
|
|
911
|
+
<Button size="sm" variant="ghost">Ghost</Button>
|
|
912
|
+
<Button size="sm" variant="glow">Glow</Button>
|
|
913
|
+
</div>
|
|
914
|
+
</div>
|
|
915
|
+
|
|
916
|
+
<div className="preview-item">
|
|
917
|
+
<h3 className="text-sm font-medium mb-3">Button Sizes</h3>
|
|
918
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
919
|
+
<Button size="sm" variant="glow">Small</Button>
|
|
920
|
+
<Button size="md" variant="glow">Medium</Button>
|
|
921
|
+
<Button size="lg" variant="glow">Large</Button>
|
|
922
|
+
</div>
|
|
923
|
+
</div>
|
|
924
|
+
|
|
925
|
+
<div className="preview-item">
|
|
926
|
+
<h3 className="text-sm font-medium mb-3">Button States</h3>
|
|
927
|
+
<div className="flex flex-wrap gap-2">
|
|
928
|
+
<Button size="sm" variant="glow">
|
|
929
|
+
<Check className="h-4 w-4 mr-1.5" />
|
|
930
|
+
With Icon
|
|
931
|
+
</Button>
|
|
932
|
+
<Button size="sm" variant="glow" disabled>
|
|
933
|
+
<Loader2 className="h-4 w-4 mr-1.5 animate-spin" />
|
|
934
|
+
Loading
|
|
935
|
+
</Button>
|
|
936
|
+
<Button size="sm" variant="outline" disabled>Disabled</Button>
|
|
937
|
+
</div>
|
|
938
|
+
</div>
|
|
939
|
+
|
|
940
|
+
<div className="preview-item">
|
|
941
|
+
<h3 className="text-sm font-medium mb-3">Icon Buttons</h3>
|
|
942
|
+
<div className="flex flex-wrap gap-2">
|
|
943
|
+
<Button size="sm" variant="outline" className="px-2.5">
|
|
944
|
+
<Settings className="h-4 w-4" />
|
|
945
|
+
</Button>
|
|
946
|
+
<Button size="sm" variant="glow" className="px-2.5">
|
|
947
|
+
<Bell className="h-4 w-4" />
|
|
948
|
+
</Button>
|
|
949
|
+
<Button size="sm" variant="secondary" className="px-2.5">
|
|
950
|
+
<User className="h-4 w-4" />
|
|
951
|
+
</Button>
|
|
952
|
+
</div>
|
|
953
|
+
</div>
|
|
954
|
+
</div>
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function InputsPreview() {
|
|
959
|
+
return (
|
|
960
|
+
<div className="space-y-6">
|
|
961
|
+
<div className="preview-item">
|
|
962
|
+
<h3 className="text-sm font-medium mb-3">Text Inputs</h3>
|
|
963
|
+
<div className="grid sm:grid-cols-2 gap-4 max-w-md">
|
|
964
|
+
<div className="space-y-2">
|
|
965
|
+
<label className="text-xs font-medium">Default</label>
|
|
966
|
+
<Input placeholder="Enter text..." />
|
|
967
|
+
</div>
|
|
968
|
+
<div className="space-y-2">
|
|
969
|
+
<label className="text-xs font-medium">With Icon</label>
|
|
970
|
+
<div className="relative">
|
|
971
|
+
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
972
|
+
<Input placeholder="Email" className="pl-10" />
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
<div className="space-y-2">
|
|
976
|
+
<label className="text-xs font-medium">Disabled</label>
|
|
977
|
+
<Input placeholder="Disabled" disabled />
|
|
978
|
+
</div>
|
|
979
|
+
<div className="space-y-2">
|
|
980
|
+
<label className="text-xs font-medium">With Button</label>
|
|
981
|
+
<div className="flex gap-2">
|
|
982
|
+
<Input placeholder="Search..." className="flex-1" />
|
|
983
|
+
<Button size="sm" variant="glow">
|
|
984
|
+
<Search className="h-4 w-4" />
|
|
985
|
+
</Button>
|
|
986
|
+
</div>
|
|
987
|
+
</div>
|
|
988
|
+
</div>
|
|
989
|
+
</div>
|
|
990
|
+
|
|
991
|
+
<div className="preview-item">
|
|
992
|
+
<h3 className="text-sm font-medium mb-3">Switches</h3>
|
|
993
|
+
<Card className="max-w-sm">
|
|
994
|
+
<CardContent className="space-y-4 pt-6">
|
|
995
|
+
<div className="flex items-center justify-between">
|
|
996
|
+
<div className="flex items-center gap-3">
|
|
997
|
+
<Sun className="h-4 w-4" />
|
|
998
|
+
<span className="text-sm">Light Mode</span>
|
|
999
|
+
</div>
|
|
1000
|
+
<Switch defaultChecked />
|
|
1001
|
+
</div>
|
|
1002
|
+
<div className="flex items-center justify-between">
|
|
1003
|
+
<div className="flex items-center gap-3">
|
|
1004
|
+
<Bell className="h-4 w-4" />
|
|
1005
|
+
<span className="text-sm">Notifications</span>
|
|
1006
|
+
</div>
|
|
1007
|
+
<Switch defaultChecked />
|
|
1008
|
+
</div>
|
|
1009
|
+
<div className="flex items-center justify-between">
|
|
1010
|
+
<div className="flex items-center gap-3">
|
|
1011
|
+
<Mail className="h-4 w-4" />
|
|
1012
|
+
<span className="text-sm">Email Updates</span>
|
|
1013
|
+
</div>
|
|
1014
|
+
<Switch />
|
|
1015
|
+
</div>
|
|
1016
|
+
</CardContent>
|
|
1017
|
+
</Card>
|
|
1018
|
+
</div>
|
|
1019
|
+
|
|
1020
|
+
<div className="preview-item">
|
|
1021
|
+
<h3 className="text-sm font-medium mb-3">Checkboxes</h3>
|
|
1022
|
+
<div className="space-y-3">
|
|
1023
|
+
<label className="flex items-center gap-3">
|
|
1024
|
+
<Checkbox defaultChecked />
|
|
1025
|
+
<span className="text-sm">Enable notifications</span>
|
|
1026
|
+
</label>
|
|
1027
|
+
<label className="flex items-center gap-3">
|
|
1028
|
+
<Checkbox />
|
|
1029
|
+
<span className="text-sm">Auto-save changes</span>
|
|
1030
|
+
</label>
|
|
1031
|
+
<label className="flex items-center gap-3">
|
|
1032
|
+
<Checkbox defaultChecked />
|
|
1033
|
+
<span className="text-sm">Show previews</span>
|
|
1034
|
+
</label>
|
|
1035
|
+
</div>
|
|
1036
|
+
</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function CardsPreview() {
|
|
1042
|
+
return (
|
|
1043
|
+
<div className="space-y-6">
|
|
1044
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
1045
|
+
<Card className="preview-item border-border/40 bg-card/70">
|
|
1046
|
+
<CardHeader>
|
|
1047
|
+
<CardTitle className="text-sm">Simple Card</CardTitle>
|
|
1048
|
+
<CardDescription className="text-xs">Basic card component</CardDescription>
|
|
1049
|
+
</CardHeader>
|
|
1050
|
+
<CardContent>
|
|
1051
|
+
<p className="text-xs text-muted-foreground">
|
|
1052
|
+
Cards provide a flexible container for displaying content.
|
|
1053
|
+
</p>
|
|
1054
|
+
</CardContent>
|
|
1055
|
+
</Card>
|
|
1056
|
+
|
|
1057
|
+
<Card className="preview-item border-primary/30 bg-primary/5">
|
|
1058
|
+
<CardHeader>
|
|
1059
|
+
<CardTitle className="text-sm flex items-center gap-2">
|
|
1060
|
+
<CheckCircle2 className="h-4 w-4 text-primary" />
|
|
1061
|
+
Featured Card
|
|
1062
|
+
</CardTitle>
|
|
1063
|
+
<CardDescription className="text-xs">Highlighted styling</CardDescription>
|
|
1064
|
+
</CardHeader>
|
|
1065
|
+
<CardContent>
|
|
1066
|
+
<p className="text-xs text-muted-foreground">
|
|
1067
|
+
Cards can have custom borders and backgrounds.
|
|
1068
|
+
</p>
|
|
1069
|
+
</CardContent>
|
|
1070
|
+
</Card>
|
|
1071
|
+
|
|
1072
|
+
<Card className="preview-item border-border/40 bg-card/70">
|
|
1073
|
+
<CardHeader className="pb-3">
|
|
1074
|
+
<CardTitle className="text-sm">Interactive Card</CardTitle>
|
|
1075
|
+
</CardHeader>
|
|
1076
|
+
<CardContent className="space-y-3">
|
|
1077
|
+
<p className="text-xs text-muted-foreground">
|
|
1078
|
+
Cards can contain buttons and other interactive elements.
|
|
1079
|
+
</p>
|
|
1080
|
+
<div className="flex gap-2">
|
|
1081
|
+
<Button size="sm" variant="outline" className="text-xs flex-1">Cancel</Button>
|
|
1082
|
+
<Button size="sm" variant="glow" className="text-xs flex-1">Confirm</Button>
|
|
1083
|
+
</div>
|
|
1084
|
+
</CardContent>
|
|
1085
|
+
</Card>
|
|
1086
|
+
</div>
|
|
1087
|
+
|
|
1088
|
+
<Card className="preview-item border-border/40 bg-card/70">
|
|
1089
|
+
<CardHeader className="flex flex-row items-center justify-between">
|
|
1090
|
+
<div>
|
|
1091
|
+
<CardTitle className="text-sm">Card with Actions</CardTitle>
|
|
1092
|
+
<CardDescription className="text-xs">Header actions example</CardDescription>
|
|
1093
|
+
</div>
|
|
1094
|
+
<Button variant="ghost" size="sm" className="px-2">
|
|
1095
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
1096
|
+
</Button>
|
|
1097
|
+
</CardHeader>
|
|
1098
|
+
<CardContent className="space-y-4">
|
|
1099
|
+
<div className="flex items-center gap-4 p-3 rounded-xl bg-muted/50">
|
|
1100
|
+
<Avatar className="h-10 w-10">
|
|
1101
|
+
<AvatarFallback className="bg-primary/20 text-primary">JD</AvatarFallback>
|
|
1102
|
+
</Avatar>
|
|
1103
|
+
<div className="flex-1">
|
|
1104
|
+
<p className="text-sm font-medium">Project Update</p>
|
|
1105
|
+
<p className="text-xs text-muted-foreground">Updated 2 hours ago</p>
|
|
1106
|
+
</div>
|
|
1107
|
+
<Badge variant="glass">New</Badge>
|
|
1108
|
+
</div>
|
|
1109
|
+
<div className="flex gap-2">
|
|
1110
|
+
<Button variant="outline" size="sm" className="text-xs gap-1.5">
|
|
1111
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
1112
|
+
Delete
|
|
1113
|
+
</Button>
|
|
1114
|
+
<Button size="sm" className="text-xs gap-1.5">
|
|
1115
|
+
<Check className="h-3.5 w-3.5" />
|
|
1116
|
+
Approve
|
|
1117
|
+
</Button>
|
|
1118
|
+
</div>
|
|
1119
|
+
</CardContent>
|
|
1120
|
+
</Card>
|
|
1121
|
+
</div>
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function FeedbackPreview() {
|
|
1126
|
+
return (
|
|
1127
|
+
<div className="space-y-6">
|
|
1128
|
+
<div className="preview-item">
|
|
1129
|
+
<h3 className="text-sm font-medium mb-3">Badges</h3>
|
|
1130
|
+
<div className="flex flex-wrap gap-2">
|
|
1131
|
+
<Badge>Default</Badge>
|
|
1132
|
+
<Badge variant="outline">Outline</Badge>
|
|
1133
|
+
<Badge variant="glass">Glass</Badge>
|
|
1134
|
+
<Badge className="gap-1"><Check className="h-3 w-3" /> Success</Badge>
|
|
1135
|
+
<Badge variant="outline" className="gap-1"><Bell className="h-3 w-3" /> Alert</Badge>
|
|
1136
|
+
</div>
|
|
1137
|
+
</div>
|
|
1138
|
+
|
|
1139
|
+
<div className="preview-item">
|
|
1140
|
+
<h3 className="text-sm font-medium mb-3">Loading States</h3>
|
|
1141
|
+
<Card>
|
|
1142
|
+
<CardContent className="p-6 space-y-4">
|
|
1143
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
1144
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
1145
|
+
Loading content...
|
|
1146
|
+
</div>
|
|
1147
|
+
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
|
1148
|
+
<div className="h-full w-2/3 bg-primary rounded-full animate-pulse" />
|
|
1149
|
+
</div>
|
|
1150
|
+
<div className="flex gap-2">
|
|
1151
|
+
<div className="h-8 w-8 rounded-full bg-muted animate-pulse" />
|
|
1152
|
+
<div className="flex-1 space-y-2">
|
|
1153
|
+
<div className="h-3 bg-muted rounded animate-pulse w-1/3" />
|
|
1154
|
+
<div className="h-2 bg-muted rounded animate-pulse w-1/2" />
|
|
1155
|
+
</div>
|
|
1156
|
+
</div>
|
|
1157
|
+
</CardContent>
|
|
1158
|
+
</Card>
|
|
1159
|
+
</div>
|
|
1160
|
+
|
|
1161
|
+
<div className="preview-item">
|
|
1162
|
+
<h3 className="text-sm font-medium mb-3">Alert States</h3>
|
|
1163
|
+
<div className="space-y-3 max-w-md">
|
|
1164
|
+
<div className="flex items-start gap-3 p-3 rounded-xl bg-primary/10 border border-primary/20">
|
|
1165
|
+
<Info className="h-4 w-4 text-primary shrink-0 mt-0.5" />
|
|
1166
|
+
<div className="flex-1">
|
|
1167
|
+
<p className="text-sm font-medium">Information</p>
|
|
1168
|
+
<p className="text-xs text-muted-foreground">This is an informational message.</p>
|
|
1169
|
+
</div>
|
|
1170
|
+
</div>
|
|
1171
|
+
<div className="flex items-start gap-3 p-3 rounded-xl bg-secondary/10 border border-secondary/20">
|
|
1172
|
+
<CheckCircle2 className="h-4 w-4 text-secondary shrink-0 mt-0.5" />
|
|
1173
|
+
<div className="flex-1">
|
|
1174
|
+
<p className="text-sm font-medium">Success</p>
|
|
1175
|
+
<p className="text-xs text-muted-foreground">Your action was completed successfully.</p>
|
|
1176
|
+
</div>
|
|
1177
|
+
</div>
|
|
1178
|
+
<div className="flex items-start gap-3 p-3 rounded-xl bg-destructive/10 border border-destructive/20">
|
|
1179
|
+
<AlertCircle className="h-4 w-4 text-destructive shrink-0 mt-0.5" />
|
|
1180
|
+
<div className="flex-1">
|
|
1181
|
+
<p className="text-sm font-medium">Error</p>
|
|
1182
|
+
<p className="text-xs text-muted-foreground">Something went wrong. Please try again.</p>
|
|
1183
|
+
</div>
|
|
1184
|
+
</div>
|
|
1185
|
+
</div>
|
|
1186
|
+
</div>
|
|
1187
|
+
|
|
1188
|
+
<div className="preview-item">
|
|
1189
|
+
<h3 className="text-sm font-medium mb-3">Empty State</h3>
|
|
1190
|
+
<Card>
|
|
1191
|
+
<CardContent className="text-center py-12">
|
|
1192
|
+
<div className="h-16 w-16 rounded-full bg-muted flex items-center justify-center mx-auto mb-4">
|
|
1193
|
+
<Mail className="h-8 w-8 text-muted-foreground" />
|
|
1194
|
+
</div>
|
|
1195
|
+
<h3 className="font-medium mb-1">No messages yet</h3>
|
|
1196
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
1197
|
+
Start a conversation to see messages here.
|
|
1198
|
+
</p>
|
|
1199
|
+
<Button size="sm" variant="outline">
|
|
1200
|
+
Start Conversation
|
|
1201
|
+
</Button>
|
|
1202
|
+
</CardContent>
|
|
1203
|
+
</Card>
|
|
1204
|
+
</div>
|
|
1205
|
+
</div>
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Helper functions
|
|
1210
|
+
function generateExportCode(format: string, tokens: any, themeId: string): string {
|
|
1211
|
+
switch (format) {
|
|
1212
|
+
case "css":
|
|
1213
|
+
return generateCSS(tokens, themeId);
|
|
1214
|
+
case "scss":
|
|
1215
|
+
return generateSCSS(tokens, themeId);
|
|
1216
|
+
case "json":
|
|
1217
|
+
return JSON.stringify(tokens, null, 2);
|
|
1218
|
+
case "tailwind":
|
|
1219
|
+
return generateTailwind(tokens, themeId);
|
|
1220
|
+
case "w3c":
|
|
1221
|
+
return generateW3C(tokens, themeId);
|
|
1222
|
+
case "figma":
|
|
1223
|
+
return generateFigma(tokens, themeId);
|
|
1224
|
+
default:
|
|
1225
|
+
return generateCSS(tokens, themeId);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function generateCSS(tokens: any, themeId: string): string {
|
|
1230
|
+
let css = `/* ${themeId} Theme Tokens */\n:root {\n`;
|
|
1231
|
+
|
|
1232
|
+
Object.entries(tokens.colors).forEach(([key, value]) => {
|
|
1233
|
+
css += ` --${key}: ${value};\n`;
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
if (tokens.gradients) {
|
|
1237
|
+
css += `\n /* Gradients */\n`;
|
|
1238
|
+
Object.entries(tokens.gradients).forEach(([key, value]) => {
|
|
1239
|
+
css += ` --gradient-${key}: ${value};\n`;
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
css += `}\n`;
|
|
1244
|
+
return css;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function generateSCSS(tokens: any, themeId: string): string {
|
|
1248
|
+
let scss = `// ${themeId} Theme Tokens\n$tokens: (\n`;
|
|
1249
|
+
|
|
1250
|
+
Object.entries(tokens.colors).forEach(([key, value]) => {
|
|
1251
|
+
scss += ` "${key}": ${value},\n`;
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
scss += `);\n`;
|
|
1255
|
+
return scss;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function generateTailwind(tokens: any, themeId: string): string {
|
|
1259
|
+
return `// tailwind.config.js
|
|
1260
|
+
module.exports = {
|
|
1261
|
+
theme: {
|
|
1262
|
+
extend: {
|
|
1263
|
+
colors: ${JSON.stringify(tokens.colors, null, 6).replace(/"/g, "'")},
|
|
1264
|
+
},
|
|
1265
|
+
},
|
|
1266
|
+
}`;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function generateW3C(tokens: any, themeId: string): string {
|
|
1270
|
+
return JSON.stringify({
|
|
1271
|
+
$schema: "https://design-tokens.github.io/format/",
|
|
1272
|
+
$themes: [{
|
|
1273
|
+
id: themeId,
|
|
1274
|
+
name: themeId,
|
|
1275
|
+
selectedTokenSets: { [themeId]: "enabled" }
|
|
1276
|
+
}],
|
|
1277
|
+
[themeId]: {
|
|
1278
|
+
color: tokens.colors
|
|
1279
|
+
}
|
|
1280
|
+
}, null, 2);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function generateFigma(tokens: any, themeId: string): string {
|
|
1284
|
+
const figmaTokens: any = {};
|
|
1285
|
+
|
|
1286
|
+
Object.entries(tokens.colors).forEach(([key, value]) => {
|
|
1287
|
+
figmaTokens[key] = {
|
|
1288
|
+
value: value,
|
|
1289
|
+
type: "color"
|
|
1290
|
+
};
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
return JSON.stringify({
|
|
1294
|
+
[themeId]: {
|
|
1295
|
+
color: figmaTokens
|
|
1296
|
+
}
|
|
1297
|
+
}, null, 2);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
function getFileExtension(format: string): string {
|
|
1301
|
+
switch (format) {
|
|
1302
|
+
case "css": return "css";
|
|
1303
|
+
case "scss": return "scss";
|
|
1304
|
+
case "json":
|
|
1305
|
+
case "w3c":
|
|
1306
|
+
case "figma": return "json";
|
|
1307
|
+
case "tailwind": return "js";
|
|
1308
|
+
default: return "txt";
|
|
1309
|
+
}
|
|
1310
|
+
}
|