create-waypoint-app 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/bin/create-waypoint-app.js +5 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1806 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1806 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import chalk4 from "chalk";
|
|
4
|
+
|
|
5
|
+
// src/prompts.ts
|
|
6
|
+
import prompts from "prompts";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
async function gatherProjectConfig(initialName) {
|
|
9
|
+
const response = await prompts([
|
|
10
|
+
{
|
|
11
|
+
type: "text",
|
|
12
|
+
name: "projectName",
|
|
13
|
+
message: "Project name:",
|
|
14
|
+
initial: initialName || "my-waypoint-app",
|
|
15
|
+
validate: (value) => {
|
|
16
|
+
if (!value) return "Project name is required";
|
|
17
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
18
|
+
return "Project name can only contain lowercase letters, numbers, and hyphens";
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
name: "description",
|
|
26
|
+
message: "Project description:",
|
|
27
|
+
initial: "A Waypoint 2.0 design system project"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: "multiselect",
|
|
31
|
+
name: "themeModes",
|
|
32
|
+
message: "Select theme modes:",
|
|
33
|
+
choices: [
|
|
34
|
+
{ title: "Light Mode", value: "light", selected: true },
|
|
35
|
+
{ title: "Dark Mode", value: "dark", selected: true },
|
|
36
|
+
{ title: "High Contrast Light", value: "high-contrast-light" },
|
|
37
|
+
{ title: "High Contrast Dark", value: "high-contrast-dark" }
|
|
38
|
+
],
|
|
39
|
+
hint: "- Space to select, Enter to confirm",
|
|
40
|
+
min: 1
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: "select",
|
|
44
|
+
name: "themePreset",
|
|
45
|
+
message: "Default theme preset:",
|
|
46
|
+
choices: [
|
|
47
|
+
{ title: "Default", value: "default", description: "Neutral grays with accent colors" },
|
|
48
|
+
{ title: "Warm", value: "warm", description: "Oranges and earthy tones" },
|
|
49
|
+
{ title: "Cool", value: "cool", description: "Blues and teals" },
|
|
50
|
+
{ title: "Pastel", value: "pastel", description: "Soft, muted colors" },
|
|
51
|
+
{ title: "Bold", value: "bold", description: "High contrast, vibrant colors" },
|
|
52
|
+
{ title: "Ocean", value: "ocean", description: "Deep blues and aquatics" }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: "confirm",
|
|
57
|
+
name: "enableRTL",
|
|
58
|
+
message: "Enable RTL (Right-to-Left) support?",
|
|
59
|
+
initial: false
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: "confirm",
|
|
63
|
+
name: "enableFontScaling",
|
|
64
|
+
message: "Enable font scaling accessibility?",
|
|
65
|
+
initial: true
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: "multiselect",
|
|
69
|
+
name: "features",
|
|
70
|
+
message: "Select component groups to include:",
|
|
71
|
+
choices: [
|
|
72
|
+
{ title: "Core Components", value: "core", selected: true, description: "Button, Card, Input, Dialog, etc." },
|
|
73
|
+
{ title: "Form Components", value: "forms", selected: true, description: "Form, Checkbox, Select, Switch, etc." },
|
|
74
|
+
{ title: "Feedback Components", value: "feedback", selected: true, description: "Toast, Alert, Progress, Skeleton" },
|
|
75
|
+
{ title: "Navigation Components", value: "navigation", selected: true, description: "Tabs, Breadcrumb, Pagination, Menu" },
|
|
76
|
+
{ title: "Data Components", value: "data", description: "Table, Calendar, Data display" },
|
|
77
|
+
{ title: "Advanced Components", value: "advanced", description: "Command, Carousel, Resizable, Drawer" },
|
|
78
|
+
{ title: "Chart Components", value: "charts", description: "Recharts-based chart components" }
|
|
79
|
+
],
|
|
80
|
+
hint: "- Space to select, Enter to confirm"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: "confirm",
|
|
84
|
+
name: "includePageTemplates",
|
|
85
|
+
message: "Include page templates (27+ layouts)?",
|
|
86
|
+
initial: false
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: "confirm",
|
|
90
|
+
name: "includeComposer",
|
|
91
|
+
message: "Include Composer visual builder tool?",
|
|
92
|
+
initial: false
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: "confirm",
|
|
96
|
+
name: "includeAuth",
|
|
97
|
+
message: "Include authentication scaffolding?",
|
|
98
|
+
initial: false
|
|
99
|
+
}
|
|
100
|
+
], {
|
|
101
|
+
onCancel: () => {
|
|
102
|
+
console.log(chalk.red("\n Setup cancelled.\n"));
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
projectName: response.projectName,
|
|
108
|
+
description: response.description,
|
|
109
|
+
themeModes: response.themeModes,
|
|
110
|
+
themePreset: response.themePreset,
|
|
111
|
+
enableRTL: response.enableRTL,
|
|
112
|
+
enableFontScaling: response.enableFontScaling,
|
|
113
|
+
features: response.features,
|
|
114
|
+
includeAuth: response.includeAuth,
|
|
115
|
+
includePageTemplates: response.includePageTemplates,
|
|
116
|
+
includeComposer: response.includeComposer
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/scaffolder.ts
|
|
121
|
+
import path3 from "path";
|
|
122
|
+
import fs3 from "fs-extra";
|
|
123
|
+
import ora from "ora";
|
|
124
|
+
|
|
125
|
+
// src/generators/package.ts
|
|
126
|
+
function generatePackageJson(config) {
|
|
127
|
+
const dependencies = {
|
|
128
|
+
"react": "^18.3.1",
|
|
129
|
+
"react-dom": "^18.3.1",
|
|
130
|
+
"react-router-dom": "^7.12.0",
|
|
131
|
+
"class-variance-authority": "^0.7.1",
|
|
132
|
+
"clsx": "^2.1.1",
|
|
133
|
+
"tailwind-merge": "^2.5.2",
|
|
134
|
+
"lucide-react": "^0.462.0",
|
|
135
|
+
"@radix-ui/react-slot": "^1.1.0"
|
|
136
|
+
};
|
|
137
|
+
const devDependencies = {
|
|
138
|
+
"@types/react": "^18.3.12",
|
|
139
|
+
"@types/react-dom": "^18.3.1",
|
|
140
|
+
"@vitejs/plugin-react-swc": "^3.5.0",
|
|
141
|
+
"autoprefixer": "^10.4.20",
|
|
142
|
+
"postcss": "^8.4.49",
|
|
143
|
+
"tailwindcss": "^3.4.14",
|
|
144
|
+
"tailwindcss-animate": "^1.0.7",
|
|
145
|
+
"typescript": "^5.6.3",
|
|
146
|
+
"vite": "^5.4.10"
|
|
147
|
+
};
|
|
148
|
+
if (config.features.includes("core")) {
|
|
149
|
+
Object.assign(dependencies, {
|
|
150
|
+
"@radix-ui/react-dialog": "^1.1.2",
|
|
151
|
+
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
|
152
|
+
"@radix-ui/react-tooltip": "^1.1.4",
|
|
153
|
+
"@radix-ui/react-popover": "^1.1.1",
|
|
154
|
+
"@radix-ui/react-separator": "^1.1.0",
|
|
155
|
+
"@radix-ui/react-scroll-area": "^1.1.0",
|
|
156
|
+
"@radix-ui/react-avatar": "^1.1.0",
|
|
157
|
+
"@radix-ui/react-aspect-ratio": "^1.1.0",
|
|
158
|
+
"@radix-ui/react-collapsible": "^1.1.0",
|
|
159
|
+
"@radix-ui/react-accordion": "^1.2.0"
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (config.features.includes("forms")) {
|
|
163
|
+
Object.assign(dependencies, {
|
|
164
|
+
"@radix-ui/react-checkbox": "^1.1.1",
|
|
165
|
+
"@radix-ui/react-radio-group": "^1.2.0",
|
|
166
|
+
"@radix-ui/react-select": "^2.1.1",
|
|
167
|
+
"@radix-ui/react-switch": "^1.1.0",
|
|
168
|
+
"@radix-ui/react-slider": "^1.2.0",
|
|
169
|
+
"@radix-ui/react-label": "^2.1.0",
|
|
170
|
+
"@hookform/resolvers": "^3.9.0",
|
|
171
|
+
"react-hook-form": "^7.53.0",
|
|
172
|
+
"zod": "^3.23.8",
|
|
173
|
+
"input-otp": "^1.2.4"
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (config.features.includes("feedback")) {
|
|
177
|
+
Object.assign(dependencies, {
|
|
178
|
+
"@radix-ui/react-toast": "^1.2.1",
|
|
179
|
+
"@radix-ui/react-alert-dialog": "^1.1.1",
|
|
180
|
+
"@radix-ui/react-progress": "^1.1.0",
|
|
181
|
+
"sonner": "^1.5.0"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (config.features.includes("navigation")) {
|
|
185
|
+
Object.assign(dependencies, {
|
|
186
|
+
"@radix-ui/react-tabs": "^1.1.0",
|
|
187
|
+
"@radix-ui/react-navigation-menu": "^1.2.0",
|
|
188
|
+
"@radix-ui/react-menubar": "^1.1.1",
|
|
189
|
+
"@radix-ui/react-context-menu": "^2.2.1",
|
|
190
|
+
"@radix-ui/react-toggle": "^1.1.0",
|
|
191
|
+
"@radix-ui/react-toggle-group": "^1.1.0"
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (config.features.includes("data")) {
|
|
195
|
+
Object.assign(dependencies, {
|
|
196
|
+
"react-day-picker": "^8.10.1",
|
|
197
|
+
"date-fns": "^3.6.0"
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (config.features.includes("advanced")) {
|
|
201
|
+
Object.assign(dependencies, {
|
|
202
|
+
"cmdk": "^1.0.0",
|
|
203
|
+
"embla-carousel-react": "^8.3.0",
|
|
204
|
+
"embla-carousel-autoplay": "^8.6.0",
|
|
205
|
+
"react-resizable-panels": "^2.1.3",
|
|
206
|
+
"vaul": "^1.1.2",
|
|
207
|
+
"@radix-ui/react-hover-card": "^1.1.1"
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (config.features.includes("charts")) {
|
|
211
|
+
Object.assign(dependencies, {
|
|
212
|
+
"recharts": "^2.12.7"
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
dependencies["motion"] = "^12.18.1";
|
|
216
|
+
dependencies["next-themes"] = "^0.3.0";
|
|
217
|
+
if (config.includeAuth) {
|
|
218
|
+
dependencies["@supabase/supabase-js"] = "^2.84.0";
|
|
219
|
+
dependencies["@tanstack/react-query"] = "^5.56.2";
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
name: config.projectName,
|
|
223
|
+
private: true,
|
|
224
|
+
version: "0.1.0",
|
|
225
|
+
description: config.description,
|
|
226
|
+
type: "module",
|
|
227
|
+
scripts: {
|
|
228
|
+
"dev": "vite",
|
|
229
|
+
"build": "tsc && vite build",
|
|
230
|
+
"preview": "vite preview",
|
|
231
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
|
232
|
+
},
|
|
233
|
+
dependencies,
|
|
234
|
+
devDependencies
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/generators/tailwind.ts
|
|
239
|
+
function generateTailwindConfig(config) {
|
|
240
|
+
return `import type { Config } from "tailwindcss";
|
|
241
|
+
|
|
242
|
+
const config = {
|
|
243
|
+
darkMode: ["class"],
|
|
244
|
+
content: [
|
|
245
|
+
"./pages/**/*.{ts,tsx}",
|
|
246
|
+
"./components/**/*.{ts,tsx}",
|
|
247
|
+
"./app/**/*.{ts,tsx}",
|
|
248
|
+
"./src/**/*.{ts,tsx}",
|
|
249
|
+
],
|
|
250
|
+
prefix: "",
|
|
251
|
+
theme: {
|
|
252
|
+
container: {
|
|
253
|
+
center: true,
|
|
254
|
+
padding: "2rem",
|
|
255
|
+
screens: {
|
|
256
|
+
"2xl": "1400px",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
screens: {
|
|
260
|
+
sm: "640px",
|
|
261
|
+
md: "768px",
|
|
262
|
+
lg: "1024px",
|
|
263
|
+
xl: "1200px",
|
|
264
|
+
"2xl": "1400px",
|
|
265
|
+
},
|
|
266
|
+
extend: {
|
|
267
|
+
fontFamily: {
|
|
268
|
+
sans: ['Circular', 'ui-sans-serif', 'system-ui', 'sans-serif'],
|
|
269
|
+
heading: ['"Circular 20"', 'Circular', 'ui-sans-serif', 'system-ui', 'sans-serif'],
|
|
270
|
+
},
|
|
271
|
+
colors: {
|
|
272
|
+
border: "hsl(var(--border))",
|
|
273
|
+
input: "hsl(var(--input))",
|
|
274
|
+
ring: "hsl(var(--ring))",
|
|
275
|
+
background: "hsl(var(--background))",
|
|
276
|
+
foreground: "hsl(var(--foreground))",
|
|
277
|
+
primary: {
|
|
278
|
+
DEFAULT: "hsl(var(--primary))",
|
|
279
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
280
|
+
},
|
|
281
|
+
secondary: {
|
|
282
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
283
|
+
foreground: "hsl(var(--secondary-foreground))",
|
|
284
|
+
},
|
|
285
|
+
destructive: {
|
|
286
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
287
|
+
foreground: "hsl(var(--destructive-foreground))",
|
|
288
|
+
text: "hsl(var(--destructive-text))",
|
|
289
|
+
},
|
|
290
|
+
success: {
|
|
291
|
+
DEFAULT: "hsl(var(--success))",
|
|
292
|
+
foreground: "hsl(var(--success-foreground))",
|
|
293
|
+
},
|
|
294
|
+
warning: {
|
|
295
|
+
DEFAULT: "hsl(var(--warning))",
|
|
296
|
+
foreground: "hsl(var(--warning-foreground))",
|
|
297
|
+
},
|
|
298
|
+
muted: {
|
|
299
|
+
DEFAULT: "hsl(var(--muted))",
|
|
300
|
+
foreground: "hsl(var(--muted-foreground))",
|
|
301
|
+
},
|
|
302
|
+
accent: {
|
|
303
|
+
DEFAULT: "hsl(var(--accent))",
|
|
304
|
+
foreground: "hsl(var(--accent-foreground))",
|
|
305
|
+
},
|
|
306
|
+
popover: {
|
|
307
|
+
DEFAULT: "hsl(var(--popover))",
|
|
308
|
+
foreground: "hsl(var(--popover-foreground))",
|
|
309
|
+
},
|
|
310
|
+
card: {
|
|
311
|
+
DEFAULT: "hsl(var(--card))",
|
|
312
|
+
foreground: "hsl(var(--card-foreground))",
|
|
313
|
+
},
|
|
314
|
+
sidebar: {
|
|
315
|
+
DEFAULT: "hsl(var(--sidebar-background))",
|
|
316
|
+
foreground: "hsl(var(--sidebar-foreground))",
|
|
317
|
+
primary: "hsl(var(--sidebar-primary))",
|
|
318
|
+
"primary-foreground": "hsl(var(--sidebar-primary-foreground))",
|
|
319
|
+
accent: "hsl(var(--sidebar-accent))",
|
|
320
|
+
"accent-foreground": "hsl(var(--sidebar-accent-foreground))",
|
|
321
|
+
border: "hsl(var(--sidebar-border))",
|
|
322
|
+
ring: "hsl(var(--sidebar-ring))",
|
|
323
|
+
},
|
|
324
|
+
brand: {
|
|
325
|
+
DEFAULT: "hsl(var(--brand))",
|
|
326
|
+
dark: "hsl(var(--brand-dark))",
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
borderRadius: {
|
|
330
|
+
lg: "var(--radius)",
|
|
331
|
+
md: "calc(var(--radius) - 2px)",
|
|
332
|
+
sm: "calc(var(--radius) - 4px)",
|
|
333
|
+
},
|
|
334
|
+
keyframes: {
|
|
335
|
+
"accordion-down": {
|
|
336
|
+
from: { height: "0" },
|
|
337
|
+
to: { height: "var(--radix-accordion-content-height)" },
|
|
338
|
+
},
|
|
339
|
+
"accordion-up": {
|
|
340
|
+
from: { height: "var(--radix-accordion-content-height)" },
|
|
341
|
+
to: { height: "0" },
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
animation: {
|
|
345
|
+
"accordion-down": "accordion-down 0.2s ease-out",
|
|
346
|
+
"accordion-up": "accordion-up 0.2s ease-out",
|
|
347
|
+
"spin-slow": "spin 8s linear infinite",
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
plugins: [require("tailwindcss-animate")],
|
|
352
|
+
} satisfies Config;
|
|
353
|
+
|
|
354
|
+
export default config;
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/generators/vite.ts
|
|
359
|
+
function generateViteConfig() {
|
|
360
|
+
return `import { defineConfig } from "vite";
|
|
361
|
+
import react from "@vitejs/plugin-react-swc";
|
|
362
|
+
import path from "path";
|
|
363
|
+
|
|
364
|
+
// https://vitejs.dev/config/
|
|
365
|
+
export default defineConfig({
|
|
366
|
+
server: {
|
|
367
|
+
host: "::",
|
|
368
|
+
port: 8080,
|
|
369
|
+
},
|
|
370
|
+
plugins: [react()],
|
|
371
|
+
resolve: {
|
|
372
|
+
alias: {
|
|
373
|
+
"@": path.resolve(__dirname, "./src"),
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/generators/styles.ts
|
|
381
|
+
function generateIndexCSS(config) {
|
|
382
|
+
let css = `@tailwind base;
|
|
383
|
+
@tailwind components;
|
|
384
|
+
@tailwind utilities;
|
|
385
|
+
|
|
386
|
+
/* Waypoint 2.0 Design System */
|
|
387
|
+
|
|
388
|
+
`;
|
|
389
|
+
css += `@layer base {
|
|
390
|
+
`;
|
|
391
|
+
if (config.themeModes.includes("light")) {
|
|
392
|
+
css += ` :root {
|
|
393
|
+
--background: 0 0% 100%;
|
|
394
|
+
--foreground: 222.2 84% 4.9%;
|
|
395
|
+
|
|
396
|
+
--card: 0 0% 100%;
|
|
397
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
398
|
+
|
|
399
|
+
--popover: 0 0% 100%;
|
|
400
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
401
|
+
|
|
402
|
+
--primary: 222.2 47.4% 11.2%;
|
|
403
|
+
--primary-foreground: 210 40% 98%;
|
|
404
|
+
|
|
405
|
+
--secondary: 210 40% 96.1%;
|
|
406
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
407
|
+
|
|
408
|
+
--muted: 210 40% 96.1%;
|
|
409
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
410
|
+
|
|
411
|
+
--accent: 210 40% 96.1%;
|
|
412
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
413
|
+
|
|
414
|
+
--destructive: 0 84.2% 60.2%;
|
|
415
|
+
--destructive-foreground: 210 40% 98%;
|
|
416
|
+
--destructive-text: 0 84.2% 45%;
|
|
417
|
+
|
|
418
|
+
--success: 142 76% 36%;
|
|
419
|
+
--success-foreground: 0 0% 100%;
|
|
420
|
+
--warning: 38 92% 50%;
|
|
421
|
+
--warning-foreground: 38 92% 20%;
|
|
422
|
+
|
|
423
|
+
--border: 214.3 31.8% 91.4%;
|
|
424
|
+
--input: 214.3 31.8% 91.4%;
|
|
425
|
+
--ring: 222.2 84% 4.9%;
|
|
426
|
+
|
|
427
|
+
--radius: 0.5rem;
|
|
428
|
+
|
|
429
|
+
--brand: 217 100% 50%;
|
|
430
|
+
--brand-dark: 218 100% 33%;
|
|
431
|
+
|
|
432
|
+
--sidebar-background: 0 0% 98%;
|
|
433
|
+
--sidebar-foreground: 240 5.3% 26.1%;
|
|
434
|
+
--sidebar-primary: 240 5.9% 10%;
|
|
435
|
+
--sidebar-primary-foreground: 0 0% 98%;
|
|
436
|
+
--sidebar-accent: 240 4.8% 95.9%;
|
|
437
|
+
--sidebar-accent-foreground: 240 5.9% 10%;
|
|
438
|
+
--sidebar-border: 220 13% 91%;
|
|
439
|
+
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
`;
|
|
443
|
+
}
|
|
444
|
+
if (config.themeModes.includes("dark")) {
|
|
445
|
+
css += ` .dark {
|
|
446
|
+
--background: 222.2 84% 4.9%;
|
|
447
|
+
--foreground: 210 40% 98%;
|
|
448
|
+
|
|
449
|
+
--card: 222.2 84% 4.9%;
|
|
450
|
+
--card-foreground: 210 40% 98%;
|
|
451
|
+
|
|
452
|
+
--popover: 222.2 84% 4.9%;
|
|
453
|
+
--popover-foreground: 210 40% 98%;
|
|
454
|
+
|
|
455
|
+
--primary: 210 40% 98%;
|
|
456
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
457
|
+
|
|
458
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
459
|
+
--secondary-foreground: 210 40% 98%;
|
|
460
|
+
|
|
461
|
+
--muted: 217.2 32.6% 17.5%;
|
|
462
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
463
|
+
|
|
464
|
+
--accent: 217.2 32.6% 17.5%;
|
|
465
|
+
--accent-foreground: 210 40% 98%;
|
|
466
|
+
|
|
467
|
+
--destructive: 0 62.8% 30.6%;
|
|
468
|
+
--destructive-foreground: 210 40% 98%;
|
|
469
|
+
--destructive-text: 0 84.2% 65%;
|
|
470
|
+
|
|
471
|
+
--success: 142 76% 46%;
|
|
472
|
+
--success-foreground: 0 0% 100%;
|
|
473
|
+
--warning: 38 92% 50%;
|
|
474
|
+
--warning-foreground: 38 92% 90%;
|
|
475
|
+
|
|
476
|
+
--border: 217.2 32.6% 17.5%;
|
|
477
|
+
--input: 217.2 32.6% 17.5%;
|
|
478
|
+
--ring: 212.7 26.8% 83.9%;
|
|
479
|
+
|
|
480
|
+
--brand: 217 100% 50%;
|
|
481
|
+
--brand-dark: 218 100% 33%;
|
|
482
|
+
|
|
483
|
+
--sidebar-background: 240 5.9% 10%;
|
|
484
|
+
--sidebar-foreground: 240 4.8% 95.9%;
|
|
485
|
+
--sidebar-primary: 224.3 76.3% 48%;
|
|
486
|
+
--sidebar-primary-foreground: 0 0% 100%;
|
|
487
|
+
--sidebar-accent: 240 3.7% 15.9%;
|
|
488
|
+
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
|
489
|
+
--sidebar-border: 240 3.7% 15.9%;
|
|
490
|
+
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
`;
|
|
494
|
+
}
|
|
495
|
+
if (config.themeModes.includes("high-contrast-light")) {
|
|
496
|
+
css += ` /* High Contrast Light Mode */
|
|
497
|
+
.high-contrast {
|
|
498
|
+
--background: 0 0% 100%;
|
|
499
|
+
--foreground: 0 0% 0%;
|
|
500
|
+
|
|
501
|
+
--card: 0 0% 100%;
|
|
502
|
+
--card-foreground: 0 0% 0%;
|
|
503
|
+
|
|
504
|
+
--popover: 0 0% 100%;
|
|
505
|
+
--popover-foreground: 0 0% 0%;
|
|
506
|
+
|
|
507
|
+
--primary: 0 0% 0%;
|
|
508
|
+
--primary-foreground: 0 0% 100%;
|
|
509
|
+
|
|
510
|
+
--secondary: 0 0% 96%;
|
|
511
|
+
--secondary-foreground: 0 0% 0%;
|
|
512
|
+
|
|
513
|
+
--muted: 0 0% 96%;
|
|
514
|
+
--muted-foreground: 0 0% 25%;
|
|
515
|
+
|
|
516
|
+
--accent: 0 0% 96%;
|
|
517
|
+
--accent-foreground: 0 0% 0%;
|
|
518
|
+
|
|
519
|
+
--destructive: 0 100% 40%;
|
|
520
|
+
--destructive-foreground: 0 0% 100%;
|
|
521
|
+
|
|
522
|
+
--border: 0 0% 0%;
|
|
523
|
+
--input: 0 0% 0%;
|
|
524
|
+
--ring: 0 0% 0%;
|
|
525
|
+
|
|
526
|
+
--sidebar-background: 0 0% 100%;
|
|
527
|
+
--sidebar-foreground: 0 0% 0%;
|
|
528
|
+
--sidebar-primary: 0 0% 0%;
|
|
529
|
+
--sidebar-primary-foreground: 0 0% 100%;
|
|
530
|
+
--sidebar-accent: 0 0% 96%;
|
|
531
|
+
--sidebar-accent-foreground: 0 0% 0%;
|
|
532
|
+
--sidebar-border: 0 0% 0%;
|
|
533
|
+
--sidebar-ring: 0 0% 0%;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
`;
|
|
537
|
+
}
|
|
538
|
+
if (config.themeModes.includes("high-contrast-dark")) {
|
|
539
|
+
css += ` /* High Contrast Dark Mode */
|
|
540
|
+
.dark.high-contrast {
|
|
541
|
+
--background: 0 0% 0%;
|
|
542
|
+
--foreground: 0 0% 100%;
|
|
543
|
+
|
|
544
|
+
--card: 0 0% 0%;
|
|
545
|
+
--card-foreground: 0 0% 100%;
|
|
546
|
+
|
|
547
|
+
--popover: 0 0% 0%;
|
|
548
|
+
--popover-foreground: 0 0% 100%;
|
|
549
|
+
|
|
550
|
+
--primary: 0 0% 100%;
|
|
551
|
+
--primary-foreground: 0 0% 0%;
|
|
552
|
+
|
|
553
|
+
--secondary: 0 0% 15%;
|
|
554
|
+
--secondary-foreground: 0 0% 100%;
|
|
555
|
+
|
|
556
|
+
--muted: 0 0% 15%;
|
|
557
|
+
--muted-foreground: 0 0% 75%;
|
|
558
|
+
|
|
559
|
+
--accent: 0 0% 15%;
|
|
560
|
+
--accent-foreground: 0 0% 100%;
|
|
561
|
+
|
|
562
|
+
--destructive: 0 100% 60%;
|
|
563
|
+
--destructive-foreground: 0 0% 0%;
|
|
564
|
+
|
|
565
|
+
--border: 0 0% 100%;
|
|
566
|
+
--input: 0 0% 100%;
|
|
567
|
+
--ring: 0 0% 100%;
|
|
568
|
+
|
|
569
|
+
--sidebar-background: 0 0% 0%;
|
|
570
|
+
--sidebar-foreground: 0 0% 100%;
|
|
571
|
+
--sidebar-primary: 0 0% 100%;
|
|
572
|
+
--sidebar-primary-foreground: 0 0% 0%;
|
|
573
|
+
--sidebar-accent: 0 0% 15%;
|
|
574
|
+
--sidebar-accent-foreground: 0 0% 100%;
|
|
575
|
+
--sidebar-border: 0 0% 100%;
|
|
576
|
+
--sidebar-ring: 0 0% 100%;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
`;
|
|
580
|
+
}
|
|
581
|
+
if (config.enableFontScaling) {
|
|
582
|
+
css += ` /* Font scaling classes */
|
|
583
|
+
.font-scale-small {
|
|
584
|
+
font-size: calc(var(--font-scale, 1) * 1rem);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.font-scale-medium {
|
|
588
|
+
font-size: calc(var(--font-scale, 1) * 1rem);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.font-scale-large {
|
|
592
|
+
font-size: calc(var(--font-scale, 1) * 1rem);
|
|
593
|
+
}
|
|
594
|
+
`;
|
|
595
|
+
}
|
|
596
|
+
css += `}
|
|
597
|
+
|
|
598
|
+
`;
|
|
599
|
+
css += `@layer base {
|
|
600
|
+
* {
|
|
601
|
+
@apply border-border;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
body {
|
|
605
|
+
@apply bg-background text-foreground;`;
|
|
606
|
+
if (config.enableFontScaling) {
|
|
607
|
+
css += `
|
|
608
|
+
font-size: calc(1rem * var(--font-scale, 1));`;
|
|
609
|
+
}
|
|
610
|
+
css += `
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
`;
|
|
614
|
+
if (config.enableRTL) {
|
|
615
|
+
css += ` /* RTL Support */
|
|
616
|
+
.rtl {
|
|
617
|
+
direction: rtl;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.rtl .text-left {
|
|
621
|
+
text-align: right;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.rtl .text-right {
|
|
625
|
+
text-align: left;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.rtl .ml-2 {
|
|
629
|
+
margin-left: 0;
|
|
630
|
+
margin-right: 0.5rem;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.rtl .mr-2 {
|
|
634
|
+
margin-right: 0;
|
|
635
|
+
margin-left: 0.5rem;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.rtl .pl-3 {
|
|
639
|
+
padding-left: 0;
|
|
640
|
+
padding-right: 0.75rem;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.rtl .pr-3 {
|
|
644
|
+
padding-right: 0;
|
|
645
|
+
padding-left: 0.75rem;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
`;
|
|
649
|
+
}
|
|
650
|
+
if (config.enableFontScaling) {
|
|
651
|
+
css += ` /* Scale all text elements */
|
|
652
|
+
h1, h2, h3, h4, h5, h6, p, span, div, button, input, textarea, label, a {
|
|
653
|
+
font-size: calc(1em * var(--font-scale, 1));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
`;
|
|
657
|
+
}
|
|
658
|
+
css += ` /* Heading font family + size hierarchy */
|
|
659
|
+
h1, h2, h3, h4, h5, h6 {
|
|
660
|
+
font-family: "Circular 20", Circular, ui-sans-serif, system-ui, sans-serif;
|
|
661
|
+
font-weight: 700;
|
|
662
|
+
}
|
|
663
|
+
`;
|
|
664
|
+
if (config.enableFontScaling) {
|
|
665
|
+
css += ` h1 { font-size: calc(2.25rem * var(--font-scale, 1)); }
|
|
666
|
+
h2 { font-size: calc(1.875rem * var(--font-scale, 1)); }
|
|
667
|
+
h3 { font-size: calc(1.5rem * var(--font-scale, 1)); }
|
|
668
|
+
h4 { font-size: calc(1.25rem * var(--font-scale, 1)); }
|
|
669
|
+
h5 { font-size: calc(1.125rem * var(--font-scale, 1)); }
|
|
670
|
+
h6 { font-size: calc(1rem * var(--font-scale, 1)); }
|
|
671
|
+
|
|
672
|
+
/* Scale text utility classes */
|
|
673
|
+
.text-sm { font-size: calc(0.875rem * var(--font-scale, 1)); }
|
|
674
|
+
.text-xs { font-size: calc(0.75rem * var(--font-scale, 1)); }
|
|
675
|
+
.text-lg { font-size: calc(1.125rem * var(--font-scale, 1)); }
|
|
676
|
+
.text-xl { font-size: calc(1.25rem * var(--font-scale, 1)); }
|
|
677
|
+
.text-2xl { font-size: calc(1.5rem * var(--font-scale, 1)); }
|
|
678
|
+
.text-3xl { font-size: calc(1.875rem * var(--font-scale, 1)); }
|
|
679
|
+
`;
|
|
680
|
+
}
|
|
681
|
+
css += `
|
|
682
|
+
pre {
|
|
683
|
+
@apply text-left;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
`;
|
|
687
|
+
return css;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/generators/config.ts
|
|
691
|
+
function generateTsConfig() {
|
|
692
|
+
return {
|
|
693
|
+
compilerOptions: {
|
|
694
|
+
target: "ES2020",
|
|
695
|
+
useDefineForClassFields: true,
|
|
696
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
697
|
+
module: "ESNext",
|
|
698
|
+
skipLibCheck: true,
|
|
699
|
+
moduleResolution: "bundler",
|
|
700
|
+
allowImportingTsExtensions: true,
|
|
701
|
+
resolveJsonModule: true,
|
|
702
|
+
isolatedModules: true,
|
|
703
|
+
noEmit: true,
|
|
704
|
+
jsx: "react-jsx",
|
|
705
|
+
strict: true,
|
|
706
|
+
noUnusedLocals: true,
|
|
707
|
+
noUnusedParameters: true,
|
|
708
|
+
noFallthroughCasesInSwitch: true,
|
|
709
|
+
baseUrl: ".",
|
|
710
|
+
paths: {
|
|
711
|
+
"@/*": ["./src/*"]
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
include: ["src"],
|
|
715
|
+
references: [{ path: "./tsconfig.node.json" }]
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function generateComponentsJson() {
|
|
719
|
+
return {
|
|
720
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
721
|
+
style: "default",
|
|
722
|
+
rsc: false,
|
|
723
|
+
tsx: true,
|
|
724
|
+
tailwind: {
|
|
725
|
+
config: "tailwind.config.ts",
|
|
726
|
+
css: "src/index.css",
|
|
727
|
+
baseColor: "slate",
|
|
728
|
+
cssVariables: true,
|
|
729
|
+
prefix: ""
|
|
730
|
+
},
|
|
731
|
+
aliases: {
|
|
732
|
+
components: "@/components",
|
|
733
|
+
utils: "@/lib/utils",
|
|
734
|
+
ui: "@/components/ui",
|
|
735
|
+
lib: "@/lib",
|
|
736
|
+
hooks: "@/hooks"
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/generators/components.ts
|
|
742
|
+
import path from "path";
|
|
743
|
+
import fs from "fs-extra";
|
|
744
|
+
var componentMappings = {
|
|
745
|
+
core: [
|
|
746
|
+
"button",
|
|
747
|
+
"card",
|
|
748
|
+
"dialog",
|
|
749
|
+
"dropdown-menu",
|
|
750
|
+
"input",
|
|
751
|
+
"separator",
|
|
752
|
+
"scroll-area",
|
|
753
|
+
"avatar",
|
|
754
|
+
"aspect-ratio",
|
|
755
|
+
"collapsible",
|
|
756
|
+
"accordion",
|
|
757
|
+
"badge",
|
|
758
|
+
"skeleton",
|
|
759
|
+
"tooltip",
|
|
760
|
+
"popover"
|
|
761
|
+
],
|
|
762
|
+
forms: [
|
|
763
|
+
"checkbox",
|
|
764
|
+
"form",
|
|
765
|
+
"input",
|
|
766
|
+
"label",
|
|
767
|
+
"radio-group",
|
|
768
|
+
"select",
|
|
769
|
+
"slider",
|
|
770
|
+
"switch",
|
|
771
|
+
"textarea",
|
|
772
|
+
"input-otp"
|
|
773
|
+
],
|
|
774
|
+
feedback: [
|
|
775
|
+
"alert",
|
|
776
|
+
"alert-dialog",
|
|
777
|
+
"progress",
|
|
778
|
+
"toast",
|
|
779
|
+
"toaster",
|
|
780
|
+
"sonner"
|
|
781
|
+
],
|
|
782
|
+
navigation: [
|
|
783
|
+
"breadcrumb",
|
|
784
|
+
"navigation-menu",
|
|
785
|
+
"menubar",
|
|
786
|
+
"context-menu",
|
|
787
|
+
"pagination",
|
|
788
|
+
"tabs",
|
|
789
|
+
"toggle",
|
|
790
|
+
"toggle-group"
|
|
791
|
+
],
|
|
792
|
+
data: [
|
|
793
|
+
"calendar",
|
|
794
|
+
"table"
|
|
795
|
+
],
|
|
796
|
+
advanced: [
|
|
797
|
+
"carousel",
|
|
798
|
+
"command",
|
|
799
|
+
"drawer",
|
|
800
|
+
"hover-card",
|
|
801
|
+
"resizable",
|
|
802
|
+
"sheet"
|
|
803
|
+
],
|
|
804
|
+
charts: [
|
|
805
|
+
"chart"
|
|
806
|
+
]
|
|
807
|
+
};
|
|
808
|
+
async function generateComponents(projectPath, config) {
|
|
809
|
+
await generateUtilFiles(projectPath);
|
|
810
|
+
const componentsToGenerate = /* @__PURE__ */ new Set();
|
|
811
|
+
for (const feature of config.features) {
|
|
812
|
+
const components = componentMappings[feature] || [];
|
|
813
|
+
components.forEach((c) => componentsToGenerate.add(c));
|
|
814
|
+
}
|
|
815
|
+
for (const component of componentsToGenerate) {
|
|
816
|
+
await generateComponentFile(projectPath, component);
|
|
817
|
+
}
|
|
818
|
+
await generateComponentIndex(projectPath, Array.from(componentsToGenerate));
|
|
819
|
+
}
|
|
820
|
+
async function generateUtilFiles(projectPath) {
|
|
821
|
+
const utilsContent = `import { type ClassValue, clsx } from "clsx";
|
|
822
|
+
import { twMerge } from "tailwind-merge";
|
|
823
|
+
|
|
824
|
+
export function cn(...inputs: ClassValue[]) {
|
|
825
|
+
return twMerge(clsx(inputs));
|
|
826
|
+
}
|
|
827
|
+
`;
|
|
828
|
+
await fs.writeFile(path.join(projectPath, "src/lib/utils.ts"), utilsContent);
|
|
829
|
+
const useMobileContent = `import * as React from "react";
|
|
830
|
+
|
|
831
|
+
const MOBILE_BREAKPOINT = 768;
|
|
832
|
+
|
|
833
|
+
export function useIsMobile() {
|
|
834
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
|
|
835
|
+
|
|
836
|
+
React.useEffect(() => {
|
|
837
|
+
const mql = window.matchMedia(\`(max-width: \${MOBILE_BREAKPOINT - 1}px)\`);
|
|
838
|
+
const onChange = () => {
|
|
839
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
840
|
+
};
|
|
841
|
+
mql.addEventListener("change", onChange);
|
|
842
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
843
|
+
return () => mql.removeEventListener("change", onChange);
|
|
844
|
+
}, []);
|
|
845
|
+
|
|
846
|
+
return !!isMobile;
|
|
847
|
+
}
|
|
848
|
+
`;
|
|
849
|
+
await fs.writeFile(path.join(projectPath, "src/hooks/use-mobile.tsx"), useMobileContent);
|
|
850
|
+
const useToastContent = `import * as React from "react";
|
|
851
|
+
|
|
852
|
+
const TOAST_LIMIT = 1;
|
|
853
|
+
const TOAST_REMOVE_DELAY = 1000000;
|
|
854
|
+
|
|
855
|
+
type ToasterToast = {
|
|
856
|
+
id: string;
|
|
857
|
+
title?: React.ReactNode;
|
|
858
|
+
description?: React.ReactNode;
|
|
859
|
+
action?: React.ReactNode;
|
|
860
|
+
variant?: "default" | "destructive";
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
type State = {
|
|
864
|
+
toasts: ToasterToast[];
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
let count = 0;
|
|
868
|
+
function genId() {
|
|
869
|
+
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
|
870
|
+
return count.toString();
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
|
874
|
+
|
|
875
|
+
const addToRemoveQueue = (toastId: string) => {
|
|
876
|
+
if (toastTimeouts.has(toastId)) {
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
const timeout = setTimeout(() => {
|
|
881
|
+
toastTimeouts.delete(toastId);
|
|
882
|
+
dispatch({
|
|
883
|
+
type: "REMOVE_TOAST",
|
|
884
|
+
toastId: toastId,
|
|
885
|
+
});
|
|
886
|
+
}, TOAST_REMOVE_DELAY);
|
|
887
|
+
|
|
888
|
+
toastTimeouts.set(toastId, timeout);
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
type Action =
|
|
892
|
+
| { type: "ADD_TOAST"; toast: ToasterToast }
|
|
893
|
+
| { type: "UPDATE_TOAST"; toast: Partial<ToasterToast> }
|
|
894
|
+
| { type: "DISMISS_TOAST"; toastId?: string }
|
|
895
|
+
| { type: "REMOVE_TOAST"; toastId?: string };
|
|
896
|
+
|
|
897
|
+
const listeners: Array<(state: State) => void> = [];
|
|
898
|
+
|
|
899
|
+
let memoryState: State = { toasts: [] };
|
|
900
|
+
|
|
901
|
+
function dispatch(action: Action) {
|
|
902
|
+
memoryState = reducer(memoryState, action);
|
|
903
|
+
listeners.forEach((listener) => {
|
|
904
|
+
listener(memoryState);
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function reducer(state: State, action: Action): State {
|
|
909
|
+
switch (action.type) {
|
|
910
|
+
case "ADD_TOAST":
|
|
911
|
+
return {
|
|
912
|
+
...state,
|
|
913
|
+
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
case "UPDATE_TOAST":
|
|
917
|
+
return {
|
|
918
|
+
...state,
|
|
919
|
+
toasts: state.toasts.map((t) =>
|
|
920
|
+
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
|
921
|
+
),
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
case "DISMISS_TOAST": {
|
|
925
|
+
const { toastId } = action;
|
|
926
|
+
if (toastId) {
|
|
927
|
+
addToRemoveQueue(toastId);
|
|
928
|
+
} else {
|
|
929
|
+
state.toasts.forEach((toast) => {
|
|
930
|
+
addToRemoveQueue(toast.id);
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
return state;
|
|
934
|
+
}
|
|
935
|
+
case "REMOVE_TOAST":
|
|
936
|
+
if (action.toastId === undefined) {
|
|
937
|
+
return { ...state, toasts: [] };
|
|
938
|
+
}
|
|
939
|
+
return {
|
|
940
|
+
...state,
|
|
941
|
+
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function toast({ ...props }: Omit<ToasterToast, "id">) {
|
|
947
|
+
const id = genId();
|
|
948
|
+
dispatch({
|
|
949
|
+
type: "ADD_TOAST",
|
|
950
|
+
toast: { ...props, id },
|
|
951
|
+
});
|
|
952
|
+
return {
|
|
953
|
+
id,
|
|
954
|
+
dismiss: () => dispatch({ type: "DISMISS_TOAST", toastId: id }),
|
|
955
|
+
update: (props: Partial<ToasterToast>) =>
|
|
956
|
+
dispatch({ type: "UPDATE_TOAST", toast: { ...props, id } }),
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function useToast() {
|
|
961
|
+
const [state, setState] = React.useState<State>(memoryState);
|
|
962
|
+
|
|
963
|
+
React.useEffect(() => {
|
|
964
|
+
listeners.push(setState);
|
|
965
|
+
return () => {
|
|
966
|
+
const index = listeners.indexOf(setState);
|
|
967
|
+
if (index > -1) {
|
|
968
|
+
listeners.splice(index, 1);
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
}, [state]);
|
|
972
|
+
|
|
973
|
+
return {
|
|
974
|
+
...state,
|
|
975
|
+
toast,
|
|
976
|
+
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export { useToast, toast };
|
|
981
|
+
`;
|
|
982
|
+
await fs.writeFile(path.join(projectPath, "src/hooks/use-toast.ts"), useToastContent);
|
|
983
|
+
}
|
|
984
|
+
async function generateComponentFile(projectPath, componentName) {
|
|
985
|
+
const content = getComponentTemplate(componentName);
|
|
986
|
+
if (content) {
|
|
987
|
+
const fileName = `${componentName}.tsx`;
|
|
988
|
+
await fs.writeFile(path.join(projectPath, "src/components/ui", fileName), content);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async function generateComponentIndex(projectPath, components) {
|
|
992
|
+
const exports = components.map((c) => `export * from "./${c}";`).join("\n");
|
|
993
|
+
await fs.writeFile(path.join(projectPath, "src/components/ui/index.ts"), exports + "\n");
|
|
994
|
+
}
|
|
995
|
+
function getComponentTemplate(name) {
|
|
996
|
+
const templates = {
|
|
997
|
+
button: `import * as React from "react";
|
|
998
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
999
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
1000
|
+
import { cn } from "@/lib/utils";
|
|
1001
|
+
|
|
1002
|
+
const buttonVariants = cva(
|
|
1003
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 min-w-6 min-h-6",
|
|
1004
|
+
{
|
|
1005
|
+
variants: {
|
|
1006
|
+
variant: {
|
|
1007
|
+
default: "bg-blue-600 text-white hover:bg-blue-700",
|
|
1008
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
1009
|
+
"outline-destructive": "border border-input bg-background text-destructive-text hover:bg-destructive/10 hover:text-destructive-text",
|
|
1010
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
1011
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
1012
|
+
tertiary: "hover:bg-accent hover:text-accent-foreground",
|
|
1013
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
1014
|
+
success: "bg-green-600 text-white hover:bg-green-700",
|
|
1015
|
+
cautionary: "bg-orange-500 text-white hover:bg-orange-600",
|
|
1016
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
1017
|
+
},
|
|
1018
|
+
size: {
|
|
1019
|
+
default: "h-10 px-4 py-2",
|
|
1020
|
+
sm: "h-9 rounded-md px-3",
|
|
1021
|
+
lg: "h-11 rounded-md px-8",
|
|
1022
|
+
icon: "h-11 w-11",
|
|
1023
|
+
},
|
|
1024
|
+
},
|
|
1025
|
+
defaultVariants: {
|
|
1026
|
+
variant: "default",
|
|
1027
|
+
size: "default",
|
|
1028
|
+
},
|
|
1029
|
+
}
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
export interface ButtonProps
|
|
1033
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
1034
|
+
VariantProps<typeof buttonVariants> {
|
|
1035
|
+
asChild?: boolean;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
1039
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
1040
|
+
const Comp = asChild ? Slot : "button";
|
|
1041
|
+
return (
|
|
1042
|
+
<Comp
|
|
1043
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
1044
|
+
ref={ref}
|
|
1045
|
+
{...props}
|
|
1046
|
+
/>
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
);
|
|
1050
|
+
Button.displayName = "Button";
|
|
1051
|
+
|
|
1052
|
+
export { Button, buttonVariants };
|
|
1053
|
+
`,
|
|
1054
|
+
card: `import * as React from "react";
|
|
1055
|
+
import { cn } from "@/lib/utils";
|
|
1056
|
+
|
|
1057
|
+
const Card = React.forwardRef<
|
|
1058
|
+
HTMLDivElement,
|
|
1059
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1060
|
+
>(({ className, ...props }, ref) => (
|
|
1061
|
+
<div
|
|
1062
|
+
ref={ref}
|
|
1063
|
+
className={cn(
|
|
1064
|
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
1065
|
+
className
|
|
1066
|
+
)}
|
|
1067
|
+
{...props}
|
|
1068
|
+
/>
|
|
1069
|
+
));
|
|
1070
|
+
Card.displayName = "Card";
|
|
1071
|
+
|
|
1072
|
+
const CardHeader = React.forwardRef<
|
|
1073
|
+
HTMLDivElement,
|
|
1074
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1075
|
+
>(({ className, ...props }, ref) => (
|
|
1076
|
+
<div
|
|
1077
|
+
ref={ref}
|
|
1078
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
1079
|
+
{...props}
|
|
1080
|
+
/>
|
|
1081
|
+
));
|
|
1082
|
+
CardHeader.displayName = "CardHeader";
|
|
1083
|
+
|
|
1084
|
+
const CardTitle = React.forwardRef<
|
|
1085
|
+
HTMLParagraphElement,
|
|
1086
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
1087
|
+
>(({ className, ...props }, ref) => (
|
|
1088
|
+
<h3
|
|
1089
|
+
ref={ref}
|
|
1090
|
+
className={cn(
|
|
1091
|
+
"text-2xl font-semibold leading-none tracking-tight",
|
|
1092
|
+
className
|
|
1093
|
+
)}
|
|
1094
|
+
{...props}
|
|
1095
|
+
/>
|
|
1096
|
+
));
|
|
1097
|
+
CardTitle.displayName = "CardTitle";
|
|
1098
|
+
|
|
1099
|
+
const CardDescription = React.forwardRef<
|
|
1100
|
+
HTMLParagraphElement,
|
|
1101
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
1102
|
+
>(({ className, ...props }, ref) => (
|
|
1103
|
+
<p
|
|
1104
|
+
ref={ref}
|
|
1105
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
1106
|
+
{...props}
|
|
1107
|
+
/>
|
|
1108
|
+
));
|
|
1109
|
+
CardDescription.displayName = "CardDescription";
|
|
1110
|
+
|
|
1111
|
+
const CardContent = React.forwardRef<
|
|
1112
|
+
HTMLDivElement,
|
|
1113
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1114
|
+
>(({ className, ...props }, ref) => (
|
|
1115
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
1116
|
+
));
|
|
1117
|
+
CardContent.displayName = "CardContent";
|
|
1118
|
+
|
|
1119
|
+
const CardFooter = React.forwardRef<
|
|
1120
|
+
HTMLDivElement,
|
|
1121
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
1122
|
+
>(({ className, ...props }, ref) => (
|
|
1123
|
+
<div
|
|
1124
|
+
ref={ref}
|
|
1125
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
1126
|
+
{...props}
|
|
1127
|
+
/>
|
|
1128
|
+
));
|
|
1129
|
+
CardFooter.displayName = "CardFooter";
|
|
1130
|
+
|
|
1131
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
|
1132
|
+
`,
|
|
1133
|
+
input: `import * as React from "react";
|
|
1134
|
+
import { cn } from "@/lib/utils";
|
|
1135
|
+
|
|
1136
|
+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|
1137
|
+
({ className, type, ...props }, ref) => {
|
|
1138
|
+
return (
|
|
1139
|
+
<input
|
|
1140
|
+
type={type}
|
|
1141
|
+
className={cn(
|
|
1142
|
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
1143
|
+
className
|
|
1144
|
+
)}
|
|
1145
|
+
ref={ref}
|
|
1146
|
+
{...props}
|
|
1147
|
+
/>
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
);
|
|
1151
|
+
Input.displayName = "Input";
|
|
1152
|
+
|
|
1153
|
+
export { Input };
|
|
1154
|
+
`,
|
|
1155
|
+
label: `import * as React from "react";
|
|
1156
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
1157
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
1158
|
+
import { cn } from "@/lib/utils";
|
|
1159
|
+
|
|
1160
|
+
const labelVariants = cva(
|
|
1161
|
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
1162
|
+
);
|
|
1163
|
+
|
|
1164
|
+
const Label = React.forwardRef<
|
|
1165
|
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
1166
|
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
|
1167
|
+
VariantProps<typeof labelVariants>
|
|
1168
|
+
>(({ className, ...props }, ref) => (
|
|
1169
|
+
<LabelPrimitive.Root
|
|
1170
|
+
ref={ref}
|
|
1171
|
+
className={cn(labelVariants(), className)}
|
|
1172
|
+
{...props}
|
|
1173
|
+
/>
|
|
1174
|
+
));
|
|
1175
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
1176
|
+
|
|
1177
|
+
export { Label };
|
|
1178
|
+
`,
|
|
1179
|
+
badge: `import * as React from "react";
|
|
1180
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
1181
|
+
import { cn } from "@/lib/utils";
|
|
1182
|
+
|
|
1183
|
+
const badgeVariants = cva(
|
|
1184
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
1185
|
+
{
|
|
1186
|
+
variants: {
|
|
1187
|
+
variant: {
|
|
1188
|
+
default:
|
|
1189
|
+
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
|
1190
|
+
secondary:
|
|
1191
|
+
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
1192
|
+
destructive:
|
|
1193
|
+
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
1194
|
+
outline: "text-foreground",
|
|
1195
|
+
},
|
|
1196
|
+
},
|
|
1197
|
+
defaultVariants: {
|
|
1198
|
+
variant: "default",
|
|
1199
|
+
},
|
|
1200
|
+
}
|
|
1201
|
+
);
|
|
1202
|
+
|
|
1203
|
+
export interface BadgeProps
|
|
1204
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
1205
|
+
VariantProps<typeof badgeVariants> {}
|
|
1206
|
+
|
|
1207
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
1208
|
+
return (
|
|
1209
|
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
export { Badge, badgeVariants };
|
|
1214
|
+
`,
|
|
1215
|
+
separator: `import * as React from "react";
|
|
1216
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
1217
|
+
import { cn } from "@/lib/utils";
|
|
1218
|
+
|
|
1219
|
+
const Separator = React.forwardRef<
|
|
1220
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
1221
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
1222
|
+
>(
|
|
1223
|
+
(
|
|
1224
|
+
{ className, orientation = "horizontal", decorative = true, ...props },
|
|
1225
|
+
ref
|
|
1226
|
+
) => (
|
|
1227
|
+
<SeparatorPrimitive.Root
|
|
1228
|
+
ref={ref}
|
|
1229
|
+
decorative={decorative}
|
|
1230
|
+
orientation={orientation}
|
|
1231
|
+
className={cn(
|
|
1232
|
+
"shrink-0 bg-border",
|
|
1233
|
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
|
1234
|
+
className
|
|
1235
|
+
)}
|
|
1236
|
+
{...props}
|
|
1237
|
+
/>
|
|
1238
|
+
)
|
|
1239
|
+
);
|
|
1240
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
1241
|
+
|
|
1242
|
+
export { Separator };
|
|
1243
|
+
`,
|
|
1244
|
+
skeleton: `import { cn } from "@/lib/utils";
|
|
1245
|
+
|
|
1246
|
+
function Skeleton({
|
|
1247
|
+
className,
|
|
1248
|
+
...props
|
|
1249
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
1250
|
+
return (
|
|
1251
|
+
<div
|
|
1252
|
+
className={cn("animate-pulse rounded-md bg-muted", className)}
|
|
1253
|
+
{...props}
|
|
1254
|
+
/>
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
export { Skeleton };
|
|
1259
|
+
`
|
|
1260
|
+
};
|
|
1261
|
+
return templates[name] || null;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// src/generators/app.ts
|
|
1265
|
+
import path2 from "path";
|
|
1266
|
+
import fs2 from "fs-extra";
|
|
1267
|
+
async function generateAppFiles(projectPath, config) {
|
|
1268
|
+
const mainContent = `import { StrictMode } from "react";
|
|
1269
|
+
import { createRoot } from "react-dom/client";
|
|
1270
|
+
import { BrowserRouter } from "react-router-dom";
|
|
1271
|
+
import App from "./App.tsx";
|
|
1272
|
+
import "./index.css";
|
|
1273
|
+
|
|
1274
|
+
createRoot(document.getElementById("root")!).render(
|
|
1275
|
+
<StrictMode>
|
|
1276
|
+
<BrowserRouter>
|
|
1277
|
+
<App />
|
|
1278
|
+
</BrowserRouter>
|
|
1279
|
+
</StrictMode>
|
|
1280
|
+
);
|
|
1281
|
+
`;
|
|
1282
|
+
await fs2.writeFile(path2.join(projectPath, "src/main.tsx"), mainContent);
|
|
1283
|
+
const appContent = generateAppComponent(config);
|
|
1284
|
+
await fs2.writeFile(path2.join(projectPath, "src/App.tsx"), appContent);
|
|
1285
|
+
const homeContent = generateHomePage(config);
|
|
1286
|
+
await fs2.writeFile(path2.join(projectPath, "src/pages/Home.tsx"), homeContent);
|
|
1287
|
+
if (config.themeModes.includes("dark")) {
|
|
1288
|
+
await generateThemeProvider(projectPath);
|
|
1289
|
+
}
|
|
1290
|
+
const tsconfigNode = {
|
|
1291
|
+
compilerOptions: {
|
|
1292
|
+
composite: true,
|
|
1293
|
+
skipLibCheck: true,
|
|
1294
|
+
module: "ESNext",
|
|
1295
|
+
moduleResolution: "bundler",
|
|
1296
|
+
allowSyntheticDefaultImports: true,
|
|
1297
|
+
strict: true
|
|
1298
|
+
},
|
|
1299
|
+
include: ["vite.config.ts"]
|
|
1300
|
+
};
|
|
1301
|
+
await fs2.writeJson(path2.join(projectPath, "tsconfig.node.json"), tsconfigNode, { spaces: 2 });
|
|
1302
|
+
const viteSvg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFBD4F"></stop><stop offset="100%" stop-color="#FF980E"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>`;
|
|
1303
|
+
await fs2.writeFile(path2.join(projectPath, "public/vite.svg"), viteSvg);
|
|
1304
|
+
}
|
|
1305
|
+
function generateAppComponent(config) {
|
|
1306
|
+
const hasDarkMode = config.themeModes.includes("dark");
|
|
1307
|
+
if (hasDarkMode) {
|
|
1308
|
+
return `import { Routes, Route } from "react-router-dom";
|
|
1309
|
+
import { ThemeProvider } from "./contexts/ThemeProvider";
|
|
1310
|
+
import { Toaster } from "sonner";
|
|
1311
|
+
import Home from "./pages/Home";
|
|
1312
|
+
|
|
1313
|
+
function App() {
|
|
1314
|
+
return (
|
|
1315
|
+
<ThemeProvider defaultTheme="system" storageKey="waypoint-theme">
|
|
1316
|
+
<Routes>
|
|
1317
|
+
<Route path="/" element={<Home />} />
|
|
1318
|
+
</Routes>
|
|
1319
|
+
<Toaster />
|
|
1320
|
+
</ThemeProvider>
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
export default App;
|
|
1325
|
+
`;
|
|
1326
|
+
}
|
|
1327
|
+
return `import { Routes, Route } from "react-router-dom";
|
|
1328
|
+
import { Toaster } from "sonner";
|
|
1329
|
+
import Home from "./pages/Home";
|
|
1330
|
+
|
|
1331
|
+
function App() {
|
|
1332
|
+
return (
|
|
1333
|
+
<>
|
|
1334
|
+
<Routes>
|
|
1335
|
+
<Route path="/" element={<Home />} />
|
|
1336
|
+
</Routes>
|
|
1337
|
+
<Toaster />
|
|
1338
|
+
</>
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
export default App;
|
|
1343
|
+
`;
|
|
1344
|
+
}
|
|
1345
|
+
function generateHomePage(config) {
|
|
1346
|
+
return `import { Button } from "@/components/ui/button";
|
|
1347
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
1348
|
+
|
|
1349
|
+
export default function Home() {
|
|
1350
|
+
return (
|
|
1351
|
+
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
|
1352
|
+
<Card className="w-full max-w-md">
|
|
1353
|
+
<CardHeader className="text-center">
|
|
1354
|
+
<CardTitle className="text-3xl font-bold">
|
|
1355
|
+
Welcome to ${config.projectName}
|
|
1356
|
+
</CardTitle>
|
|
1357
|
+
<CardDescription>
|
|
1358
|
+
Built with Waypoint 2.0 Design System
|
|
1359
|
+
</CardDescription>
|
|
1360
|
+
</CardHeader>
|
|
1361
|
+
<CardContent className="space-y-4">
|
|
1362
|
+
<p className="text-muted-foreground text-center">
|
|
1363
|
+
Your project is ready! Start building something amazing.
|
|
1364
|
+
</p>
|
|
1365
|
+
<div className="flex gap-2 justify-center">
|
|
1366
|
+
<Button>Get Started</Button>
|
|
1367
|
+
<Button variant="outline">Documentation</Button>
|
|
1368
|
+
</div>
|
|
1369
|
+
</CardContent>
|
|
1370
|
+
</Card>
|
|
1371
|
+
</div>
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
`;
|
|
1375
|
+
}
|
|
1376
|
+
async function generateThemeProvider(projectPath) {
|
|
1377
|
+
const themeProviderContent = `import { createContext, useContext, useEffect, useState } from "react";
|
|
1378
|
+
|
|
1379
|
+
type Theme = "dark" | "light" | "system";
|
|
1380
|
+
|
|
1381
|
+
type ThemeProviderProps = {
|
|
1382
|
+
children: React.ReactNode;
|
|
1383
|
+
defaultTheme?: Theme;
|
|
1384
|
+
storageKey?: string;
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
type ThemeProviderState = {
|
|
1388
|
+
theme: Theme;
|
|
1389
|
+
setTheme: (theme: Theme) => void;
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
const initialState: ThemeProviderState = {
|
|
1393
|
+
theme: "system",
|
|
1394
|
+
setTheme: () => null,
|
|
1395
|
+
};
|
|
1396
|
+
|
|
1397
|
+
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
|
1398
|
+
|
|
1399
|
+
export function ThemeProvider({
|
|
1400
|
+
children,
|
|
1401
|
+
defaultTheme = "system",
|
|
1402
|
+
storageKey = "vite-ui-theme",
|
|
1403
|
+
...props
|
|
1404
|
+
}: ThemeProviderProps) {
|
|
1405
|
+
const [theme, setTheme] = useState<Theme>(
|
|
1406
|
+
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
|
1407
|
+
);
|
|
1408
|
+
|
|
1409
|
+
useEffect(() => {
|
|
1410
|
+
const root = window.document.documentElement;
|
|
1411
|
+
|
|
1412
|
+
root.classList.remove("light", "dark");
|
|
1413
|
+
|
|
1414
|
+
if (theme === "system") {
|
|
1415
|
+
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
|
1416
|
+
.matches
|
|
1417
|
+
? "dark"
|
|
1418
|
+
: "light";
|
|
1419
|
+
|
|
1420
|
+
root.classList.add(systemTheme);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
root.classList.add(theme);
|
|
1425
|
+
}, [theme]);
|
|
1426
|
+
|
|
1427
|
+
const value = {
|
|
1428
|
+
theme,
|
|
1429
|
+
setTheme: (theme: Theme) => {
|
|
1430
|
+
localStorage.setItem(storageKey, theme);
|
|
1431
|
+
setTheme(theme);
|
|
1432
|
+
},
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1435
|
+
return (
|
|
1436
|
+
<ThemeProviderContext.Provider {...props} value={value}>
|
|
1437
|
+
{children}
|
|
1438
|
+
</ThemeProviderContext.Provider>
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
export const useTheme = () => {
|
|
1443
|
+
const context = useContext(ThemeProviderContext);
|
|
1444
|
+
|
|
1445
|
+
if (context === undefined)
|
|
1446
|
+
throw new Error("useTheme must be used within a ThemeProvider");
|
|
1447
|
+
|
|
1448
|
+
return context;
|
|
1449
|
+
};
|
|
1450
|
+
`;
|
|
1451
|
+
await fs2.writeFile(path2.join(projectPath, "src/contexts/ThemeProvider.tsx"), themeProviderContent);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// src/generators/readme.ts
|
|
1455
|
+
function generateReadme(config) {
|
|
1456
|
+
const themeModesList = config.themeModes.map((m) => {
|
|
1457
|
+
switch (m) {
|
|
1458
|
+
case "light":
|
|
1459
|
+
return "Light Mode";
|
|
1460
|
+
case "dark":
|
|
1461
|
+
return "Dark Mode";
|
|
1462
|
+
case "high-contrast-light":
|
|
1463
|
+
return "High Contrast Light";
|
|
1464
|
+
case "high-contrast-dark":
|
|
1465
|
+
return "High Contrast Dark";
|
|
1466
|
+
default:
|
|
1467
|
+
return m;
|
|
1468
|
+
}
|
|
1469
|
+
}).join(", ");
|
|
1470
|
+
const featuresList = config.features.map((f) => {
|
|
1471
|
+
switch (f) {
|
|
1472
|
+
case "core":
|
|
1473
|
+
return "Core UI Components";
|
|
1474
|
+
case "forms":
|
|
1475
|
+
return "Form Components";
|
|
1476
|
+
case "feedback":
|
|
1477
|
+
return "Feedback Components";
|
|
1478
|
+
case "navigation":
|
|
1479
|
+
return "Navigation Components";
|
|
1480
|
+
case "data":
|
|
1481
|
+
return "Data Display Components";
|
|
1482
|
+
case "advanced":
|
|
1483
|
+
return "Advanced Components";
|
|
1484
|
+
case "charts":
|
|
1485
|
+
return "Chart Components";
|
|
1486
|
+
default:
|
|
1487
|
+
return f;
|
|
1488
|
+
}
|
|
1489
|
+
}).join(", ");
|
|
1490
|
+
return `# ${config.projectName}
|
|
1491
|
+
|
|
1492
|
+
${config.description}
|
|
1493
|
+
|
|
1494
|
+
Built with [Waypoint 2.0 Design System](https://waypoint-2-dot-0.lovable.app).
|
|
1495
|
+
|
|
1496
|
+
## Features
|
|
1497
|
+
|
|
1498
|
+
- **Theme Modes:** ${themeModesList}
|
|
1499
|
+
- **Theme Preset:** ${config.themePreset.charAt(0).toUpperCase() + config.themePreset.slice(1)}
|
|
1500
|
+
- **Components:** ${featuresList}
|
|
1501
|
+
${config.enableRTL ? "- **RTL Support:** Enabled" : ""}
|
|
1502
|
+
${config.enableFontScaling ? "- **Font Scaling:** Enabled for accessibility" : ""}
|
|
1503
|
+
${config.includeAuth ? "- **Authentication:** Scaffolding included" : ""}
|
|
1504
|
+
${config.includePageTemplates ? "- **Page Templates:** 27+ responsive layouts" : ""}
|
|
1505
|
+
${config.includeComposer ? "- **Composer:** Visual page builder tool" : ""}
|
|
1506
|
+
|
|
1507
|
+
## Getting Started
|
|
1508
|
+
|
|
1509
|
+
\`\`\`bash
|
|
1510
|
+
# Install dependencies
|
|
1511
|
+
npm install
|
|
1512
|
+
|
|
1513
|
+
# Start development server
|
|
1514
|
+
npm run dev
|
|
1515
|
+
|
|
1516
|
+
# Build for production
|
|
1517
|
+
npm run build
|
|
1518
|
+
\`\`\`
|
|
1519
|
+
|
|
1520
|
+
## Project Structure
|
|
1521
|
+
|
|
1522
|
+
\`\`\`
|
|
1523
|
+
${config.projectName}/
|
|
1524
|
+
\u251C\u2500\u2500 public/
|
|
1525
|
+
\u2502 \u2514\u2500\u2500 vite.svg
|
|
1526
|
+
\u251C\u2500\u2500 src/
|
|
1527
|
+
\u2502 \u251C\u2500\u2500 components/
|
|
1528
|
+
\u2502 \u2502 \u2514\u2500\u2500 ui/ # Waypoint UI components
|
|
1529
|
+
\u2502 \u251C\u2500\u2500 contexts/ # React contexts (theme, etc.)
|
|
1530
|
+
\u2502 \u251C\u2500\u2500 hooks/ # Custom React hooks
|
|
1531
|
+
\u2502 \u251C\u2500\u2500 lib/ # Utilities
|
|
1532
|
+
\u2502 \u251C\u2500\u2500 pages/ # Page components
|
|
1533
|
+
\u2502 \u251C\u2500\u2500 App.tsx
|
|
1534
|
+
\u2502 \u251C\u2500\u2500 main.tsx
|
|
1535
|
+
\u2502 \u2514\u2500\u2500 index.css # Tailwind + design tokens
|
|
1536
|
+
\u251C\u2500\u2500 index.html
|
|
1537
|
+
\u251C\u2500\u2500 package.json
|
|
1538
|
+
\u251C\u2500\u2500 tailwind.config.ts
|
|
1539
|
+
\u251C\u2500\u2500 vite.config.ts
|
|
1540
|
+
\u2514\u2500\u2500 tsconfig.json
|
|
1541
|
+
\`\`\`
|
|
1542
|
+
|
|
1543
|
+
## Customization
|
|
1544
|
+
|
|
1545
|
+
### Theme Colors
|
|
1546
|
+
|
|
1547
|
+
Edit the CSS variables in \`src/index.css\` to customize your color palette:
|
|
1548
|
+
|
|
1549
|
+
\`\`\`css
|
|
1550
|
+
:root {
|
|
1551
|
+
--primary: 222.2 47.4% 11.2%;
|
|
1552
|
+
--secondary: 210 40% 96.1%;
|
|
1553
|
+
/* ... other tokens */
|
|
1554
|
+
}
|
|
1555
|
+
\`\`\`
|
|
1556
|
+
|
|
1557
|
+
### Adding Components
|
|
1558
|
+
|
|
1559
|
+
Use the shadcn/ui CLI to add more components:
|
|
1560
|
+
|
|
1561
|
+
\`\`\`bash
|
|
1562
|
+
npx shadcn-ui@latest add [component-name]
|
|
1563
|
+
\`\`\`
|
|
1564
|
+
|
|
1565
|
+
## Documentation
|
|
1566
|
+
|
|
1567
|
+
- [Waypoint 2.0 Design System](https://waypoint-2-dot-0.lovable.app)
|
|
1568
|
+
- [Component Library](https://waypoint-2-dot-0.lovable.app/components)
|
|
1569
|
+
- [Tailwind CSS](https://tailwindcss.com/docs)
|
|
1570
|
+
- [Radix UI](https://www.radix-ui.com/docs)
|
|
1571
|
+
|
|
1572
|
+
## License
|
|
1573
|
+
|
|
1574
|
+
MIT
|
|
1575
|
+
`;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/scaffolder.ts
|
|
1579
|
+
async function scaffoldProject(config) {
|
|
1580
|
+
const projectPath = path3.resolve(process.cwd(), config.projectName);
|
|
1581
|
+
if (await fs3.pathExists(projectPath)) {
|
|
1582
|
+
throw new Error(`Directory "${config.projectName}" already exists`);
|
|
1583
|
+
}
|
|
1584
|
+
const spinner = ora("Creating project structure...").start();
|
|
1585
|
+
try {
|
|
1586
|
+
await fs3.ensureDir(projectPath);
|
|
1587
|
+
await createDirectoryStructure(projectPath);
|
|
1588
|
+
spinner.text = "Generating configuration files...";
|
|
1589
|
+
await generateConfigFiles(projectPath, config);
|
|
1590
|
+
spinner.text = "Generating styles...";
|
|
1591
|
+
await generateStyleFiles(projectPath, config);
|
|
1592
|
+
spinner.text = "Generating components...";
|
|
1593
|
+
await generateComponents(projectPath, config);
|
|
1594
|
+
spinner.text = "Generating application files...";
|
|
1595
|
+
await generateAppFiles(projectPath, config);
|
|
1596
|
+
spinner.text = "Generating documentation...";
|
|
1597
|
+
await generateDocs(projectPath, config);
|
|
1598
|
+
spinner.succeed("Project created successfully!");
|
|
1599
|
+
} catch (error) {
|
|
1600
|
+
spinner.fail("Failed to create project");
|
|
1601
|
+
await fs3.remove(projectPath);
|
|
1602
|
+
throw error;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
async function createDirectoryStructure(projectPath) {
|
|
1606
|
+
const directories = [
|
|
1607
|
+
"src",
|
|
1608
|
+
"src/components",
|
|
1609
|
+
"src/components/ui",
|
|
1610
|
+
"src/lib",
|
|
1611
|
+
"src/hooks",
|
|
1612
|
+
"src/contexts",
|
|
1613
|
+
"src/pages",
|
|
1614
|
+
"src/assets",
|
|
1615
|
+
"public"
|
|
1616
|
+
];
|
|
1617
|
+
for (const dir of directories) {
|
|
1618
|
+
await fs3.ensureDir(path3.join(projectPath, dir));
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
async function generateConfigFiles(projectPath, config) {
|
|
1622
|
+
const packageJson = generatePackageJson(config);
|
|
1623
|
+
await fs3.writeJson(path3.join(projectPath, "package.json"), packageJson, { spaces: 2 });
|
|
1624
|
+
const tailwindConfig = generateTailwindConfig(config);
|
|
1625
|
+
await fs3.writeFile(path3.join(projectPath, "tailwind.config.ts"), tailwindConfig);
|
|
1626
|
+
const viteConfig = generateViteConfig();
|
|
1627
|
+
await fs3.writeFile(path3.join(projectPath, "vite.config.ts"), viteConfig);
|
|
1628
|
+
const tsConfig = generateTsConfig();
|
|
1629
|
+
await fs3.writeJson(path3.join(projectPath, "tsconfig.json"), tsConfig, { spaces: 2 });
|
|
1630
|
+
const componentsJson = generateComponentsJson();
|
|
1631
|
+
await fs3.writeJson(path3.join(projectPath, "components.json"), componentsJson, { spaces: 2 });
|
|
1632
|
+
const postcssConfig = `export default {
|
|
1633
|
+
plugins: {
|
|
1634
|
+
tailwindcss: {},
|
|
1635
|
+
autoprefixer: {},
|
|
1636
|
+
},
|
|
1637
|
+
};
|
|
1638
|
+
`;
|
|
1639
|
+
await fs3.writeFile(path3.join(projectPath, "postcss.config.js"), postcssConfig);
|
|
1640
|
+
const gitignore = `# Dependencies
|
|
1641
|
+
node_modules
|
|
1642
|
+
.pnp
|
|
1643
|
+
.pnp.js
|
|
1644
|
+
|
|
1645
|
+
# Build
|
|
1646
|
+
dist
|
|
1647
|
+
dist-ssr
|
|
1648
|
+
*.local
|
|
1649
|
+
|
|
1650
|
+
# IDE
|
|
1651
|
+
.vscode/*
|
|
1652
|
+
!.vscode/extensions.json
|
|
1653
|
+
.idea
|
|
1654
|
+
*.suo
|
|
1655
|
+
*.ntvs*
|
|
1656
|
+
*.njsproj
|
|
1657
|
+
*.sln
|
|
1658
|
+
*.sw?
|
|
1659
|
+
|
|
1660
|
+
# Environment
|
|
1661
|
+
.env
|
|
1662
|
+
.env.local
|
|
1663
|
+
.env.*.local
|
|
1664
|
+
|
|
1665
|
+
# Logs
|
|
1666
|
+
npm-debug.log*
|
|
1667
|
+
yarn-debug.log*
|
|
1668
|
+
yarn-error.log*
|
|
1669
|
+
pnpm-debug.log*
|
|
1670
|
+
|
|
1671
|
+
# OS
|
|
1672
|
+
.DS_Store
|
|
1673
|
+
Thumbs.db
|
|
1674
|
+
`;
|
|
1675
|
+
await fs3.writeFile(path3.join(projectPath, ".gitignore"), gitignore);
|
|
1676
|
+
const indexHtml = `<!DOCTYPE html>
|
|
1677
|
+
<html lang="en">
|
|
1678
|
+
<head>
|
|
1679
|
+
<meta charset="UTF-8" />
|
|
1680
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
1681
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1682
|
+
<title>${config.projectName}</title>
|
|
1683
|
+
<meta name="description" content="${config.description}" />
|
|
1684
|
+
</head>
|
|
1685
|
+
<body>
|
|
1686
|
+
<div id="root"></div>
|
|
1687
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
1688
|
+
</body>
|
|
1689
|
+
</html>
|
|
1690
|
+
`;
|
|
1691
|
+
await fs3.writeFile(path3.join(projectPath, "index.html"), indexHtml);
|
|
1692
|
+
}
|
|
1693
|
+
async function generateStyleFiles(projectPath, config) {
|
|
1694
|
+
const indexCSS = generateIndexCSS(config);
|
|
1695
|
+
await fs3.writeFile(path3.join(projectPath, "src/index.css"), indexCSS);
|
|
1696
|
+
}
|
|
1697
|
+
async function generateDocs(projectPath, config) {
|
|
1698
|
+
const readme = generateReadme(config);
|
|
1699
|
+
await fs3.writeFile(path3.join(projectPath, "README.md"), readme);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// src/utils/post-setup.ts
|
|
1703
|
+
import { exec } from "child_process";
|
|
1704
|
+
import { promisify } from "util";
|
|
1705
|
+
import ora2 from "ora";
|
|
1706
|
+
import chalk2 from "chalk";
|
|
1707
|
+
var execAsync = promisify(exec);
|
|
1708
|
+
async function installDependencies(projectName) {
|
|
1709
|
+
const spinner = ora2("Installing dependencies...").start();
|
|
1710
|
+
try {
|
|
1711
|
+
await execAsync("npm install", { cwd: projectName });
|
|
1712
|
+
spinner.succeed("Dependencies installed");
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
spinner.fail("Failed to install dependencies");
|
|
1715
|
+
console.log(chalk2.yellow(" Run `npm install` manually to install dependencies"));
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
async function initializeGit(projectName) {
|
|
1719
|
+
const spinner = ora2("Initializing git repository...").start();
|
|
1720
|
+
try {
|
|
1721
|
+
await execAsync("git init", { cwd: projectName });
|
|
1722
|
+
await execAsync("git add .", { cwd: projectName });
|
|
1723
|
+
await execAsync('git commit -m "Initial commit from create-waypoint-app"', { cwd: projectName });
|
|
1724
|
+
spinner.succeed("Git repository initialized");
|
|
1725
|
+
} catch (error) {
|
|
1726
|
+
spinner.fail("Failed to initialize git");
|
|
1727
|
+
console.log(chalk2.yellow(" Run `git init` manually to initialize the repository"));
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/utils/messages.ts
|
|
1732
|
+
import chalk3 from "chalk";
|
|
1733
|
+
function printWelcome() {
|
|
1734
|
+
console.log();
|
|
1735
|
+
console.log(chalk3.cyan.bold(" \u2566 \u2566\u250C\u2500\u2510\u252C \u252C\u250C\u2500\u2510\u250C\u2500\u2510\u252C\u250C\u2510\u250C\u250C\u252C\u2510 \u250C\u2500\u2510 \u250C\u2500\u2510"));
|
|
1736
|
+
console.log(chalk3.cyan.bold(" \u2551\u2551\u2551\u251C\u2500\u2524\u2514\u252C\u2518\u251C\u2500\u2518\u2502 \u2502\u2502\u2502\u2502\u2502 \u2502 \u250C\u2500\u2518 \u2502 \u2502"));
|
|
1737
|
+
console.log(chalk3.cyan.bold(" \u255A\u2569\u255D\u2534 \u2534 \u2534 \u2534 \u2514\u2500\u2518\u2534\u2518\u2514\u2518 \u2534 \u2514\u2500\u2518o\u2514\u2500\u2518"));
|
|
1738
|
+
console.log();
|
|
1739
|
+
console.log(chalk3.white(" Create beautiful, accessible React applications"));
|
|
1740
|
+
console.log(chalk3.dim(" with the Waypoint 2.0 Design System"));
|
|
1741
|
+
console.log();
|
|
1742
|
+
}
|
|
1743
|
+
function printSuccess(config) {
|
|
1744
|
+
console.log();
|
|
1745
|
+
console.log(chalk3.green.bold(" \u2713 Success!") + ` Created ${chalk3.cyan(config.projectName)}`);
|
|
1746
|
+
console.log();
|
|
1747
|
+
console.log(" Next steps:");
|
|
1748
|
+
console.log();
|
|
1749
|
+
console.log(chalk3.cyan(` cd ${config.projectName}`));
|
|
1750
|
+
console.log(chalk3.cyan(" npm run dev"));
|
|
1751
|
+
console.log();
|
|
1752
|
+
console.log(" Documentation:");
|
|
1753
|
+
console.log(chalk3.dim(" https://waypoint-2-dot-0.lovable.app"));
|
|
1754
|
+
console.log();
|
|
1755
|
+
console.log(chalk3.dim(` Theme modes: ${config.themeModes.join(", ")}`));
|
|
1756
|
+
console.log(chalk3.dim(` Components: ${config.features.length} groups included`));
|
|
1757
|
+
if (config.enableRTL) console.log(chalk3.dim(" RTL support: enabled"));
|
|
1758
|
+
console.log();
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// src/index.ts
|
|
1762
|
+
async function createProject() {
|
|
1763
|
+
program.name("create-waypoint-app").description("Scaffold a new Waypoint 2.0 design system project").argument("[project-name]", "Name of the project").option("-t, --template <template>", "Use a template: minimal, full, dashboard, marketing, ecommerce").option("--no-install", "Skip dependency installation").option("--no-git", "Skip git initialization").option("-y, --yes", "Use default options (skip prompts)").parse();
|
|
1764
|
+
const options = program.opts();
|
|
1765
|
+
const args = program.args;
|
|
1766
|
+
printWelcome();
|
|
1767
|
+
let config;
|
|
1768
|
+
if (options.yes) {
|
|
1769
|
+
config = getDefaultConfig(args[0] || "waypoint-app");
|
|
1770
|
+
} else {
|
|
1771
|
+
config = await gatherProjectConfig(args[0]);
|
|
1772
|
+
}
|
|
1773
|
+
console.log();
|
|
1774
|
+
console.log(chalk4.cyan(" Creating your Waypoint 2.0 project..."));
|
|
1775
|
+
console.log();
|
|
1776
|
+
try {
|
|
1777
|
+
await scaffoldProject(config);
|
|
1778
|
+
if (options.install !== false) {
|
|
1779
|
+
await installDependencies(config.projectName);
|
|
1780
|
+
}
|
|
1781
|
+
if (options.git !== false) {
|
|
1782
|
+
await initializeGit(config.projectName);
|
|
1783
|
+
}
|
|
1784
|
+
printSuccess(config);
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
console.error(chalk4.red(" Error creating project:"), error);
|
|
1787
|
+
process.exit(1);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function getDefaultConfig(projectName) {
|
|
1791
|
+
return {
|
|
1792
|
+
projectName,
|
|
1793
|
+
description: "A Waypoint 2.0 design system project",
|
|
1794
|
+
themeModes: ["light", "dark"],
|
|
1795
|
+
themePreset: "default",
|
|
1796
|
+
enableRTL: false,
|
|
1797
|
+
enableFontScaling: true,
|
|
1798
|
+
features: ["core", "forms", "feedback", "navigation"],
|
|
1799
|
+
includeAuth: false,
|
|
1800
|
+
includePageTemplates: false,
|
|
1801
|
+
includeComposer: false
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
export {
|
|
1805
|
+
createProject
|
|
1806
|
+
};
|