@a13xu/lucid 1.9.5 → 1.11.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/README.md +15 -1
- package/build/database.d.ts +32 -0
- package/build/database.js +38 -0
- package/build/index.js +282 -1
- package/build/instance.d.ts +9 -0
- package/build/instance.js +78 -0
- package/build/tools/e2e.d.ts +13 -0
- package/build/tools/e2e.js +109 -0
- package/build/tools/plan.d.ts +75 -0
- package/build/tools/plan.js +148 -0
- package/build/tools/webdev/accessibility-audit.d.ts +23 -0
- package/build/tools/webdev/accessibility-audit.js +214 -0
- package/build/tools/webdev/api-client.d.ts +24 -0
- package/build/tools/webdev/api-client.js +167 -0
- package/build/tools/webdev/design-tokens.d.ts +18 -0
- package/build/tools/webdev/design-tokens.js +375 -0
- package/build/tools/webdev/generate-component.d.ts +18 -0
- package/build/tools/webdev/generate-component.js +123 -0
- package/build/tools/webdev/index.d.ts +10 -0
- package/build/tools/webdev/index.js +10 -0
- package/build/tools/webdev/perf-hints.d.ts +24 -0
- package/build/tools/webdev/perf-hints.js +247 -0
- package/build/tools/webdev/responsive-layout.d.ts +18 -0
- package/build/tools/webdev/responsive-layout.js +229 -0
- package/build/tools/webdev/scaffold-page.d.ts +18 -0
- package/build/tools/webdev/scaffold-page.js +137 -0
- package/build/tools/webdev/security-scan.d.ts +23 -0
- package/build/tools/webdev/security-scan.js +247 -0
- package/build/tools/webdev/seo-meta.d.ts +24 -0
- package/build/tools/webdev/seo-meta.js +114 -0
- package/build/tools/webdev/test-generator.d.ts +18 -0
- package/build/tools/webdev/test-generator.js +269 -0
- package/package.json +1 -1
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Schema
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
export const DesignTokensSchema = z.object({
|
|
6
|
+
brand_name: z.string().describe("Brand/project name"),
|
|
7
|
+
primary_color: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe("Primary brand color as hex (e.g. #3B82F6) or color name (e.g. blue)"),
|
|
10
|
+
mood: z
|
|
11
|
+
.enum(["minimal", "bold", "playful", "corporate"])
|
|
12
|
+
.describe("Design mood"),
|
|
13
|
+
output_format: z
|
|
14
|
+
.enum(["css-variables", "tailwind-config", "json"])
|
|
15
|
+
.describe("Output format for the token set"),
|
|
16
|
+
});
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Color utilities
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/** Named colors to hex fallback map */
|
|
21
|
+
const NAMED_COLORS = {
|
|
22
|
+
blue: "#3B82F6",
|
|
23
|
+
indigo: "#6366F1",
|
|
24
|
+
purple: "#8B5CF6",
|
|
25
|
+
pink: "#EC4899",
|
|
26
|
+
red: "#EF4444",
|
|
27
|
+
orange: "#F97316",
|
|
28
|
+
yellow: "#EAB308",
|
|
29
|
+
green: "#22C55E",
|
|
30
|
+
teal: "#14B8A6",
|
|
31
|
+
cyan: "#06B6D4",
|
|
32
|
+
slate: "#64748B",
|
|
33
|
+
gray: "#6B7280",
|
|
34
|
+
zinc: "#71717A",
|
|
35
|
+
neutral: "#737373",
|
|
36
|
+
stone: "#78716C",
|
|
37
|
+
};
|
|
38
|
+
function resolveHex(color) {
|
|
39
|
+
const trimmed = color.trim().toLowerCase();
|
|
40
|
+
if (trimmed.startsWith("#"))
|
|
41
|
+
return trimmed;
|
|
42
|
+
return NAMED_COLORS[trimmed] ?? "#3B82F6";
|
|
43
|
+
}
|
|
44
|
+
function hexToRgb(hex) {
|
|
45
|
+
const clean = hex.replace("#", "");
|
|
46
|
+
const full = clean.length === 3
|
|
47
|
+
? clean.split("").map((c) => c + c).join("")
|
|
48
|
+
: clean;
|
|
49
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
50
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
51
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
52
|
+
return [r ?? 0, g ?? 0, b ?? 0];
|
|
53
|
+
}
|
|
54
|
+
function rgbToHsl(r, g, b) {
|
|
55
|
+
const rr = r / 255, gg = g / 255, bb = b / 255;
|
|
56
|
+
const max = Math.max(rr, gg, bb);
|
|
57
|
+
const min = Math.min(rr, gg, bb);
|
|
58
|
+
const l = (max + min) / 2;
|
|
59
|
+
if (max === min)
|
|
60
|
+
return [0, 0, Math.round(l * 100)];
|
|
61
|
+
const d = max - min;
|
|
62
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
63
|
+
let h = 0;
|
|
64
|
+
if (max === rr)
|
|
65
|
+
h = ((gg - bb) / d + (gg < bb ? 6 : 0)) / 6;
|
|
66
|
+
else if (max === gg)
|
|
67
|
+
h = ((bb - rr) / d + 2) / 6;
|
|
68
|
+
else
|
|
69
|
+
h = ((rr - gg) / d + 4) / 6;
|
|
70
|
+
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
|
|
71
|
+
}
|
|
72
|
+
function hslToHex(h, s, l) {
|
|
73
|
+
const ll = l / 100, ss = s / 100;
|
|
74
|
+
const c = (1 - Math.abs(2 * ll - 1)) * ss;
|
|
75
|
+
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
|
|
76
|
+
const m = ll - c / 2;
|
|
77
|
+
let r = 0, g = 0, b = 0;
|
|
78
|
+
if (h < 60) {
|
|
79
|
+
r = c;
|
|
80
|
+
g = x;
|
|
81
|
+
}
|
|
82
|
+
else if (h < 120) {
|
|
83
|
+
r = x;
|
|
84
|
+
g = c;
|
|
85
|
+
}
|
|
86
|
+
else if (h < 180) {
|
|
87
|
+
g = c;
|
|
88
|
+
b = x;
|
|
89
|
+
}
|
|
90
|
+
else if (h < 240) {
|
|
91
|
+
g = x;
|
|
92
|
+
b = c;
|
|
93
|
+
}
|
|
94
|
+
else if (h < 300) {
|
|
95
|
+
r = x;
|
|
96
|
+
b = c;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
r = c;
|
|
100
|
+
b = x;
|
|
101
|
+
}
|
|
102
|
+
const toHex = (v) => Math.round((v + m) * 255).toString(16).padStart(2, "0");
|
|
103
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
104
|
+
}
|
|
105
|
+
/** Generate a 10-step color scale (50–950) from a base hex color */
|
|
106
|
+
function generateColorScale(baseHex) {
|
|
107
|
+
const [r, g, b] = hexToRgb(baseHex);
|
|
108
|
+
const [h, s] = rgbToHsl(r, g, b);
|
|
109
|
+
const steps = [
|
|
110
|
+
["50", s > 10 ? s - 10 : s, 97],
|
|
111
|
+
["100", s > 10 ? s - 5 : s, 94],
|
|
112
|
+
["200", s, 86],
|
|
113
|
+
["300", s, 74],
|
|
114
|
+
["400", s, 62],
|
|
115
|
+
["500", s, 50],
|
|
116
|
+
["600", s, 41],
|
|
117
|
+
["700", s, 34],
|
|
118
|
+
["800", s, 27],
|
|
119
|
+
["900", s, 20],
|
|
120
|
+
["950", s, 14],
|
|
121
|
+
];
|
|
122
|
+
const scale = {};
|
|
123
|
+
for (const [name, sat, light] of steps) {
|
|
124
|
+
scale[name] = hslToHex(h, sat, light);
|
|
125
|
+
}
|
|
126
|
+
return scale;
|
|
127
|
+
}
|
|
128
|
+
/** Pick a neutral (desaturated) scale based on primary hue */
|
|
129
|
+
function generateNeutralScale(primaryHex) {
|
|
130
|
+
const [r, g, b] = hexToRgb(primaryHex);
|
|
131
|
+
const [h] = rgbToHsl(r, g, b);
|
|
132
|
+
const neutralSteps = [
|
|
133
|
+
["50", 8, 98], ["100", 8, 95], ["200", 8, 90], ["300", 8, 82], ["400", 7, 65],
|
|
134
|
+
["500", 6, 48], ["600", 5, 38], ["700", 5, 28], ["800", 4, 20], ["900", 4, 12], ["950", 3, 8],
|
|
135
|
+
];
|
|
136
|
+
const scale = {};
|
|
137
|
+
for (const [name, sat, light] of neutralSteps) {
|
|
138
|
+
scale[name] = hslToHex(h, sat, light);
|
|
139
|
+
}
|
|
140
|
+
return scale;
|
|
141
|
+
}
|
|
142
|
+
const MOOD_CONFIG = {
|
|
143
|
+
minimal: {
|
|
144
|
+
fontSans: "'Inter', 'system-ui', sans-serif",
|
|
145
|
+
fontMono: "'JetBrains Mono', 'Fira Code', monospace",
|
|
146
|
+
radiusSm: "2px", radiusMd: "4px", radiusLg: "6px", radiusFull: "9999px",
|
|
147
|
+
shadowSm: "0 1px 2px rgba(0,0,0,0.05)",
|
|
148
|
+
shadowMd: "0 2px 8px rgba(0,0,0,0.08)",
|
|
149
|
+
shadowLg: "0 4px 16px rgba(0,0,0,0.10)",
|
|
150
|
+
spacingBase: 4,
|
|
151
|
+
},
|
|
152
|
+
bold: {
|
|
153
|
+
fontSans: "'Poppins', 'Montserrat', sans-serif",
|
|
154
|
+
fontMono: "'Roboto Mono', monospace",
|
|
155
|
+
radiusSm: "4px", radiusMd: "8px", radiusLg: "12px", radiusFull: "9999px",
|
|
156
|
+
shadowSm: "0 2px 4px rgba(0,0,0,0.15)",
|
|
157
|
+
shadowMd: "0 4px 12px rgba(0,0,0,0.20)",
|
|
158
|
+
shadowLg: "0 8px 24px rgba(0,0,0,0.25)",
|
|
159
|
+
spacingBase: 4,
|
|
160
|
+
},
|
|
161
|
+
playful: {
|
|
162
|
+
fontSans: "'Nunito', 'Quicksand', sans-serif",
|
|
163
|
+
fontMono: "'Space Mono', monospace",
|
|
164
|
+
radiusSm: "8px", radiusMd: "16px", radiusLg: "24px", radiusFull: "9999px",
|
|
165
|
+
shadowSm: "2px 2px 0 rgba(0,0,0,0.10)",
|
|
166
|
+
shadowMd: "4px 4px 0 rgba(0,0,0,0.12)",
|
|
167
|
+
shadowLg: "6px 6px 0 rgba(0,0,0,0.15)",
|
|
168
|
+
spacingBase: 4,
|
|
169
|
+
},
|
|
170
|
+
corporate: {
|
|
171
|
+
fontSans: "'IBM Plex Sans', 'Arial', sans-serif",
|
|
172
|
+
fontMono: "'IBM Plex Mono', monospace",
|
|
173
|
+
radiusSm: "2px", radiusMd: "3px", radiusLg: "4px", radiusFull: "9999px",
|
|
174
|
+
shadowSm: "0 1px 3px rgba(0,0,0,0.12)",
|
|
175
|
+
shadowMd: "0 2px 6px rgba(0,0,0,0.15)",
|
|
176
|
+
shadowLg: "0 4px 12px rgba(0,0,0,0.18)",
|
|
177
|
+
spacingBase: 4,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Formatters
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
function formatCssVariables(brand, primary, neutral, mc) {
|
|
184
|
+
const sp = (n) => `${n * mc.spacingBase}px`;
|
|
185
|
+
const primaryVars = Object.entries(primary)
|
|
186
|
+
.map(([k, v]) => ` --color-primary-${k}: ${v};`)
|
|
187
|
+
.join("\n");
|
|
188
|
+
const neutralVars = Object.entries(neutral)
|
|
189
|
+
.map(([k, v]) => ` --color-neutral-${k}: ${v};`)
|
|
190
|
+
.join("\n");
|
|
191
|
+
return `/* ${brand} Design Tokens */
|
|
192
|
+
:root {
|
|
193
|
+
/* Colors — Primary */
|
|
194
|
+
${primaryVars}
|
|
195
|
+
|
|
196
|
+
/* Colors — Neutral */
|
|
197
|
+
${neutralVars}
|
|
198
|
+
|
|
199
|
+
/* Semantic colors */
|
|
200
|
+
--color-bg: var(--color-neutral-50);
|
|
201
|
+
--color-bg-subtle: var(--color-neutral-100);
|
|
202
|
+
--color-surface: #ffffff;
|
|
203
|
+
--color-border: var(--color-neutral-200);
|
|
204
|
+
--color-text: var(--color-neutral-900);
|
|
205
|
+
--color-text-muted: var(--color-neutral-500);
|
|
206
|
+
--color-accent: var(--color-primary-500);
|
|
207
|
+
--color-accent-hover: var(--color-primary-600);
|
|
208
|
+
|
|
209
|
+
/* Typography */
|
|
210
|
+
--font-sans: ${mc.fontSans};
|
|
211
|
+
--font-mono: ${mc.fontMono};
|
|
212
|
+
--text-xs: 0.75rem;
|
|
213
|
+
--text-sm: 0.875rem;
|
|
214
|
+
--text-base: 1rem;
|
|
215
|
+
--text-lg: 1.125rem;
|
|
216
|
+
--text-xl: 1.25rem;
|
|
217
|
+
--text-2xl: 1.5rem;
|
|
218
|
+
--text-3xl: 1.875rem;
|
|
219
|
+
--text-4xl: 2.25rem;
|
|
220
|
+
--leading-tight: 1.25;
|
|
221
|
+
--leading-normal: 1.5;
|
|
222
|
+
--leading-loose: 1.75;
|
|
223
|
+
|
|
224
|
+
/* Spacing */
|
|
225
|
+
--space-1: ${sp(1)};
|
|
226
|
+
--space-2: ${sp(2)};
|
|
227
|
+
--space-3: ${sp(3)};
|
|
228
|
+
--space-4: ${sp(4)};
|
|
229
|
+
--space-6: ${sp(6)};
|
|
230
|
+
--space-8: ${sp(8)};
|
|
231
|
+
--space-12: ${sp(12)};
|
|
232
|
+
--space-16: ${sp(16)};
|
|
233
|
+
--space-24: ${sp(24)};
|
|
234
|
+
|
|
235
|
+
/* Border Radius */
|
|
236
|
+
--radius-sm: ${mc.radiusSm};
|
|
237
|
+
--radius-md: ${mc.radiusMd};
|
|
238
|
+
--radius-lg: ${mc.radiusLg};
|
|
239
|
+
--radius-full: ${mc.radiusFull};
|
|
240
|
+
|
|
241
|
+
/* Shadows */
|
|
242
|
+
--shadow-sm: ${mc.shadowSm};
|
|
243
|
+
--shadow-md: ${mc.shadowMd};
|
|
244
|
+
--shadow-lg: ${mc.shadowLg};
|
|
245
|
+
|
|
246
|
+
/* Transitions */
|
|
247
|
+
--duration-fast: 150ms;
|
|
248
|
+
--duration-normal: 250ms;
|
|
249
|
+
--duration-slow: 400ms;
|
|
250
|
+
--ease-default: cubic-bezier(0.4, 0, 0.2, 1);
|
|
251
|
+
}`;
|
|
252
|
+
}
|
|
253
|
+
function formatTailwindConfig(brand, primary, neutral, mc) {
|
|
254
|
+
const primaryEntries = Object.entries(primary)
|
|
255
|
+
.map(([k, v]) => ` ${k}: "${v}",`)
|
|
256
|
+
.join("\n");
|
|
257
|
+
const neutralEntries = Object.entries(neutral)
|
|
258
|
+
.map(([k, v]) => ` ${k}: "${v}",`)
|
|
259
|
+
.join("\n");
|
|
260
|
+
return `// tailwind.config.ts — ${brand} Design Tokens
|
|
261
|
+
import type { Config } from "tailwindcss";
|
|
262
|
+
|
|
263
|
+
const config: Config = {
|
|
264
|
+
content: ["./src/**/*.{ts,tsx,vue,html}"],
|
|
265
|
+
theme: {
|
|
266
|
+
extend: {
|
|
267
|
+
colors: {
|
|
268
|
+
primary: {
|
|
269
|
+
${primaryEntries}
|
|
270
|
+
},
|
|
271
|
+
neutral: {
|
|
272
|
+
${neutralEntries}
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
fontFamily: {
|
|
276
|
+
sans: [${mc.fontSans.split(",").map((f) => `"${f.trim().replace(/'/g, "")}"`).join(", ")}],
|
|
277
|
+
mono: [${mc.fontMono.split(",").map((f) => `"${f.trim().replace(/'/g, "")}"`).join(", ")}],
|
|
278
|
+
},
|
|
279
|
+
borderRadius: {
|
|
280
|
+
sm: "${mc.radiusSm}",
|
|
281
|
+
md: "${mc.radiusMd}",
|
|
282
|
+
lg: "${mc.radiusLg}",
|
|
283
|
+
full: "${mc.radiusFull}",
|
|
284
|
+
},
|
|
285
|
+
boxShadow: {
|
|
286
|
+
sm: "${mc.shadowSm}",
|
|
287
|
+
md: "${mc.shadowMd}",
|
|
288
|
+
lg: "${mc.shadowLg}",
|
|
289
|
+
},
|
|
290
|
+
spacing: {
|
|
291
|
+
"1": "4px", "2": "8px", "3": "12px", "4": "16px",
|
|
292
|
+
"6": "24px", "8": "32px", "12": "48px", "16": "64px",
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
plugins: [],
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export default config;`;
|
|
300
|
+
}
|
|
301
|
+
function formatJson(brand, primary, neutral, mc) {
|
|
302
|
+
const tokens = {
|
|
303
|
+
brand,
|
|
304
|
+
colors: {
|
|
305
|
+
primary: Object.fromEntries(Object.entries(primary).map(([k, v]) => [`primary.${k}`, v])),
|
|
306
|
+
neutral: Object.fromEntries(Object.entries(neutral).map(([k, v]) => [`neutral.${k}`, v])),
|
|
307
|
+
semantic: {
|
|
308
|
+
"bg": neutral["50"],
|
|
309
|
+
"bg-subtle": neutral["100"],
|
|
310
|
+
"surface": "#ffffff",
|
|
311
|
+
"border": neutral["200"],
|
|
312
|
+
"text": neutral["900"],
|
|
313
|
+
"text-muted": neutral["500"],
|
|
314
|
+
"accent": primary["500"],
|
|
315
|
+
"accent-hover": primary["600"],
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
typography: {
|
|
319
|
+
fontSans: mc.fontSans,
|
|
320
|
+
fontMono: mc.fontMono,
|
|
321
|
+
scale: { xs: "0.75rem", sm: "0.875rem", base: "1rem", lg: "1.125rem", xl: "1.25rem", "2xl": "1.5rem", "3xl": "1.875rem", "4xl": "2.25rem" },
|
|
322
|
+
},
|
|
323
|
+
spacing: { "1": "4px", "2": "8px", "3": "12px", "4": "16px", "6": "24px", "8": "32px", "12": "48px", "16": "64px" },
|
|
324
|
+
radii: { sm: mc.radiusSm, md: mc.radiusMd, lg: mc.radiusLg, full: mc.radiusFull },
|
|
325
|
+
shadows: { sm: mc.shadowSm, md: mc.shadowMd, lg: mc.shadowLg },
|
|
326
|
+
};
|
|
327
|
+
return JSON.stringify(tokens, null, 2);
|
|
328
|
+
}
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Handler
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
// Example call:
|
|
333
|
+
// handleDesignTokens({ brand_name: "Acme", primary_color: "#6366F1", mood: "minimal", output_format: "css-variables" })
|
|
334
|
+
export function handleDesignTokens(args) {
|
|
335
|
+
const { brand_name, primary_color, mood, output_format } = args;
|
|
336
|
+
const primaryHex = resolveHex(primary_color);
|
|
337
|
+
const primaryScale = generateColorScale(primaryHex);
|
|
338
|
+
const neutralScale = generateNeutralScale(primaryHex);
|
|
339
|
+
const mc = MOOD_CONFIG[mood] ?? MOOD_CONFIG["minimal"];
|
|
340
|
+
let code;
|
|
341
|
+
let lang;
|
|
342
|
+
let filename;
|
|
343
|
+
switch (output_format) {
|
|
344
|
+
case "tailwind-config":
|
|
345
|
+
code = formatTailwindConfig(brand_name, primaryScale, neutralScale, mc);
|
|
346
|
+
lang = "typescript";
|
|
347
|
+
filename = "tailwind.config.ts";
|
|
348
|
+
break;
|
|
349
|
+
case "json":
|
|
350
|
+
code = formatJson(brand_name, primaryScale, neutralScale, mc);
|
|
351
|
+
lang = "json";
|
|
352
|
+
filename = "design-tokens.json";
|
|
353
|
+
break;
|
|
354
|
+
default: // css-variables
|
|
355
|
+
code = formatCssVariables(brand_name, primaryScale, neutralScale, mc);
|
|
356
|
+
lang = "css";
|
|
357
|
+
filename = "tokens.css";
|
|
358
|
+
}
|
|
359
|
+
const lines = [
|
|
360
|
+
`✅ Design tokens: ${brand_name}`,
|
|
361
|
+
`📄 Filename: ${filename}`,
|
|
362
|
+
`🎨 Primary: ${primaryHex} | Mood: ${mood} | Format: ${output_format}`,
|
|
363
|
+
``,
|
|
364
|
+
"```" + lang,
|
|
365
|
+
code,
|
|
366
|
+
"```",
|
|
367
|
+
``,
|
|
368
|
+
`💡 Reasoning: Generated a complete design token set from primary color ${primaryHex}. ` +
|
|
369
|
+
`Color scales (50–950) derived via HSL lightness interpolation. ` +
|
|
370
|
+
`Neutral scale uses the same hue with reduced saturation. ` +
|
|
371
|
+
`Typography, spacing, radius, and shadow values tuned for "${mood}" mood. ` +
|
|
372
|
+
`Review contrast ratios with a tool like https://webaim.org/resources/contrastchecker/.`,
|
|
373
|
+
];
|
|
374
|
+
return lines.join("\n");
|
|
375
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const GenerateComponentSchema: z.ZodObject<{
|
|
3
|
+
description: z.ZodString;
|
|
4
|
+
framework: z.ZodEnum<["react", "vue", "nuxt"]>;
|
|
5
|
+
styling: z.ZodEnum<["tailwind", "css-modules", "none"]>;
|
|
6
|
+
typescript: z.ZodBoolean;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
typescript: boolean;
|
|
9
|
+
description: string;
|
|
10
|
+
framework: "vue" | "react" | "nuxt";
|
|
11
|
+
styling: "none" | "tailwind" | "css-modules";
|
|
12
|
+
}, {
|
|
13
|
+
typescript: boolean;
|
|
14
|
+
description: string;
|
|
15
|
+
framework: "vue" | "react" | "nuxt";
|
|
16
|
+
styling: "none" | "tailwind" | "css-modules";
|
|
17
|
+
}>;
|
|
18
|
+
export declare function handleGenerateComponent(args: z.infer<typeof GenerateComponentSchema>): string;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Schema
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
export const GenerateComponentSchema = z.object({
|
|
6
|
+
description: z.string().describe("Natural language description of the component"),
|
|
7
|
+
framework: z.enum(["react", "vue", "nuxt"]).describe("Target framework"),
|
|
8
|
+
styling: z.enum(["tailwind", "css-modules", "none"]).describe("Styling approach"),
|
|
9
|
+
typescript: z.boolean().describe("Whether to use TypeScript"),
|
|
10
|
+
});
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Helpers
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
function toPascalCase(str) {
|
|
15
|
+
return str
|
|
16
|
+
.replace(/[^a-zA-Z0-9\s]/g, "")
|
|
17
|
+
.split(/\s+/)
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.slice(0, 3)
|
|
20
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
|
21
|
+
.join("");
|
|
22
|
+
}
|
|
23
|
+
function buildReactComponent(name, description, styling, typescript) {
|
|
24
|
+
const ts = typescript;
|
|
25
|
+
let styleImport = "";
|
|
26
|
+
let classAttr = "";
|
|
27
|
+
if (styling === "tailwind") {
|
|
28
|
+
classAttr = `className="flex flex-col gap-4"`;
|
|
29
|
+
}
|
|
30
|
+
else if (styling === "css-modules") {
|
|
31
|
+
styleImport = `\nimport styles from "./${name}.module.css";`;
|
|
32
|
+
classAttr = `className={styles.container}`;
|
|
33
|
+
}
|
|
34
|
+
const propsBlock = ts
|
|
35
|
+
? `\ninterface ${name}Props {\n // TODO: define props\n}\n`
|
|
36
|
+
: "";
|
|
37
|
+
const fnSignature = ts
|
|
38
|
+
? `export const ${name}: React.FC<${name}Props> = (_props) => {`
|
|
39
|
+
: `export const ${name} = (_props) => {`;
|
|
40
|
+
const reactImport = ts ? `import React from "react";` : `import React from "react";`;
|
|
41
|
+
const code = `${reactImport}${styleImport}
|
|
42
|
+
${propsBlock}
|
|
43
|
+
// ${description}
|
|
44
|
+
${fnSignature}
|
|
45
|
+
return (
|
|
46
|
+
<div ${classAttr}>
|
|
47
|
+
{/* ${description} */}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default ${name};`;
|
|
53
|
+
return { code, lang: ts ? "tsx" : "jsx" };
|
|
54
|
+
}
|
|
55
|
+
function buildVueComponent(name, description, styling, typescript) {
|
|
56
|
+
const scriptLang = typescript ? ` lang="ts"` : "";
|
|
57
|
+
let classAttr = "class=\"container\"";
|
|
58
|
+
let styleTag = "";
|
|
59
|
+
if (styling === "tailwind") {
|
|
60
|
+
classAttr = "class=\"flex flex-col gap-4\"";
|
|
61
|
+
}
|
|
62
|
+
else if (styling === "css-modules") {
|
|
63
|
+
classAttr = `:class="$style.container"`;
|
|
64
|
+
styleTag = `\n<style module>\n.container {\n /* ${name} styles */\n}\n</style>`;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
styleTag = `\n<style scoped>\n.container {\n /* ${name} styles */\n}\n</style>`;
|
|
68
|
+
}
|
|
69
|
+
const code = `<script setup${scriptLang}>
|
|
70
|
+
// ${description}
|
|
71
|
+
// TODO: define props and emits
|
|
72
|
+
// const props = defineProps<{}>()
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<div ${classAttr}>
|
|
77
|
+
<!-- ${description} -->
|
|
78
|
+
</div>
|
|
79
|
+
</template>${styleTag}`;
|
|
80
|
+
return { code, lang: "vue" };
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Handler
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Example call:
|
|
86
|
+
// handleGenerateComponent({ description: "user profile card with avatar", framework: "react", styling: "tailwind", typescript: true })
|
|
87
|
+
export function handleGenerateComponent(args) {
|
|
88
|
+
const { description, framework, styling, typescript } = args;
|
|
89
|
+
const componentName = toPascalCase(description) || "MyComponent";
|
|
90
|
+
let code;
|
|
91
|
+
let lang;
|
|
92
|
+
let filename;
|
|
93
|
+
if (framework === "react") {
|
|
94
|
+
({ code, lang } = buildReactComponent(componentName, description, styling, typescript));
|
|
95
|
+
filename = `${componentName}.${typescript ? "tsx" : "jsx"}`;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// vue or nuxt
|
|
99
|
+
({ code, lang } = buildVueComponent(componentName, description, styling, typescript));
|
|
100
|
+
filename = `${componentName}.vue`;
|
|
101
|
+
}
|
|
102
|
+
const styleNote = styling === "tailwind"
|
|
103
|
+
? "Tailwind CSS utility classes pre-applied"
|
|
104
|
+
: styling === "css-modules"
|
|
105
|
+
? "CSS Modules — a .module.css file is also needed"
|
|
106
|
+
: "No styling framework — add your own styles";
|
|
107
|
+
const lines = [
|
|
108
|
+
`✅ Component: ${componentName}`,
|
|
109
|
+
`📄 Filename: ${filename}`,
|
|
110
|
+
`🔧 ${framework.toUpperCase()} | ${styling} | TypeScript: ${typescript}`,
|
|
111
|
+
``,
|
|
112
|
+
"```" + lang,
|
|
113
|
+
code,
|
|
114
|
+
"```",
|
|
115
|
+
``,
|
|
116
|
+
`💡 Reasoning: Scaffold for "${description}". ${styleNote}. ` +
|
|
117
|
+
`Fill in props, state, and template logic. ` +
|
|
118
|
+
(framework === "react"
|
|
119
|
+
? "Define props in the interface and remove unused ones."
|
|
120
|
+
: "Use defineProps<>() and defineEmits<>() as needed."),
|
|
121
|
+
];
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { GenerateComponentSchema, handleGenerateComponent } from "./generate-component.js";
|
|
2
|
+
export { ScaffoldPageSchema, handleScaffoldPage } from "./scaffold-page.js";
|
|
3
|
+
export { SeoMetaSchema, handleSeoMeta } from "./seo-meta.js";
|
|
4
|
+
export { AccessibilityAuditSchema, handleAccessibilityAudit } from "./accessibility-audit.js";
|
|
5
|
+
export { ApiClientSchema, handleApiClient } from "./api-client.js";
|
|
6
|
+
export { TestGeneratorSchema, handleTestGenerator } from "./test-generator.js";
|
|
7
|
+
export { ResponsiveLayoutSchema, handleResponsiveLayout } from "./responsive-layout.js";
|
|
8
|
+
export { SecurityScanSchema, handleSecurityScan } from "./security-scan.js";
|
|
9
|
+
export { DesignTokensSchema, handleDesignTokens } from "./design-tokens.js";
|
|
10
|
+
export { PerfHintsSchema, handlePerfHints } from "./perf-hints.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { GenerateComponentSchema, handleGenerateComponent } from "./generate-component.js";
|
|
2
|
+
export { ScaffoldPageSchema, handleScaffoldPage } from "./scaffold-page.js";
|
|
3
|
+
export { SeoMetaSchema, handleSeoMeta } from "./seo-meta.js";
|
|
4
|
+
export { AccessibilityAuditSchema, handleAccessibilityAudit } from "./accessibility-audit.js";
|
|
5
|
+
export { ApiClientSchema, handleApiClient } from "./api-client.js";
|
|
6
|
+
export { TestGeneratorSchema, handleTestGenerator } from "./test-generator.js";
|
|
7
|
+
export { ResponsiveLayoutSchema, handleResponsiveLayout } from "./responsive-layout.js";
|
|
8
|
+
export { SecurityScanSchema, handleSecurityScan } from "./security-scan.js";
|
|
9
|
+
export { DesignTokensSchema, handleDesignTokens } from "./design-tokens.js";
|
|
10
|
+
export { PerfHintsSchema, handlePerfHints } from "./perf-hints.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const PerfHintsSchema: z.ZodObject<{
|
|
3
|
+
code: z.ZodString;
|
|
4
|
+
framework: z.ZodEnum<["react", "vue", "nuxt", "vanilla"]>;
|
|
5
|
+
context: z.ZodEnum<["component", "page", "layout"]>;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
code: string;
|
|
8
|
+
context: "component" | "page" | "layout";
|
|
9
|
+
framework: "vue" | "react" | "nuxt" | "vanilla";
|
|
10
|
+
}, {
|
|
11
|
+
code: string;
|
|
12
|
+
context: "component" | "page" | "layout";
|
|
13
|
+
framework: "vue" | "react" | "nuxt" | "vanilla";
|
|
14
|
+
}>;
|
|
15
|
+
export type CwvMetric = "LCP" | "CLS" | "INP" | "FCP" | "TTFB" | "General";
|
|
16
|
+
export type PerfImpact = "high" | "medium" | "low";
|
|
17
|
+
export interface PerfIssue {
|
|
18
|
+
line: number;
|
|
19
|
+
metric: CwvMetric;
|
|
20
|
+
impact: PerfImpact;
|
|
21
|
+
message: string;
|
|
22
|
+
fix: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function handlePerfHints(args: z.infer<typeof PerfHintsSchema>): string;
|