@work-rjkashyap/unified-ui 0.3.1 → 0.3.3
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/CHANGELOG.md +97 -0
- package/bin/cli.mjs +364 -333
- package/dist/{chunk-EUHL6H76.cjs → chunk-A2DGHQL2.cjs} +5285 -5303
- package/dist/{chunk-AQJ7H5SF.mjs → chunk-XAIUX2YS.mjs} +5285 -5303
- package/dist/components.cjs +310 -310
- package/dist/components.d.cts +730 -730
- package/dist/components.d.ts +730 -730
- package/dist/components.mjs +1 -1
- package/dist/index.cjs +311 -311
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/styles.css +12 -12
package/bin/cli.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
2
|
// ============================================================================
|
|
4
3
|
// Unified UI — CLI
|
|
5
4
|
// ============================================================================
|
|
@@ -13,7 +12,7 @@
|
|
|
13
12
|
// npx @work-rjkashyap/unified-ui init
|
|
14
13
|
//
|
|
15
14
|
// Components are fetched from the registry at:
|
|
16
|
-
// https://unified-ui
|
|
15
|
+
// https://unified-ui.space/r/<name>.json
|
|
17
16
|
//
|
|
18
17
|
// Files are written into the user's project at:
|
|
19
18
|
// src/components/ui/<component>.tsx
|
|
@@ -24,32 +23,27 @@
|
|
|
24
23
|
// This CLI resolves the full dependency tree — if you add "confirm-dialog",
|
|
25
24
|
// it also pulls in "alert-dialog", "button", "cn", "focus-ring", etc.
|
|
26
25
|
// ============================================================================
|
|
27
|
-
|
|
26
|
+
import { execSync } from "node:child_process";
|
|
28
27
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
29
28
|
import { dirname, join, resolve } from "node:path";
|
|
30
29
|
import { createInterface } from "node:readline";
|
|
31
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
32
30
|
|
|
33
31
|
// ---------------------------------------------------------------------------
|
|
34
32
|
// Config
|
|
35
33
|
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
34
|
const REGISTRY_BASE_URL =
|
|
38
|
-
process.env.UNIFIED_UI_REGISTRY_URL ||
|
|
39
|
-
"https://unified-ui-rajeshwar.vercel.app/r";
|
|
40
|
-
|
|
35
|
+
process.env.UNIFIED_UI_REGISTRY_URL || "https://www.unified-ui.space/r";
|
|
41
36
|
const CONFIG_FILE = "unified-ui.json";
|
|
42
|
-
|
|
43
37
|
// ---------------------------------------------------------------------------
|
|
44
38
|
// Starter kit templates
|
|
45
39
|
// ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
40
|
const FRAMEWORKS = [
|
|
48
41
|
{
|
|
49
42
|
name: "vite-react",
|
|
50
43
|
label: "Vite + React",
|
|
51
44
|
description: "Vite + React 19 SPA with full component library",
|
|
52
|
-
scaffoldCmd: (name) =>
|
|
45
|
+
scaffoldCmd: (name) =>
|
|
46
|
+
`npm create vite@latest ${name} -- --template react-ts`,
|
|
53
47
|
deps: ["@work-rjkashyap/unified-ui"],
|
|
54
48
|
devDeps: ["@tailwindcss/vite", "tailwindcss"],
|
|
55
49
|
},
|
|
@@ -57,7 +51,8 @@ const FRAMEWORKS = [
|
|
|
57
51
|
name: "nextjs",
|
|
58
52
|
label: "Next.js",
|
|
59
53
|
description: "Next.js App Router with SSR + full component library",
|
|
60
|
-
scaffoldCmd: (name) =>
|
|
54
|
+
scaffoldCmd: (name) =>
|
|
55
|
+
`npx create-next-app@latest ${name} --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --yes`,
|
|
61
56
|
deps: ["@work-rjkashyap/unified-ui", "next-themes"],
|
|
62
57
|
devDeps: [],
|
|
63
58
|
},
|
|
@@ -78,9 +73,8 @@ const FRAMEWORKS = [
|
|
|
78
73
|
devDeps: ["@tailwindcss/vite", "tailwindcss"],
|
|
79
74
|
},
|
|
80
75
|
];
|
|
81
|
-
|
|
82
76
|
const DEFAULT_CONFIG = {
|
|
83
|
-
$schema: "https://unified-ui
|
|
77
|
+
$schema: "https://unified-ui.space/r/schema/config.json",
|
|
84
78
|
srcDir: "src",
|
|
85
79
|
aliases: {
|
|
86
80
|
components: "@/components/ui",
|
|
@@ -89,7 +83,6 @@ const DEFAULT_CONFIG = {
|
|
|
89
83
|
},
|
|
90
84
|
typescript: true,
|
|
91
85
|
};
|
|
92
|
-
|
|
93
86
|
const COLORS = {
|
|
94
87
|
reset: "\x1b[0m",
|
|
95
88
|
bold: "\x1b[1m",
|
|
@@ -101,25 +94,19 @@ const COLORS = {
|
|
|
101
94
|
magenta: "\x1b[35m",
|
|
102
95
|
cyan: "\x1b[36m",
|
|
103
96
|
};
|
|
104
|
-
|
|
105
97
|
const c = (color, text) => `${COLORS[color]}${text}${COLORS.reset}`;
|
|
106
|
-
|
|
107
98
|
// ---------------------------------------------------------------------------
|
|
108
99
|
// Helpers
|
|
109
100
|
// ---------------------------------------------------------------------------
|
|
110
|
-
|
|
111
101
|
function log(msg = "") {
|
|
112
102
|
console.log(msg);
|
|
113
103
|
}
|
|
114
|
-
|
|
115
104
|
function logStep(icon, msg) {
|
|
116
105
|
console.log(` ${icon} ${msg}`);
|
|
117
106
|
}
|
|
118
|
-
|
|
119
107
|
function logError(msg) {
|
|
120
108
|
console.error(`\n ${c("red", "✗")} ${msg}\n`);
|
|
121
109
|
}
|
|
122
|
-
|
|
123
110
|
async function confirm(question) {
|
|
124
111
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
125
112
|
return new Promise((res) => {
|
|
@@ -129,7 +116,6 @@ async function confirm(question) {
|
|
|
129
116
|
});
|
|
130
117
|
});
|
|
131
118
|
}
|
|
132
|
-
|
|
133
119
|
async function promptText(question, defaultValue = "") {
|
|
134
120
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
135
121
|
const hint = defaultValue ? ` ${c("dim", `(${defaultValue})`)}` : "";
|
|
@@ -140,25 +126,25 @@ async function promptText(question, defaultValue = "") {
|
|
|
140
126
|
});
|
|
141
127
|
});
|
|
142
128
|
}
|
|
143
|
-
|
|
144
129
|
async function promptSelect(question, options) {
|
|
145
130
|
log(` ${question}`);
|
|
146
131
|
log();
|
|
147
132
|
for (let i = 0; i < options.length; i++) {
|
|
148
133
|
const opt = options[i];
|
|
149
|
-
log(
|
|
134
|
+
log(
|
|
135
|
+
` ${c("cyan", String(i + 1))}. ${c("bold", opt.label.padEnd(18))} ${c("dim", opt.description)}`,
|
|
136
|
+
);
|
|
150
137
|
}
|
|
151
138
|
log();
|
|
152
139
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
153
140
|
return new Promise((res) => {
|
|
154
|
-
rl.question(` ${c("dim", `Select (1-${options.length}):`)
|
|
141
|
+
rl.question(` ${c("dim", `Select (1-${options.length}):`)} `, (answer) => {
|
|
155
142
|
rl.close();
|
|
156
143
|
const idx = parseInt(answer.trim(), 10) - 1;
|
|
157
144
|
res(idx >= 0 && idx < options.length ? options[idx] : null);
|
|
158
145
|
});
|
|
159
146
|
});
|
|
160
147
|
}
|
|
161
|
-
|
|
162
148
|
function runCmd(cmd, cwd, stdio = "inherit") {
|
|
163
149
|
try {
|
|
164
150
|
execSync(cmd, { cwd, stdio });
|
|
@@ -167,28 +153,25 @@ function runCmd(cmd, cwd, stdio = "inherit") {
|
|
|
167
153
|
return false;
|
|
168
154
|
}
|
|
169
155
|
}
|
|
170
|
-
|
|
171
156
|
function ensureDir(dir) {
|
|
172
157
|
mkdirSync(dir, { recursive: true });
|
|
173
158
|
}
|
|
174
|
-
|
|
175
159
|
function writeOverlay(targetPath, content) {
|
|
176
160
|
ensureDir(dirname(targetPath));
|
|
177
161
|
writeFileSync(targetPath, content);
|
|
178
162
|
}
|
|
179
|
-
|
|
180
163
|
async function fetchJSON(url) {
|
|
181
164
|
const response = await fetch(url);
|
|
182
165
|
if (!response.ok) {
|
|
183
|
-
throw new Error(
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Failed to fetch ${url}: ${response.status} ${response.statusText}`,
|
|
168
|
+
);
|
|
184
169
|
}
|
|
185
170
|
return response.json();
|
|
186
171
|
}
|
|
187
|
-
|
|
188
172
|
// ---------------------------------------------------------------------------
|
|
189
173
|
// Config management
|
|
190
174
|
// ---------------------------------------------------------------------------
|
|
191
|
-
|
|
192
175
|
function findProjectRoot() {
|
|
193
176
|
let dir = process.cwd();
|
|
194
177
|
while (dir !== dirname(dir)) {
|
|
@@ -197,11 +180,9 @@ function findProjectRoot() {
|
|
|
197
180
|
}
|
|
198
181
|
return process.cwd();
|
|
199
182
|
}
|
|
200
|
-
|
|
201
183
|
function loadConfig() {
|
|
202
184
|
const root = findProjectRoot();
|
|
203
185
|
const configPath = join(root, CONFIG_FILE);
|
|
204
|
-
|
|
205
186
|
if (existsSync(configPath)) {
|
|
206
187
|
try {
|
|
207
188
|
return {
|
|
@@ -213,28 +194,27 @@ function loadConfig() {
|
|
|
213
194
|
return { root, ...DEFAULT_CONFIG };
|
|
214
195
|
}
|
|
215
196
|
}
|
|
216
|
-
|
|
217
197
|
return { root, ...DEFAULT_CONFIG };
|
|
218
198
|
}
|
|
219
|
-
|
|
220
199
|
function saveConfig(config) {
|
|
221
200
|
const root = findProjectRoot();
|
|
222
201
|
const configPath = join(root, CONFIG_FILE);
|
|
223
202
|
const { root: _root, ...rest } = config;
|
|
224
|
-
writeFileSync(configPath, JSON.stringify(rest, null, 2)
|
|
203
|
+
writeFileSync(configPath, `${JSON.stringify(rest, null, 2)}\n`);
|
|
225
204
|
}
|
|
226
|
-
|
|
227
205
|
// ---------------------------------------------------------------------------
|
|
228
206
|
// Path resolution
|
|
229
207
|
// ---------------------------------------------------------------------------
|
|
230
|
-
|
|
231
208
|
function resolveTargetPath(config, file) {
|
|
232
209
|
const srcDir = join(config.root, config.srcDir);
|
|
233
|
-
|
|
234
210
|
switch (file.type) {
|
|
235
211
|
case "component":
|
|
236
212
|
return join(srcDir, "components", "ui", basename(file.path));
|
|
237
213
|
case "util":
|
|
214
|
+
// Preserve full subdirectory structure for lib/tokens/* and lib/motion/*
|
|
215
|
+
if (file.path.startsWith("lib/tokens/")) {
|
|
216
|
+
return join(srcDir, file.path);
|
|
217
|
+
}
|
|
238
218
|
if (file.path.includes("motion/")) {
|
|
239
219
|
return join(srcDir, "lib", "motion", basename(file.path));
|
|
240
220
|
}
|
|
@@ -247,11 +227,9 @@ function resolveTargetPath(config, file) {
|
|
|
247
227
|
return join(srcDir, file.target || file.path);
|
|
248
228
|
}
|
|
249
229
|
}
|
|
250
|
-
|
|
251
230
|
function basename(p) {
|
|
252
231
|
return p.split("/").pop();
|
|
253
232
|
}
|
|
254
|
-
|
|
255
233
|
// ---------------------------------------------------------------------------
|
|
256
234
|
// Import path rewriting
|
|
257
235
|
// ---------------------------------------------------------------------------
|
|
@@ -262,10 +240,8 @@ function basename(p) {
|
|
|
262
240
|
//
|
|
263
241
|
// We rewrite these to match the user's alias config.
|
|
264
242
|
// ---------------------------------------------------------------------------
|
|
265
|
-
|
|
266
243
|
function rewriteContentImports(content, config) {
|
|
267
244
|
let result = content;
|
|
268
|
-
|
|
269
245
|
// Rewrite @/lib/* -> user's lib alias
|
|
270
246
|
if (config.aliases.lib !== "@/lib") {
|
|
271
247
|
result = result.replace(
|
|
@@ -273,7 +249,6 @@ function rewriteContentImports(content, config) {
|
|
|
273
249
|
`from "${config.aliases.lib}/`,
|
|
274
250
|
);
|
|
275
251
|
}
|
|
276
|
-
|
|
277
252
|
// Rewrite @/components/ui/* -> user's components alias
|
|
278
253
|
if (config.aliases.components !== "@/components/ui") {
|
|
279
254
|
result = result.replace(
|
|
@@ -281,35 +256,28 @@ function rewriteContentImports(content, config) {
|
|
|
281
256
|
`from "${config.aliases.components}/`,
|
|
282
257
|
);
|
|
283
258
|
}
|
|
284
|
-
|
|
285
259
|
return result;
|
|
286
260
|
}
|
|
287
|
-
|
|
288
261
|
// ---------------------------------------------------------------------------
|
|
289
262
|
// Dependency resolution
|
|
290
263
|
// ---------------------------------------------------------------------------
|
|
291
|
-
|
|
292
264
|
async function resolveFullDependencyTree(names, registryUrl) {
|
|
293
265
|
const resolved = new Map();
|
|
294
266
|
const queue = [...names];
|
|
295
267
|
const visited = new Set();
|
|
296
|
-
|
|
297
268
|
while (queue.length > 0) {
|
|
298
269
|
const name = queue.shift();
|
|
299
270
|
if (visited.has(name)) continue;
|
|
300
271
|
visited.add(name);
|
|
301
|
-
|
|
302
272
|
try {
|
|
303
273
|
const item = await fetchJSON(`${registryUrl}/${name}.json`);
|
|
304
274
|
resolved.set(name, item);
|
|
305
|
-
|
|
306
275
|
// Queue registry dependencies (other components)
|
|
307
276
|
if (item.registryDependencies) {
|
|
308
277
|
for (const dep of item.registryDependencies) {
|
|
309
278
|
if (!visited.has(dep)) queue.push(dep);
|
|
310
279
|
}
|
|
311
280
|
}
|
|
312
|
-
|
|
313
281
|
// Queue internal util dependencies
|
|
314
282
|
if (item.internalDependencies) {
|
|
315
283
|
for (const util of item.internalDependencies.utils || []) {
|
|
@@ -323,21 +291,18 @@ async function resolveFullDependencyTree(names, registryUrl) {
|
|
|
323
291
|
logError(`Could not fetch "${name}" from registry: ${err.message}`);
|
|
324
292
|
}
|
|
325
293
|
}
|
|
326
|
-
|
|
327
294
|
return resolved;
|
|
328
295
|
}
|
|
329
|
-
|
|
330
296
|
// ---------------------------------------------------------------------------
|
|
331
297
|
// npm dependency installer
|
|
332
298
|
// ---------------------------------------------------------------------------
|
|
333
|
-
|
|
334
299
|
async function detectPackageManager(root) {
|
|
335
|
-
if (existsSync(join(root, "bun.lock")) || existsSync(join(root, "bun.lockb")))
|
|
300
|
+
if (existsSync(join(root, "bun.lock")) || existsSync(join(root, "bun.lockb")))
|
|
301
|
+
return "bun";
|
|
336
302
|
if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
337
303
|
if (existsSync(join(root, "yarn.lock"))) return "yarn";
|
|
338
304
|
return "npm";
|
|
339
305
|
}
|
|
340
|
-
|
|
341
306
|
function getInstallCommand(pm, deps) {
|
|
342
307
|
const packages = deps.join(" ");
|
|
343
308
|
switch (pm) {
|
|
@@ -351,59 +316,289 @@ function getInstallCommand(pm, deps) {
|
|
|
351
316
|
return `npm install ${packages}`;
|
|
352
317
|
}
|
|
353
318
|
}
|
|
354
|
-
|
|
355
319
|
async function installNpmDeps(deps, root) {
|
|
356
320
|
if (deps.length === 0) return;
|
|
357
|
-
|
|
358
321
|
const pm = await detectPackageManager(root);
|
|
359
322
|
const cmd = getInstallCommand(pm, deps);
|
|
360
|
-
|
|
361
323
|
logStep("📦", `Installing npm dependencies with ${c("cyan", pm)}...`);
|
|
362
324
|
logStep(" ", c("dim", cmd));
|
|
363
|
-
|
|
364
325
|
const { execSync } = await import("node:child_process");
|
|
365
326
|
try {
|
|
366
327
|
execSync(cmd, { cwd: root, stdio: "pipe" });
|
|
367
328
|
logStep("✓", c("green", `${deps.length} package(s) installed`));
|
|
368
|
-
} catch (
|
|
329
|
+
} catch (_err) {
|
|
330
|
+
logStep(
|
|
331
|
+
"⚠",
|
|
332
|
+
c("yellow", `Auto-install failed. Run manually:\n ${cmd}`),
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async function installNpmDevDeps(deps, root) {
|
|
337
|
+
if (deps.length === 0) return;
|
|
338
|
+
const pm = await detectPackageManager(root);
|
|
339
|
+
const cmd = getInstallCommand(pm, deps)
|
|
340
|
+
.replace(" add ", " add -D ")
|
|
341
|
+
.replace(" install ", " install -D ");
|
|
342
|
+
logStep("📦", `Installing dev dependencies with ${c("cyan", pm)}...`);
|
|
343
|
+
logStep(" ", c("dim", cmd));
|
|
344
|
+
try {
|
|
345
|
+
execSync(cmd, { cwd: root, stdio: "pipe" });
|
|
346
|
+
logStep("✓", c("green", `${deps.length} dev package(s) installed`));
|
|
347
|
+
} catch (_err) {
|
|
369
348
|
logStep(
|
|
370
349
|
"⚠",
|
|
371
350
|
c("yellow", `Auto-install failed. Run manually:\n ${cmd}`),
|
|
372
351
|
);
|
|
373
352
|
}
|
|
374
353
|
}
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
// Vite + Tailwind CSS detection & setup
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
function detectViteConfig(root) {
|
|
358
|
+
for (const name of [
|
|
359
|
+
"vite.config.ts",
|
|
360
|
+
"vite.config.js",
|
|
361
|
+
"vite.config.mjs",
|
|
362
|
+
"vite.config.mts",
|
|
363
|
+
]) {
|
|
364
|
+
if (existsSync(join(root, name))) return name;
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function hasDependency(root, pkg) {
|
|
369
|
+
try {
|
|
370
|
+
const raw = readFileSync(join(root, "package.json"), "utf-8");
|
|
371
|
+
const json = JSON.parse(raw);
|
|
372
|
+
const all = {
|
|
373
|
+
...json.dependencies,
|
|
374
|
+
...json.devDependencies,
|
|
375
|
+
...json.peerDependencies,
|
|
376
|
+
};
|
|
377
|
+
return Boolean(all[pkg]);
|
|
378
|
+
} catch {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
async function setupTailwindForVite(root, config) {
|
|
383
|
+
const viteConfigName = detectViteConfig(root);
|
|
384
|
+
if (!viteConfigName) return; // Not a Vite project
|
|
385
|
+
|
|
386
|
+
logStep("🔍", "Detected Vite project");
|
|
387
|
+
|
|
388
|
+
// 1. Install tailwindcss + @tailwindcss/vite if missing
|
|
389
|
+
const missingDevDeps = [];
|
|
390
|
+
if (!hasDependency(root, "tailwindcss")) missingDevDeps.push("tailwindcss");
|
|
391
|
+
if (!hasDependency(root, "@tailwindcss/vite"))
|
|
392
|
+
missingDevDeps.push("@tailwindcss/vite");
|
|
393
|
+
if (missingDevDeps.length > 0) {
|
|
394
|
+
await installNpmDevDeps(missingDevDeps, root);
|
|
395
|
+
} else {
|
|
396
|
+
logStep("✓", c("dim", "tailwindcss + @tailwindcss/vite already installed"));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 2. Patch vite.config to add tailwindcss plugin, path import, and @ alias
|
|
400
|
+
const viteConfigPath = join(root, viteConfigName);
|
|
401
|
+
let viteContent = readFileSync(viteConfigPath, "utf-8");
|
|
402
|
+
let viteModified = false;
|
|
403
|
+
|
|
404
|
+
// 2a. Add @tailwindcss/vite import + plugin
|
|
405
|
+
if (!viteContent.includes("@tailwindcss/vite")) {
|
|
406
|
+
const tailwindImport = 'import tailwindcss from "@tailwindcss/vite";\n';
|
|
407
|
+
const importMatch = viteContent.match(/^(import\s.+\n)+/m);
|
|
408
|
+
if (importMatch) {
|
|
409
|
+
const lastImportEnd = importMatch.index + importMatch[0].length;
|
|
410
|
+
viteContent =
|
|
411
|
+
viteContent.slice(0, lastImportEnd) +
|
|
412
|
+
tailwindImport +
|
|
413
|
+
viteContent.slice(lastImportEnd);
|
|
414
|
+
} else {
|
|
415
|
+
viteContent = tailwindImport + viteContent;
|
|
416
|
+
}
|
|
417
|
+
if (viteContent.includes("plugins:")) {
|
|
418
|
+
viteContent = viteContent.replace(
|
|
419
|
+
/plugins:\s*\[/,
|
|
420
|
+
"plugins: [\n tailwindcss(),",
|
|
421
|
+
);
|
|
422
|
+
} else if (viteContent.includes("defineConfig({")) {
|
|
423
|
+
viteContent = viteContent.replace(
|
|
424
|
+
/defineConfig\(\{/,
|
|
425
|
+
"defineConfig({\n plugins: [tailwindcss()],",
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
viteModified = true;
|
|
429
|
+
} else {
|
|
430
|
+
logStep("✓", c("dim", `${viteConfigName} already has @tailwindcss/vite`));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// 2b. Add path import + resolve.alias for @
|
|
434
|
+
if (
|
|
435
|
+
!viteContent.includes("node:path") &&
|
|
436
|
+
!viteContent.match(/import\s+path\s+from\s+["']path["']/)
|
|
437
|
+
) {
|
|
438
|
+
const pathImport = 'import path from "node:path";\n';
|
|
439
|
+
const importMatch = viteContent.match(/^(import\s.+\n)+/m);
|
|
440
|
+
if (importMatch) {
|
|
441
|
+
const lastImportEnd = importMatch.index + importMatch[0].length;
|
|
442
|
+
viteContent =
|
|
443
|
+
viteContent.slice(0, lastImportEnd) +
|
|
444
|
+
pathImport +
|
|
445
|
+
viteContent.slice(lastImportEnd);
|
|
446
|
+
} else {
|
|
447
|
+
viteContent = pathImport + viteContent;
|
|
448
|
+
}
|
|
449
|
+
viteModified = true;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (
|
|
453
|
+
!viteContent.includes("resolve:") &&
|
|
454
|
+
!viteContent.includes("resolve.alias")
|
|
455
|
+
) {
|
|
456
|
+
// Add resolve.alias block before the closing of defineConfig
|
|
457
|
+
const resolveBlock = ` resolve: {\n alias: {\n "@": path.resolve(__dirname, "src"),\n },\n },`;
|
|
458
|
+
if (viteContent.includes("plugins:")) {
|
|
459
|
+
// Insert after plugins block — find the plugins array closing and add after
|
|
460
|
+
viteContent = viteContent.replace(
|
|
461
|
+
/(plugins:\s*\[[\s\S]*?\],?\n)/m,
|
|
462
|
+
`$1${resolveBlock}\n`,
|
|
463
|
+
);
|
|
464
|
+
} else if (viteContent.includes("defineConfig({")) {
|
|
465
|
+
viteContent = viteContent.replace(
|
|
466
|
+
/defineConfig\(\{/,
|
|
467
|
+
`defineConfig({\n${resolveBlock}`,
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
viteModified = true;
|
|
471
|
+
} else {
|
|
472
|
+
logStep("✓", c("dim", `${viteConfigName} already has resolve.alias`));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (viteModified) {
|
|
476
|
+
writeFileSync(viteConfigPath, viteContent);
|
|
477
|
+
logStep(
|
|
478
|
+
"✓",
|
|
479
|
+
`Patched ${c("cyan", viteConfigName)} with Tailwind CSS plugin + @ alias`,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// 2c. Patch tsconfig to add @/* path alias
|
|
484
|
+
const tsconfigCandidates = ["tsconfig.app.json", "tsconfig.json"];
|
|
485
|
+
for (const tscName of tsconfigCandidates) {
|
|
486
|
+
const tscPath = join(root, tscName);
|
|
487
|
+
if (!existsSync(tscPath)) continue;
|
|
488
|
+
const tscContent = readFileSync(tscPath, "utf-8");
|
|
489
|
+
if (tscContent.includes('"@/*"')) {
|
|
490
|
+
logStep("✓", c("dim", `${tscName} already has @/* path alias`));
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
// Strip comments for JSON parsing (single-line // comments)
|
|
495
|
+
const stripped = tscContent.replace(/^\s*\/\/.*$/gm, "");
|
|
496
|
+
const tsconfig = JSON.parse(stripped);
|
|
497
|
+
if (!tsconfig.compilerOptions) tsconfig.compilerOptions = {};
|
|
498
|
+
if (!tsconfig.compilerOptions.paths) tsconfig.compilerOptions.paths = {};
|
|
499
|
+
tsconfig.compilerOptions.paths["@/*"] = ["./src/*"];
|
|
500
|
+
// Also ensure baseUrl is set for paths to work
|
|
501
|
+
if (!tsconfig.compilerOptions.baseUrl) {
|
|
502
|
+
tsconfig.compilerOptions.baseUrl = ".";
|
|
503
|
+
}
|
|
504
|
+
writeFileSync(tscPath, `${JSON.stringify(tsconfig, null, 2)}\n`);
|
|
505
|
+
logStep("✓", `Patched ${c("cyan", tscName)} with @/* path alias`);
|
|
506
|
+
} catch {
|
|
507
|
+
logStep(
|
|
508
|
+
"⚠",
|
|
509
|
+
c("yellow", `Could not patch ${tscName} — add @/* path alias manually`),
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
375
514
|
|
|
515
|
+
// 3. Patch CSS entry file
|
|
516
|
+
const srcDir = join(root, config.srcDir);
|
|
517
|
+
const cssCandidates = [
|
|
518
|
+
"index.css",
|
|
519
|
+
"App.css",
|
|
520
|
+
"style.css",
|
|
521
|
+
"main.css",
|
|
522
|
+
"globals.css",
|
|
523
|
+
];
|
|
524
|
+
let cssPath = null;
|
|
525
|
+
for (const name of cssCandidates) {
|
|
526
|
+
const candidate = join(srcDir, name);
|
|
527
|
+
if (existsSync(candidate)) {
|
|
528
|
+
cssPath = candidate;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (!cssPath) {
|
|
533
|
+
// Create src/index.css if no CSS file found
|
|
534
|
+
cssPath = join(srcDir, "index.css");
|
|
535
|
+
}
|
|
536
|
+
const tailwindDirective = '@import "tailwindcss";';
|
|
537
|
+
const unifiedUiImport = '@import "@/styles/unified-ui.css";';
|
|
538
|
+
if (existsSync(cssPath)) {
|
|
539
|
+
let cssContent = readFileSync(cssPath, "utf-8");
|
|
540
|
+
let modified = false;
|
|
541
|
+
if (!cssContent.includes(tailwindDirective)) {
|
|
542
|
+
cssContent = `${tailwindDirective}\n${cssContent}`;
|
|
543
|
+
modified = true;
|
|
544
|
+
}
|
|
545
|
+
if (!cssContent.includes(unifiedUiImport)) {
|
|
546
|
+
// Insert right after the tailwindcss import
|
|
547
|
+
cssContent = cssContent.replace(
|
|
548
|
+
tailwindDirective,
|
|
549
|
+
`${tailwindDirective}\n${unifiedUiImport}`,
|
|
550
|
+
);
|
|
551
|
+
modified = true;
|
|
552
|
+
}
|
|
553
|
+
if (modified) {
|
|
554
|
+
writeFileSync(cssPath, cssContent);
|
|
555
|
+
logStep(
|
|
556
|
+
"✓",
|
|
557
|
+
`Updated ${c("cyan", cssPath.replace(`${root}/`, ""))} with Tailwind imports`,
|
|
558
|
+
);
|
|
559
|
+
} else {
|
|
560
|
+
logStep(
|
|
561
|
+
"✓",
|
|
562
|
+
c(
|
|
563
|
+
"dim",
|
|
564
|
+
`${cssPath.replace(`${root}/`, "")} already has Tailwind imports`,
|
|
565
|
+
),
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
const content = `${tailwindDirective}\n${unifiedUiImport}\n`;
|
|
570
|
+
mkdirSync(dirname(cssPath), { recursive: true });
|
|
571
|
+
writeFileSync(cssPath, content);
|
|
572
|
+
logStep(
|
|
573
|
+
"✓",
|
|
574
|
+
`Created ${c("cyan", cssPath.replace(`${root}/`, ""))} with Tailwind imports`,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
376
578
|
// ---------------------------------------------------------------------------
|
|
377
579
|
// File writer
|
|
378
580
|
// ---------------------------------------------------------------------------
|
|
379
|
-
|
|
380
581
|
function writeFile(targetPath, content, config, overwrite = false) {
|
|
381
582
|
const rewritten = rewriteContentImports(content, config);
|
|
382
|
-
|
|
383
583
|
if (existsSync(targetPath) && !overwrite) {
|
|
384
584
|
return { path: targetPath, status: "skipped" };
|
|
385
585
|
}
|
|
386
|
-
|
|
387
586
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
388
587
|
writeFileSync(targetPath, rewritten);
|
|
389
588
|
return { path: targetPath, status: "created" };
|
|
390
589
|
}
|
|
391
|
-
|
|
392
590
|
// ---------------------------------------------------------------------------
|
|
393
591
|
// Commands
|
|
394
592
|
// ---------------------------------------------------------------------------
|
|
395
|
-
|
|
396
593
|
// ---------------------------------------------------------------------------
|
|
397
594
|
// Starter kit overlays (embedded content)
|
|
398
595
|
// ---------------------------------------------------------------------------
|
|
399
|
-
|
|
400
596
|
const OVERLAYS = {
|
|
401
597
|
"vite-react": {
|
|
402
598
|
files: {
|
|
403
599
|
"vite.config.ts": `import tailwindcss from "@tailwindcss/vite";
|
|
404
600
|
import react from "@vitejs/plugin-react";
|
|
405
601
|
import { defineConfig } from "vite";
|
|
406
|
-
|
|
407
602
|
export default defineConfig({
|
|
408
603
|
plugins: [react(), tailwindcss()],
|
|
409
604
|
resolve: {
|
|
@@ -415,7 +610,6 @@ export default defineConfig({
|
|
|
415
610
|
`,
|
|
416
611
|
"src/index.css": `@import "tailwindcss";
|
|
417
612
|
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
418
|
-
|
|
419
613
|
body {
|
|
420
614
|
min-height: 100svh;
|
|
421
615
|
}
|
|
@@ -425,7 +619,6 @@ import { createRoot } from "react-dom/client";
|
|
|
425
619
|
import { DSThemeProvider } from "@work-rjkashyap/unified-ui/theme";
|
|
426
620
|
import App from "./App";
|
|
427
621
|
import "./index.css";
|
|
428
|
-
|
|
429
622
|
createRoot(document.getElementById("root")!).render(
|
|
430
623
|
<StrictMode>
|
|
431
624
|
<DSThemeProvider manageHtmlClass>
|
|
@@ -437,10 +630,8 @@ createRoot(document.getElementById("root")!).render(
|
|
|
437
630
|
"src/App.tsx": `import { Button } from "@work-rjkashyap/unified-ui/components";
|
|
438
631
|
import { Heading, Body } from "@work-rjkashyap/unified-ui/primitives";
|
|
439
632
|
import { useDSTheme } from "@work-rjkashyap/unified-ui/theme";
|
|
440
|
-
|
|
441
633
|
function App() {
|
|
442
634
|
const { theme, setTheme } = useDSTheme();
|
|
443
|
-
|
|
444
635
|
return (
|
|
445
636
|
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-8 text-foreground">
|
|
446
637
|
<div className="w-full max-w-md space-y-6 rounded-lg border border-border bg-card p-8">
|
|
@@ -450,7 +641,6 @@ function App() {
|
|
|
450
641
|
Your starter project is ready. Start building!
|
|
451
642
|
</Body>
|
|
452
643
|
</div>
|
|
453
|
-
|
|
454
644
|
<div className="flex items-center justify-center gap-3">
|
|
455
645
|
<Button variant="primary">Get Started</Button>
|
|
456
646
|
<Button
|
|
@@ -464,17 +654,14 @@ function App() {
|
|
|
464
654
|
</div>
|
|
465
655
|
);
|
|
466
656
|
}
|
|
467
|
-
|
|
468
657
|
export default App;
|
|
469
658
|
`,
|
|
470
659
|
},
|
|
471
660
|
},
|
|
472
|
-
|
|
473
661
|
nextjs: {
|
|
474
662
|
files: {
|
|
475
663
|
"src/app/globals.css": `@import "tailwindcss";
|
|
476
664
|
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
477
|
-
|
|
478
665
|
body {
|
|
479
666
|
min-height: 100svh;
|
|
480
667
|
}
|
|
@@ -483,12 +670,10 @@ body {
|
|
|
483
670
|
import { ThemeProvider } from "next-themes";
|
|
484
671
|
import { DSThemeProvider } from "@work-rjkashyap/unified-ui/theme";
|
|
485
672
|
import "./globals.css";
|
|
486
|
-
|
|
487
673
|
export const metadata: Metadata = {
|
|
488
674
|
title: "Unified UI App",
|
|
489
675
|
description: "Built with Unified UI and Next.js",
|
|
490
676
|
};
|
|
491
|
-
|
|
492
677
|
export default function RootLayout({
|
|
493
678
|
children,
|
|
494
679
|
}: {
|
|
@@ -511,14 +696,11 @@ export default function RootLayout({
|
|
|
511
696
|
}
|
|
512
697
|
`,
|
|
513
698
|
"src/app/page.tsx": `"use client";
|
|
514
|
-
|
|
515
699
|
import { Button } from "@work-rjkashyap/unified-ui/components";
|
|
516
700
|
import { Heading, Body } from "@work-rjkashyap/unified-ui/primitives";
|
|
517
701
|
import { useDSTheme } from "@work-rjkashyap/unified-ui/theme";
|
|
518
|
-
|
|
519
702
|
export default function Home() {
|
|
520
703
|
const { theme, setTheme } = useDSTheme();
|
|
521
|
-
|
|
522
704
|
return (
|
|
523
705
|
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-8 text-foreground">
|
|
524
706
|
<div className="w-full max-w-md space-y-6 rounded-lg border border-border bg-card p-8">
|
|
@@ -528,7 +710,6 @@ export default function Home() {
|
|
|
528
710
|
Your Next.js project is ready. Start building!
|
|
529
711
|
</Body>
|
|
530
712
|
</div>
|
|
531
|
-
|
|
532
713
|
<div className="flex items-center justify-center gap-3">
|
|
533
714
|
<Button variant="primary">Get Started</Button>
|
|
534
715
|
<Button
|
|
@@ -545,13 +726,11 @@ export default function Home() {
|
|
|
545
726
|
`,
|
|
546
727
|
},
|
|
547
728
|
},
|
|
548
|
-
|
|
549
729
|
vuejs: {
|
|
550
730
|
files: {
|
|
551
731
|
"vite.config.ts": `import tailwindcss from "@tailwindcss/vite";
|
|
552
732
|
import vue from "@vitejs/plugin-vue";
|
|
553
733
|
import { defineConfig } from "vite";
|
|
554
|
-
|
|
555
734
|
export default defineConfig({
|
|
556
735
|
plugins: [vue(), tailwindcss()],
|
|
557
736
|
resolve: {
|
|
@@ -563,7 +742,6 @@ export default defineConfig({
|
|
|
563
742
|
`,
|
|
564
743
|
"src/style.css": `@import "tailwindcss";
|
|
565
744
|
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
566
|
-
|
|
567
745
|
body {
|
|
568
746
|
min-height: 100svh;
|
|
569
747
|
}
|
|
@@ -571,12 +749,10 @@ body {
|
|
|
571
749
|
"src/main.ts": `import { createApp } from "vue";
|
|
572
750
|
import App from "./App.vue";
|
|
573
751
|
import "./style.css";
|
|
574
|
-
|
|
575
752
|
createApp(App).mount("#app");
|
|
576
753
|
`,
|
|
577
754
|
"src/lib/cn.ts": `import { type ClassValue, clsx } from "clsx";
|
|
578
755
|
import { twMerge } from "tailwind-merge";
|
|
579
|
-
|
|
580
756
|
export function cn(...inputs: ClassValue[]) {
|
|
581
757
|
return twMerge(clsx(inputs));
|
|
582
758
|
}
|
|
@@ -596,10 +772,8 @@ import {
|
|
|
596
772
|
UiText,
|
|
597
773
|
} from "./components/ui";
|
|
598
774
|
import { ref } from "vue";
|
|
599
|
-
|
|
600
775
|
const email = ref("");
|
|
601
776
|
</script>
|
|
602
|
-
|
|
603
777
|
<template>
|
|
604
778
|
<div
|
|
605
779
|
class="flex min-h-svh flex-col items-center justify-center gap-8 bg-background p-8 text-foreground"
|
|
@@ -611,7 +785,6 @@ const email = ref("");
|
|
|
611
785
|
Your Vue.js project is ready with components. Start building!
|
|
612
786
|
</UiText>
|
|
613
787
|
</UiCardHeader>
|
|
614
|
-
|
|
615
788
|
<UiCardBody class="space-y-6">
|
|
616
789
|
<!-- Buttons -->
|
|
617
790
|
<div class="space-y-2">
|
|
@@ -624,7 +797,6 @@ const email = ref("");
|
|
|
624
797
|
<UiButton variant="primary" :loading="true" size="sm">Loading</UiButton>
|
|
625
798
|
</div>
|
|
626
799
|
</div>
|
|
627
|
-
|
|
628
800
|
<!-- Badges -->
|
|
629
801
|
<div class="space-y-2">
|
|
630
802
|
<UiText variant="label">Badges</UiText>
|
|
@@ -638,19 +810,16 @@ const email = ref("");
|
|
|
638
810
|
<UiBadge variant="outline">Outline</UiBadge>
|
|
639
811
|
</div>
|
|
640
812
|
</div>
|
|
641
|
-
|
|
642
813
|
<!-- Input -->
|
|
643
814
|
<div class="space-y-2">
|
|
644
815
|
<UiText variant="label">Input</UiText>
|
|
645
816
|
<UiInput v-model="email" placeholder="you@example.com" />
|
|
646
817
|
</div>
|
|
647
|
-
|
|
648
818
|
<!-- Alert -->
|
|
649
819
|
<UiAlert variant="info" title="All set!">
|
|
650
820
|
Your design system components are working in Vue.
|
|
651
821
|
</UiAlert>
|
|
652
822
|
</UiCardBody>
|
|
653
|
-
|
|
654
823
|
<UiCardFooter class="justify-between">
|
|
655
824
|
<UiButton variant="primary">Get Started</UiButton>
|
|
656
825
|
<ThemeToggle />
|
|
@@ -661,9 +830,7 @@ const email = ref("");
|
|
|
661
830
|
`,
|
|
662
831
|
"src/components/ThemeToggle.vue": `<script setup lang="ts">
|
|
663
832
|
import { ref, onMounted } from "vue";
|
|
664
|
-
|
|
665
833
|
const theme = ref<"light" | "dark">("light");
|
|
666
|
-
|
|
667
834
|
onMounted(() => {
|
|
668
835
|
const stored = localStorage.getItem("theme");
|
|
669
836
|
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
@@ -671,18 +838,15 @@ onMounted(() => {
|
|
|
671
838
|
(stored as "light" | "dark") || (prefersDark ? "dark" : "light");
|
|
672
839
|
applyTheme();
|
|
673
840
|
});
|
|
674
|
-
|
|
675
841
|
function toggle() {
|
|
676
842
|
theme.value = theme.value === "dark" ? "light" : "dark";
|
|
677
843
|
applyTheme();
|
|
678
844
|
}
|
|
679
|
-
|
|
680
845
|
function applyTheme() {
|
|
681
846
|
document.documentElement.classList.toggle("dark", theme.value === "dark");
|
|
682
847
|
localStorage.setItem("theme", theme.value);
|
|
683
848
|
}
|
|
684
849
|
</script>
|
|
685
|
-
|
|
686
850
|
<template>
|
|
687
851
|
<button
|
|
688
852
|
class="inline-flex h-9 items-center justify-center rounded-md border border-border bg-background px-4 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
|
|
@@ -706,10 +870,8 @@ export { default as UiText } from "./Text.vue";
|
|
|
706
870
|
"src/components/ui/Button.vue": `<script setup lang="ts">
|
|
707
871
|
import { computed, type HTMLAttributes } from "vue";
|
|
708
872
|
import { cn } from "@/lib/cn";
|
|
709
|
-
|
|
710
873
|
type Variant = "primary" | "secondary" | "ghost" | "danger";
|
|
711
874
|
type Size = "sm" | "md" | "lg";
|
|
712
|
-
|
|
713
875
|
interface Props {
|
|
714
876
|
variant?: Variant;
|
|
715
877
|
size?: Size;
|
|
@@ -720,7 +882,6 @@ interface Props {
|
|
|
720
882
|
as?: string;
|
|
721
883
|
class?: HTMLAttributes["class"];
|
|
722
884
|
}
|
|
723
|
-
|
|
724
885
|
const props = withDefaults(defineProps<Props>(), {
|
|
725
886
|
variant: "primary",
|
|
726
887
|
size: "md",
|
|
@@ -730,7 +891,6 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
730
891
|
loading: false,
|
|
731
892
|
disabled: false,
|
|
732
893
|
});
|
|
733
|
-
|
|
734
894
|
const variantClasses: Record<Variant, string> = {
|
|
735
895
|
primary:
|
|
736
896
|
"bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active",
|
|
@@ -741,19 +901,16 @@ const variantClasses: Record<Variant, string> = {
|
|
|
741
901
|
danger:
|
|
742
902
|
"bg-danger text-danger-foreground hover:bg-danger-hover active:bg-danger-active",
|
|
743
903
|
};
|
|
744
|
-
|
|
745
904
|
const sizeClasses: Record<Size, string> = {
|
|
746
905
|
sm: "h-8 px-3 text-xs gap-1.5",
|
|
747
906
|
md: "h-[var(--ds-control-height,36px)] px-[var(--ds-padding-button-x,16px)] text-sm gap-2",
|
|
748
907
|
lg: "h-10 px-5 text-sm gap-2",
|
|
749
908
|
};
|
|
750
|
-
|
|
751
909
|
const iconOnlySizeClasses: Record<Size, string> = {
|
|
752
910
|
sm: "w-8 !px-0",
|
|
753
911
|
md: "w-9 !px-0",
|
|
754
912
|
lg: "w-10 !px-0",
|
|
755
913
|
};
|
|
756
|
-
|
|
757
914
|
const classes = computed(() =>
|
|
758
915
|
cn(
|
|
759
916
|
// base
|
|
@@ -776,7 +933,6 @@ const classes = computed(() =>
|
|
|
776
933
|
),
|
|
777
934
|
);
|
|
778
935
|
</script>
|
|
779
|
-
|
|
780
936
|
<template>
|
|
781
937
|
<component
|
|
782
938
|
:is="as"
|
|
@@ -814,7 +970,6 @@ const classes = computed(() =>
|
|
|
814
970
|
"src/components/ui/Badge.vue": `<script setup lang="ts">
|
|
815
971
|
import { computed, type HTMLAttributes } from "vue";
|
|
816
972
|
import { cn } from "@/lib/cn";
|
|
817
|
-
|
|
818
973
|
type Variant =
|
|
819
974
|
| "default"
|
|
820
975
|
| "primary"
|
|
@@ -825,22 +980,18 @@ type Variant =
|
|
|
825
980
|
| "info"
|
|
826
981
|
| "outline";
|
|
827
982
|
type Size = "sm" | "md" | "lg";
|
|
828
|
-
|
|
829
983
|
interface Props {
|
|
830
984
|
variant?: Variant;
|
|
831
985
|
size?: Size;
|
|
832
986
|
dismissible?: boolean;
|
|
833
987
|
class?: HTMLAttributes["class"];
|
|
834
988
|
}
|
|
835
|
-
|
|
836
989
|
const props = withDefaults(defineProps<Props>(), {
|
|
837
990
|
variant: "default",
|
|
838
991
|
size: "md",
|
|
839
992
|
dismissible: false,
|
|
840
993
|
});
|
|
841
|
-
|
|
842
994
|
const emit = defineEmits<{ dismiss: [] }>();
|
|
843
|
-
|
|
844
995
|
const variantClasses: Record<Variant, string> = {
|
|
845
996
|
default: "bg-muted text-foreground border border-transparent",
|
|
846
997
|
primary:
|
|
@@ -855,13 +1006,11 @@ const variantClasses: Record<Variant, string> = {
|
|
|
855
1006
|
info: "bg-info-muted text-info-muted-foreground border border-transparent",
|
|
856
1007
|
outline: "bg-transparent text-foreground border border-border",
|
|
857
1008
|
};
|
|
858
|
-
|
|
859
1009
|
const sizeClasses: Record<Size, string> = {
|
|
860
1010
|
sm: "px-2 py-0.5 text-[11px] gap-1",
|
|
861
1011
|
md: "px-2.5 py-1 text-xs gap-1.5",
|
|
862
1012
|
lg: "px-3 py-1.5 text-sm gap-2",
|
|
863
1013
|
};
|
|
864
|
-
|
|
865
1014
|
const classes = computed(() =>
|
|
866
1015
|
cn(
|
|
867
1016
|
"inline-flex items-center gap-1.5 rounded-full font-medium leading-none whitespace-nowrap",
|
|
@@ -873,7 +1022,6 @@ const classes = computed(() =>
|
|
|
873
1022
|
),
|
|
874
1023
|
);
|
|
875
1024
|
</script>
|
|
876
|
-
|
|
877
1025
|
<template>
|
|
878
1026
|
<span :class="classes" data-ds data-ds-component="badge">
|
|
879
1027
|
<slot />
|
|
@@ -904,10 +1052,8 @@ const classes = computed(() =>
|
|
|
904
1052
|
"src/components/ui/Card.vue": `<script setup lang="ts">
|
|
905
1053
|
import { computed, provide, type HTMLAttributes, type InjectionKey } from "vue";
|
|
906
1054
|
import { cn } from "@/lib/cn";
|
|
907
|
-
|
|
908
1055
|
type Variant = "default" | "outlined" | "elevated" | "interactive";
|
|
909
1056
|
type Padding = "compact" | "comfortable";
|
|
910
|
-
|
|
911
1057
|
interface Props {
|
|
912
1058
|
variant?: Variant;
|
|
913
1059
|
padding?: Padding;
|
|
@@ -915,17 +1061,14 @@ interface Props {
|
|
|
915
1061
|
as?: string;
|
|
916
1062
|
class?: HTMLAttributes["class"];
|
|
917
1063
|
}
|
|
918
|
-
|
|
919
1064
|
const props = withDefaults(defineProps<Props>(), {
|
|
920
1065
|
variant: "default",
|
|
921
1066
|
padding: "compact",
|
|
922
1067
|
as: "div",
|
|
923
1068
|
fullWidth: false,
|
|
924
1069
|
});
|
|
925
|
-
|
|
926
1070
|
export const cardPaddingKey = Symbol("cardPadding") as InjectionKey<Padding>;
|
|
927
1071
|
provide(cardPaddingKey, props.padding);
|
|
928
|
-
|
|
929
1072
|
const variantClasses: Record<Variant, string> = {
|
|
930
1073
|
default: "bg-surface border border-border",
|
|
931
1074
|
outlined: "bg-transparent border border-border-strong",
|
|
@@ -933,7 +1076,6 @@ const variantClasses: Record<Variant, string> = {
|
|
|
933
1076
|
interactive:
|
|
934
1077
|
"bg-surface border border-border transition-[border-color,box-shadow,transform] duration-[var(--duration-normal,200ms)] ease-[var(--easing-standard,cubic-bezier(0.4,0,0.2,1))] hover:border-border-strong hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 active:shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring cursor-pointer",
|
|
935
1078
|
};
|
|
936
|
-
|
|
937
1079
|
const classes = computed(() =>
|
|
938
1080
|
cn(
|
|
939
1081
|
"flex flex-col rounded-md overflow-hidden text-sm text-foreground",
|
|
@@ -943,7 +1085,6 @@ const classes = computed(() =>
|
|
|
943
1085
|
),
|
|
944
1086
|
);
|
|
945
1087
|
</script>
|
|
946
|
-
|
|
947
1088
|
<template>
|
|
948
1089
|
<component :is="as" :class="classes" data-ds data-ds-component="card">
|
|
949
1090
|
<slot />
|
|
@@ -954,14 +1095,11 @@ const classes = computed(() =>
|
|
|
954
1095
|
import { inject, computed, type HTMLAttributes } from "vue";
|
|
955
1096
|
import { cn } from "@/lib/cn";
|
|
956
1097
|
import { cardPaddingKey } from "./Card.vue";
|
|
957
|
-
|
|
958
1098
|
interface Props {
|
|
959
1099
|
class?: HTMLAttributes["class"];
|
|
960
1100
|
}
|
|
961
|
-
|
|
962
1101
|
const props = defineProps<Props>();
|
|
963
1102
|
const padding = inject(cardPaddingKey, "compact");
|
|
964
|
-
|
|
965
1103
|
const classes = computed(() =>
|
|
966
1104
|
cn(
|
|
967
1105
|
"flex flex-col",
|
|
@@ -970,7 +1108,6 @@ const classes = computed(() =>
|
|
|
970
1108
|
),
|
|
971
1109
|
);
|
|
972
1110
|
</script>
|
|
973
|
-
|
|
974
1111
|
<template>
|
|
975
1112
|
<div :class="classes" data-ds data-ds-component="card-header">
|
|
976
1113
|
<slot />
|
|
@@ -981,14 +1118,11 @@ const classes = computed(() =>
|
|
|
981
1118
|
import { inject, computed, type HTMLAttributes } from "vue";
|
|
982
1119
|
import { cn } from "@/lib/cn";
|
|
983
1120
|
import { cardPaddingKey } from "./Card.vue";
|
|
984
|
-
|
|
985
1121
|
interface Props {
|
|
986
1122
|
class?: HTMLAttributes["class"];
|
|
987
1123
|
}
|
|
988
|
-
|
|
989
1124
|
const props = defineProps<Props>();
|
|
990
1125
|
const padding = inject(cardPaddingKey, "compact");
|
|
991
|
-
|
|
992
1126
|
const classes = computed(() =>
|
|
993
1127
|
cn(
|
|
994
1128
|
"flex flex-col flex-1",
|
|
@@ -997,7 +1131,6 @@ const classes = computed(() =>
|
|
|
997
1131
|
),
|
|
998
1132
|
);
|
|
999
1133
|
</script>
|
|
1000
|
-
|
|
1001
1134
|
<template>
|
|
1002
1135
|
<div :class="classes" data-ds data-ds-component="card-body">
|
|
1003
1136
|
<slot />
|
|
@@ -1008,14 +1141,11 @@ const classes = computed(() =>
|
|
|
1008
1141
|
import { inject, computed, type HTMLAttributes } from "vue";
|
|
1009
1142
|
import { cn } from "@/lib/cn";
|
|
1010
1143
|
import { cardPaddingKey } from "./Card.vue";
|
|
1011
|
-
|
|
1012
1144
|
interface Props {
|
|
1013
1145
|
class?: HTMLAttributes["class"];
|
|
1014
1146
|
}
|
|
1015
|
-
|
|
1016
1147
|
const props = defineProps<Props>();
|
|
1017
1148
|
const padding = inject(cardPaddingKey, "compact");
|
|
1018
|
-
|
|
1019
1149
|
const classes = computed(() =>
|
|
1020
1150
|
cn(
|
|
1021
1151
|
"flex items-center",
|
|
@@ -1024,7 +1154,6 @@ const classes = computed(() =>
|
|
|
1024
1154
|
),
|
|
1025
1155
|
);
|
|
1026
1156
|
</script>
|
|
1027
|
-
|
|
1028
1157
|
<template>
|
|
1029
1158
|
<div :class="classes" data-ds data-ds-component="card-footer">
|
|
1030
1159
|
<slot />
|
|
@@ -1034,25 +1163,20 @@ const classes = computed(() =>
|
|
|
1034
1163
|
"src/components/ui/Input.vue": `<script setup lang="ts">
|
|
1035
1164
|
import { computed, type HTMLAttributes } from "vue";
|
|
1036
1165
|
import { cn } from "@/lib/cn";
|
|
1037
|
-
|
|
1038
1166
|
type Variant = "default" | "error" | "success";
|
|
1039
1167
|
type Size = "sm" | "md" | "lg";
|
|
1040
|
-
|
|
1041
1168
|
interface Props {
|
|
1042
1169
|
variant?: Variant;
|
|
1043
1170
|
size?: Size;
|
|
1044
1171
|
disabled?: boolean;
|
|
1045
1172
|
class?: HTMLAttributes["class"];
|
|
1046
1173
|
}
|
|
1047
|
-
|
|
1048
1174
|
const props = withDefaults(defineProps<Props>(), {
|
|
1049
1175
|
variant: "default",
|
|
1050
1176
|
size: "md",
|
|
1051
1177
|
disabled: false,
|
|
1052
1178
|
});
|
|
1053
|
-
|
|
1054
1179
|
const model = defineModel<string>();
|
|
1055
|
-
|
|
1056
1180
|
const variantClasses: Record<Variant, string> = {
|
|
1057
1181
|
default:
|
|
1058
1182
|
"border-input hover:border-border-strong focus-visible:border-border-strong",
|
|
@@ -1061,13 +1185,11 @@ const variantClasses: Record<Variant, string> = {
|
|
|
1061
1185
|
success:
|
|
1062
1186
|
"border-success text-foreground focus-visible:border-success placeholder:text-input-placeholder",
|
|
1063
1187
|
};
|
|
1064
|
-
|
|
1065
1188
|
const sizeClasses: Record<Size, string> = {
|
|
1066
1189
|
sm: "h-8 px-2.5 text-xs",
|
|
1067
1190
|
md: "h-[var(--ds-control-height,36px)] px-3 text-sm",
|
|
1068
1191
|
lg: "h-10 px-3.5 text-sm",
|
|
1069
1192
|
};
|
|
1070
|
-
|
|
1071
1193
|
const classes = computed(() =>
|
|
1072
1194
|
cn(
|
|
1073
1195
|
"flex w-full text-sm leading-5 rounded-md border bg-background text-input-foreground",
|
|
@@ -1082,7 +1204,6 @@ const classes = computed(() =>
|
|
|
1082
1204
|
),
|
|
1083
1205
|
);
|
|
1084
1206
|
</script>
|
|
1085
|
-
|
|
1086
1207
|
<template>
|
|
1087
1208
|
<input
|
|
1088
1209
|
v-model="model"
|
|
@@ -1096,23 +1217,18 @@ const classes = computed(() =>
|
|
|
1096
1217
|
"src/components/ui/Alert.vue": `<script setup lang="ts">
|
|
1097
1218
|
import { computed, ref, type HTMLAttributes } from "vue";
|
|
1098
1219
|
import { cn } from "@/lib/cn";
|
|
1099
|
-
|
|
1100
1220
|
type Variant = "info" | "success" | "warning" | "danger" | "default";
|
|
1101
|
-
|
|
1102
1221
|
interface Props {
|
|
1103
1222
|
variant?: Variant;
|
|
1104
1223
|
title?: string;
|
|
1105
1224
|
dismissible?: boolean;
|
|
1106
1225
|
class?: HTMLAttributes["class"];
|
|
1107
1226
|
}
|
|
1108
|
-
|
|
1109
1227
|
const props = withDefaults(defineProps<Props>(), {
|
|
1110
1228
|
variant: "info",
|
|
1111
1229
|
dismissible: false,
|
|
1112
1230
|
});
|
|
1113
|
-
|
|
1114
1231
|
const dismissed = ref(false);
|
|
1115
|
-
|
|
1116
1232
|
const variantClasses: Record<Variant, string> = {
|
|
1117
1233
|
info: "bg-info-muted text-info-muted-foreground border-info/20",
|
|
1118
1234
|
success: "bg-success-muted text-success-muted-foreground border-success/20",
|
|
@@ -1120,7 +1236,6 @@ const variantClasses: Record<Variant, string> = {
|
|
|
1120
1236
|
danger: "bg-danger-muted text-danger-muted-foreground border-danger/20",
|
|
1121
1237
|
default: "bg-muted text-muted-foreground border-border",
|
|
1122
1238
|
};
|
|
1123
|
-
|
|
1124
1239
|
const iconColorClasses: Record<Variant, string> = {
|
|
1125
1240
|
info: "text-info",
|
|
1126
1241
|
success: "text-success",
|
|
@@ -1128,7 +1243,6 @@ const iconColorClasses: Record<Variant, string> = {
|
|
|
1128
1243
|
danger: "text-danger",
|
|
1129
1244
|
default: "text-muted-foreground",
|
|
1130
1245
|
};
|
|
1131
|
-
|
|
1132
1246
|
const classes = computed(() =>
|
|
1133
1247
|
cn(
|
|
1134
1248
|
"relative flex gap-3 rounded-md p-4 text-sm leading-5 border",
|
|
@@ -1137,7 +1251,6 @@ const classes = computed(() =>
|
|
|
1137
1251
|
props.class,
|
|
1138
1252
|
),
|
|
1139
1253
|
);
|
|
1140
|
-
|
|
1141
1254
|
// SVG icon paths by variant
|
|
1142
1255
|
const iconPaths: Record<Variant, string> = {
|
|
1143
1256
|
info: "M12 16v-4m0-4h.01M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10z",
|
|
@@ -1147,7 +1260,6 @@ const iconPaths: Record<Variant, string> = {
|
|
|
1147
1260
|
default: "M12 16v-4m0-4h.01M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10z",
|
|
1148
1261
|
};
|
|
1149
1262
|
</script>
|
|
1150
|
-
|
|
1151
1263
|
<template>
|
|
1152
1264
|
<div
|
|
1153
1265
|
v-if="!dismissed"
|
|
@@ -1201,42 +1313,34 @@ const iconPaths: Record<Variant, string> = {
|
|
|
1201
1313
|
"src/components/ui/Heading.vue": `<script setup lang="ts">
|
|
1202
1314
|
import { computed, type HTMLAttributes } from "vue";
|
|
1203
1315
|
import { cn } from "@/lib/cn";
|
|
1204
|
-
|
|
1205
1316
|
type Level = 1 | 2 | 3 | 4;
|
|
1206
1317
|
type Color = "default" | "foreground" | "muted" | "primary";
|
|
1207
|
-
|
|
1208
1318
|
interface Props {
|
|
1209
1319
|
level?: Level;
|
|
1210
1320
|
color?: Color;
|
|
1211
1321
|
class?: HTMLAttributes["class"];
|
|
1212
1322
|
}
|
|
1213
|
-
|
|
1214
1323
|
const props = withDefaults(defineProps<Props>(), {
|
|
1215
1324
|
level: 1,
|
|
1216
1325
|
color: "default",
|
|
1217
1326
|
});
|
|
1218
|
-
|
|
1219
1327
|
const levelClasses: Record<Level, string> = {
|
|
1220
1328
|
1: "text-[30px] leading-[36px] font-bold tracking-tight",
|
|
1221
1329
|
2: "text-[24px] leading-[32px] font-semibold tracking-tight",
|
|
1222
1330
|
3: "text-[20px] leading-[28px] font-semibold tracking-normal",
|
|
1223
1331
|
4: "text-[18px] leading-[28px] font-medium tracking-normal",
|
|
1224
1332
|
};
|
|
1225
|
-
|
|
1226
1333
|
const colorClasses: Record<Color, string> = {
|
|
1227
1334
|
default: "text-foreground",
|
|
1228
1335
|
foreground: "text-foreground",
|
|
1229
1336
|
muted: "text-muted-foreground",
|
|
1230
1337
|
primary: "text-primary",
|
|
1231
1338
|
};
|
|
1232
|
-
|
|
1233
1339
|
const tag = computed(() => \`h\${props.level}\` as const);
|
|
1234
|
-
|
|
1235
1340
|
const classes = computed(() =>
|
|
1236
1341
|
cn(levelClasses[props.level], colorClasses[props.color], props.class),
|
|
1237
1342
|
);
|
|
1238
1343
|
</script>
|
|
1239
|
-
|
|
1240
1344
|
<template>
|
|
1241
1345
|
<component :is="tag" :class="classes" data-ds data-ds-component="heading">
|
|
1242
1346
|
<slot />
|
|
@@ -1246,7 +1350,6 @@ const classes = computed(() =>
|
|
|
1246
1350
|
"src/components/ui/Text.vue": `<script setup lang="ts">
|
|
1247
1351
|
import { computed, type HTMLAttributes } from "vue";
|
|
1248
1352
|
import { cn } from "@/lib/cn";
|
|
1249
|
-
|
|
1250
1353
|
type Variant = "body" | "bodySm" | "caption" | "label" | "overline" | "code";
|
|
1251
1354
|
type Color =
|
|
1252
1355
|
| "default"
|
|
@@ -1257,20 +1360,17 @@ type Color =
|
|
|
1257
1360
|
| "warning"
|
|
1258
1361
|
| "danger"
|
|
1259
1362
|
| "info";
|
|
1260
|
-
|
|
1261
1363
|
interface Props {
|
|
1262
1364
|
variant?: Variant;
|
|
1263
1365
|
color?: Color;
|
|
1264
1366
|
as?: string;
|
|
1265
1367
|
class?: HTMLAttributes["class"];
|
|
1266
1368
|
}
|
|
1267
|
-
|
|
1268
1369
|
const props = withDefaults(defineProps<Props>(), {
|
|
1269
1370
|
variant: "body",
|
|
1270
1371
|
color: "default",
|
|
1271
1372
|
as: "p",
|
|
1272
1373
|
});
|
|
1273
|
-
|
|
1274
1374
|
const variantClasses: Record<Variant, string> = {
|
|
1275
1375
|
body: "text-[16px] leading-[24px] font-normal tracking-normal",
|
|
1276
1376
|
bodySm: "text-[14px] leading-[20px] font-normal tracking-normal",
|
|
@@ -1281,7 +1381,6 @@ const variantClasses: Record<Variant, string> = {
|
|
|
1281
1381
|
"text-[12px] leading-[16px] font-semibold tracking-wider uppercase text-muted-foreground",
|
|
1282
1382
|
code: "text-[14px] leading-[20px] font-normal tracking-normal font-mono",
|
|
1283
1383
|
};
|
|
1284
|
-
|
|
1285
1384
|
const colorClasses: Record<Color, string> = {
|
|
1286
1385
|
default: "text-foreground",
|
|
1287
1386
|
foreground: "text-foreground",
|
|
@@ -1292,12 +1391,10 @@ const colorClasses: Record<Color, string> = {
|
|
|
1292
1391
|
danger: "text-danger",
|
|
1293
1392
|
info: "text-info",
|
|
1294
1393
|
};
|
|
1295
|
-
|
|
1296
1394
|
const classes = computed(() =>
|
|
1297
1395
|
cn(variantClasses[props.variant], colorClasses[props.color], props.class),
|
|
1298
1396
|
);
|
|
1299
1397
|
</script>
|
|
1300
|
-
|
|
1301
1398
|
<template>
|
|
1302
1399
|
<component :is="as" :class="classes" data-ds data-ds-component="text">
|
|
1303
1400
|
<slot />
|
|
@@ -1306,13 +1403,11 @@ const classes = computed(() =>
|
|
|
1306
1403
|
`,
|
|
1307
1404
|
},
|
|
1308
1405
|
},
|
|
1309
|
-
|
|
1310
1406
|
"laravel-blade": {
|
|
1311
1407
|
files: {
|
|
1312
1408
|
"vite.config.js": `import tailwindcss from "@tailwindcss/vite";
|
|
1313
1409
|
import laravel from "laravel-vite-plugin";
|
|
1314
1410
|
import { defineConfig } from "vite";
|
|
1315
|
-
|
|
1316
1411
|
export default defineConfig({
|
|
1317
1412
|
plugins: [
|
|
1318
1413
|
laravel({
|
|
@@ -1328,7 +1423,6 @@ export default defineConfig({
|
|
|
1328
1423
|
`,
|
|
1329
1424
|
"resources/js/app.js": `// Unified UI — Laravel Blade Starter
|
|
1330
1425
|
// Design tokens are loaded via CSS. This file handles theme toggling.
|
|
1331
|
-
|
|
1332
1426
|
function initTheme() {
|
|
1333
1427
|
const stored = localStorage.getItem("theme");
|
|
1334
1428
|
const prefersDark = window.matchMedia(
|
|
@@ -1337,15 +1431,12 @@ function initTheme() {
|
|
|
1337
1431
|
const theme = stored || (prefersDark ? "dark" : "light");
|
|
1338
1432
|
document.documentElement.classList.toggle("dark", theme === "dark");
|
|
1339
1433
|
}
|
|
1340
|
-
|
|
1341
1434
|
function toggleTheme() {
|
|
1342
1435
|
const isDark = document.documentElement.classList.toggle("dark");
|
|
1343
1436
|
localStorage.setItem("theme", isDark ? "dark" : "light");
|
|
1344
1437
|
}
|
|
1345
|
-
|
|
1346
1438
|
// Initialize on load
|
|
1347
1439
|
initTheme();
|
|
1348
|
-
|
|
1349
1440
|
// Expose globally for Blade onclick handlers
|
|
1350
1441
|
window.toggleTheme = toggleTheme;
|
|
1351
1442
|
`,
|
|
@@ -1364,7 +1455,6 @@ window.toggleTheme = toggleTheme;
|
|
|
1364
1455
|
</html>
|
|
1365
1456
|
`,
|
|
1366
1457
|
"resources/views/welcome.blade.php": `@extends('layouts.app')
|
|
1367
|
-
|
|
1368
1458
|
@section('content')
|
|
1369
1459
|
<div class="flex min-h-svh flex-col items-center justify-center gap-8 p-8">
|
|
1370
1460
|
<x-ui.card class="w-full max-w-lg">
|
|
@@ -1374,7 +1464,6 @@ window.toggleTheme = toggleTheme;
|
|
|
1374
1464
|
Your Laravel project is ready with components. Start building!
|
|
1375
1465
|
</x-ui.text>
|
|
1376
1466
|
</x-ui.card-header>
|
|
1377
|
-
|
|
1378
1467
|
<x-ui.card-body class="space-y-6">
|
|
1379
1468
|
{{-- Buttons --}}
|
|
1380
1469
|
<div class="space-y-2">
|
|
@@ -1387,7 +1476,6 @@ window.toggleTheme = toggleTheme;
|
|
|
1387
1476
|
<x-ui.button variant="primary" :loading="true" size="sm">Loading</x-ui.button>
|
|
1388
1477
|
</div>
|
|
1389
1478
|
</div>
|
|
1390
|
-
|
|
1391
1479
|
{{-- Badges --}}
|
|
1392
1480
|
<div class="space-y-2">
|
|
1393
1481
|
<x-ui.text variant="label">Badges</x-ui.text>
|
|
@@ -1401,19 +1489,16 @@ window.toggleTheme = toggleTheme;
|
|
|
1401
1489
|
<x-ui.badge variant="outline">Outline</x-ui.badge>
|
|
1402
1490
|
</div>
|
|
1403
1491
|
</div>
|
|
1404
|
-
|
|
1405
1492
|
{{-- Input --}}
|
|
1406
1493
|
<div class="space-y-2">
|
|
1407
1494
|
<x-ui.text variant="label">Input</x-ui.text>
|
|
1408
1495
|
<x-ui.input placeholder="you@example.com" />
|
|
1409
1496
|
</div>
|
|
1410
|
-
|
|
1411
1497
|
{{-- Alert --}}
|
|
1412
1498
|
<x-ui.alert variant="info" title="All set!">
|
|
1413
1499
|
Your design system components are working in Laravel.
|
|
1414
1500
|
</x-ui.alert>
|
|
1415
1501
|
</x-ui.card-body>
|
|
1416
|
-
|
|
1417
1502
|
<x-ui.card-footer class="justify-between">
|
|
1418
1503
|
<x-ui.button variant="primary">Get Started</x-ui.button>
|
|
1419
1504
|
<x-ui.button variant="secondary" onclick="toggleTheme()">Toggle Theme</x-ui.button>
|
|
@@ -1431,7 +1516,6 @@ window.toggleTheme = toggleTheme;
|
|
|
1431
1516
|
'loading' => false,
|
|
1432
1517
|
'disabled' => false,
|
|
1433
1518
|
])
|
|
1434
|
-
|
|
1435
1519
|
@php
|
|
1436
1520
|
$variants = [
|
|
1437
1521
|
'primary' => 'bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active',
|
|
@@ -1439,19 +1523,16 @@ $variants = [
|
|
|
1439
1523
|
'ghost' => 'bg-transparent text-foreground hover:bg-muted hover:text-foreground active:bg-secondary-active',
|
|
1440
1524
|
'danger' => 'bg-danger text-danger-foreground hover:bg-danger-hover active:bg-danger-active',
|
|
1441
1525
|
];
|
|
1442
|
-
|
|
1443
1526
|
$sizes = [
|
|
1444
1527
|
'sm' => 'h-8 px-3 text-xs gap-1.5',
|
|
1445
1528
|
'md' => 'h-[var(--ds-control-height,36px)] px-[var(--ds-padding-button-x,16px)] text-sm gap-2',
|
|
1446
1529
|
'lg' => 'h-10 px-5 text-sm gap-2',
|
|
1447
1530
|
];
|
|
1448
|
-
|
|
1449
1531
|
$iconOnlySizes = [
|
|
1450
1532
|
'sm' => 'w-8 !px-0',
|
|
1451
1533
|
'md' => 'w-9 !px-0',
|
|
1452
1534
|
'lg' => 'w-10 !px-0',
|
|
1453
1535
|
];
|
|
1454
|
-
|
|
1455
1536
|
$classes = implode(' ', array_filter([
|
|
1456
1537
|
'inline-flex items-center justify-center gap-2 text-sm font-medium leading-5 rounded-md',
|
|
1457
1538
|
'transition-[color,background-color,border-color,box-shadow,opacity,transform] duration-[var(--duration-fast,150ms)] ease-[var(--easing-standard,cubic-bezier(0.4,0,0.2,1))]',
|
|
@@ -1465,7 +1546,6 @@ $classes = implode(' ', array_filter([
|
|
|
1465
1546
|
$loading ? 'pointer-events-none opacity-70' : '',
|
|
1466
1547
|
]));
|
|
1467
1548
|
@endphp
|
|
1468
|
-
|
|
1469
1549
|
<{{ $as }}
|
|
1470
1550
|
{{ $attributes->merge(['class' => $classes, 'disabled' => $disabled || $loading]) }}
|
|
1471
1551
|
data-ds
|
|
@@ -1486,7 +1566,6 @@ $classes = implode(' ', array_filter([
|
|
|
1486
1566
|
'size' => 'md',
|
|
1487
1567
|
'dismissible' => false,
|
|
1488
1568
|
])
|
|
1489
|
-
|
|
1490
1569
|
@php
|
|
1491
1570
|
$variants = [
|
|
1492
1571
|
'default' => 'bg-muted text-foreground border border-transparent',
|
|
@@ -1498,13 +1577,11 @@ $variants = [
|
|
|
1498
1577
|
'info' => 'bg-info-muted text-info-muted-foreground border border-transparent',
|
|
1499
1578
|
'outline' => 'bg-transparent text-foreground border border-border',
|
|
1500
1579
|
];
|
|
1501
|
-
|
|
1502
1580
|
$sizes = [
|
|
1503
1581
|
'sm' => 'px-2 py-0.5 text-[11px] gap-1',
|
|
1504
1582
|
'md' => 'px-2.5 py-1 text-xs gap-1.5',
|
|
1505
1583
|
'lg' => 'px-3 py-1.5 text-sm gap-2',
|
|
1506
1584
|
];
|
|
1507
|
-
|
|
1508
1585
|
$classes = implode(' ', [
|
|
1509
1586
|
'inline-flex items-center gap-1.5 rounded-full font-medium leading-none whitespace-nowrap',
|
|
1510
1587
|
'transition-[color,background-color,border-color,box-shadow,opacity] duration-[var(--duration-fast,150ms)] ease-[var(--easing-standard,cubic-bezier(0.4,0,0.2,1))]',
|
|
@@ -1513,7 +1590,6 @@ $classes = implode(' ', [
|
|
|
1513
1590
|
$sizes[$size] ?? $sizes['md'],
|
|
1514
1591
|
]);
|
|
1515
1592
|
@endphp
|
|
1516
|
-
|
|
1517
1593
|
<span {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="badge">
|
|
1518
1594
|
{{ $slot }}
|
|
1519
1595
|
@if($dismissible)
|
|
@@ -1533,7 +1609,6 @@ $classes = implode(' ', [
|
|
|
1533
1609
|
'fullWidth' => false,
|
|
1534
1610
|
'as' => 'div',
|
|
1535
1611
|
])
|
|
1536
|
-
|
|
1537
1612
|
@php
|
|
1538
1613
|
$variants = [
|
|
1539
1614
|
'default' => 'bg-surface border border-border',
|
|
@@ -1541,50 +1616,42 @@ $variants = [
|
|
|
1541
1616
|
'elevated' => 'bg-surface-raised border border-border-muted shadow-md',
|
|
1542
1617
|
'interactive' => 'bg-surface border border-border transition-[border-color,box-shadow,transform] duration-[var(--duration-normal,200ms)] ease-[var(--easing-standard,cubic-bezier(0.4,0,0.2,1))] hover:border-border-strong hover:shadow-md hover:-translate-y-0.5 active:translate-y-0 active:shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring cursor-pointer',
|
|
1543
1618
|
];
|
|
1544
|
-
|
|
1545
1619
|
$classes = implode(' ', array_filter([
|
|
1546
1620
|
'flex flex-col rounded-md overflow-hidden text-sm text-foreground',
|
|
1547
1621
|
$variants[$variant] ?? $variants['default'],
|
|
1548
1622
|
$fullWidth ? 'w-full' : '',
|
|
1549
1623
|
]));
|
|
1550
1624
|
@endphp
|
|
1551
|
-
|
|
1552
1625
|
<{{ $as }} {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card" data-ds-padding="{{ $padding }}">
|
|
1553
1626
|
{{ $slot }}
|
|
1554
1627
|
</{{ $as }}>
|
|
1555
1628
|
`,
|
|
1556
1629
|
"resources/views/components/ui/card-header.blade.php": `@aware(['padding' => 'compact'])
|
|
1557
|
-
|
|
1558
1630
|
@php
|
|
1559
1631
|
$classes = $padding === 'comfortable'
|
|
1560
1632
|
? 'flex flex-col px-6 pt-6 gap-1.5'
|
|
1561
1633
|
: 'flex flex-col px-[var(--ds-padding-card,16px)] pt-[var(--ds-padding-card,16px)] gap-1';
|
|
1562
1634
|
@endphp
|
|
1563
|
-
|
|
1564
1635
|
<div {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card-header">
|
|
1565
1636
|
{{ $slot }}
|
|
1566
1637
|
</div>
|
|
1567
1638
|
`,
|
|
1568
1639
|
"resources/views/components/ui/card-body.blade.php": `@aware(['padding' => 'compact'])
|
|
1569
|
-
|
|
1570
1640
|
@php
|
|
1571
1641
|
$classes = $padding === 'comfortable'
|
|
1572
1642
|
? 'flex flex-col flex-1 px-6 py-4 gap-4'
|
|
1573
1643
|
: 'flex flex-col flex-1 px-[var(--ds-padding-card,16px)] py-3 gap-[var(--ds-gap-default,0.75rem)]';
|
|
1574
1644
|
@endphp
|
|
1575
|
-
|
|
1576
1645
|
<div {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card-body">
|
|
1577
1646
|
{{ $slot }}
|
|
1578
1647
|
</div>
|
|
1579
1648
|
`,
|
|
1580
1649
|
"resources/views/components/ui/card-footer.blade.php": `@aware(['padding' => 'compact'])
|
|
1581
|
-
|
|
1582
1650
|
@php
|
|
1583
1651
|
$classes = $padding === 'comfortable'
|
|
1584
1652
|
? 'flex items-center px-6 pb-6 gap-3'
|
|
1585
1653
|
: 'flex items-center px-[var(--ds-padding-card,16px)] pb-[var(--ds-padding-card,16px)] gap-2';
|
|
1586
1654
|
@endphp
|
|
1587
|
-
|
|
1588
1655
|
<div {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card-footer">
|
|
1589
1656
|
{{ $slot }}
|
|
1590
1657
|
</div>
|
|
@@ -1594,20 +1661,17 @@ $classes = $padding === 'comfortable'
|
|
|
1594
1661
|
'size' => 'md',
|
|
1595
1662
|
'disabled' => false,
|
|
1596
1663
|
])
|
|
1597
|
-
|
|
1598
1664
|
@php
|
|
1599
1665
|
$variants = [
|
|
1600
1666
|
'default' => 'border-input hover:border-border-strong focus-visible:border-border-strong',
|
|
1601
1667
|
'error' => 'border-danger text-foreground focus-visible:border-danger placeholder:text-input-placeholder',
|
|
1602
1668
|
'success' => 'border-success text-foreground focus-visible:border-success placeholder:text-input-placeholder',
|
|
1603
1669
|
];
|
|
1604
|
-
|
|
1605
1670
|
$sizes = [
|
|
1606
1671
|
'sm' => 'h-8 px-2.5 text-xs',
|
|
1607
1672
|
'md' => 'h-[var(--ds-control-height,36px)] px-3 text-sm',
|
|
1608
1673
|
'lg' => 'h-10 px-3.5 text-sm',
|
|
1609
1674
|
];
|
|
1610
|
-
|
|
1611
1675
|
$classes = implode(' ', [
|
|
1612
1676
|
'flex w-full text-sm leading-5 rounded-md border bg-background text-input-foreground',
|
|
1613
1677
|
'placeholder:text-input-placeholder',
|
|
@@ -1619,7 +1683,6 @@ $classes = implode(' ', [
|
|
|
1619
1683
|
$sizes[$size] ?? $sizes['md'],
|
|
1620
1684
|
]);
|
|
1621
1685
|
@endphp
|
|
1622
|
-
|
|
1623
1686
|
<input {{ $attributes->merge(['class' => $classes, 'disabled' => $disabled, 'type' => 'text']) }} data-ds data-ds-component="input" />
|
|
1624
1687
|
`,
|
|
1625
1688
|
"resources/views/components/ui/alert.blade.php": `@props([
|
|
@@ -1627,7 +1690,6 @@ $classes = implode(' ', [
|
|
|
1627
1690
|
'title' => null,
|
|
1628
1691
|
'dismissible' => false,
|
|
1629
1692
|
])
|
|
1630
|
-
|
|
1631
1693
|
@php
|
|
1632
1694
|
$variants = [
|
|
1633
1695
|
'info' => 'bg-info-muted text-info-muted-foreground border-info/20',
|
|
@@ -1636,7 +1698,6 @@ $variants = [
|
|
|
1636
1698
|
'danger' => 'bg-danger-muted text-danger-muted-foreground border-danger/20',
|
|
1637
1699
|
'default' => 'bg-muted text-muted-foreground border-border',
|
|
1638
1700
|
];
|
|
1639
|
-
|
|
1640
1701
|
$iconColors = [
|
|
1641
1702
|
'info' => 'text-info',
|
|
1642
1703
|
'success' => 'text-success',
|
|
@@ -1644,7 +1705,6 @@ $iconColors = [
|
|
|
1644
1705
|
'danger' => 'text-danger',
|
|
1645
1706
|
'default' => 'text-muted-foreground',
|
|
1646
1707
|
];
|
|
1647
|
-
|
|
1648
1708
|
$iconPaths = [
|
|
1649
1709
|
'info' => 'M12 16v-4m0-4h.01M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10z',
|
|
1650
1710
|
'success' => 'M9 12l2 2 4-4m6 2a10 10 0 11-20 0 10 10 0 0120 0z',
|
|
@@ -1652,14 +1712,12 @@ $iconPaths = [
|
|
|
1652
1712
|
'danger' => 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a10 10 0 11-20 0 10 10 0 0120 0z',
|
|
1653
1713
|
'default' => 'M12 16v-4m0-4h.01M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10z',
|
|
1654
1714
|
];
|
|
1655
|
-
|
|
1656
1715
|
$classes = implode(' ', [
|
|
1657
1716
|
'relative flex gap-3 rounded-md p-4 text-sm leading-5 border',
|
|
1658
1717
|
'transition-colors duration-[var(--duration-fast,150ms)]',
|
|
1659
1718
|
$variants[$variant] ?? $variants['info'],
|
|
1660
1719
|
]);
|
|
1661
1720
|
@endphp
|
|
1662
|
-
|
|
1663
1721
|
<div {{ $attributes->merge(['class' => $classes]) }} role="alert" data-ds data-ds-component="alert">
|
|
1664
1722
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4 shrink-0 mt-0.5 {{ $iconColors[$variant] ?? $iconColors['info'] }}">
|
|
1665
1723
|
<path d="{{ $iconPaths[$variant] ?? $iconPaths['info'] }}"/>
|
|
@@ -1685,7 +1743,6 @@ $classes = implode(' ', [
|
|
|
1685
1743
|
'level' => 1,
|
|
1686
1744
|
'color' => 'default',
|
|
1687
1745
|
])
|
|
1688
|
-
|
|
1689
1746
|
@php
|
|
1690
1747
|
$levels = [
|
|
1691
1748
|
1 => 'text-[30px] leading-[36px] font-bold tracking-tight',
|
|
@@ -1693,22 +1750,18 @@ $levels = [
|
|
|
1693
1750
|
3 => 'text-[20px] leading-[28px] font-semibold tracking-normal',
|
|
1694
1751
|
4 => 'text-[18px] leading-[28px] font-medium tracking-normal',
|
|
1695
1752
|
];
|
|
1696
|
-
|
|
1697
1753
|
$colors = [
|
|
1698
1754
|
'default' => 'text-foreground',
|
|
1699
1755
|
'foreground' => 'text-foreground',
|
|
1700
1756
|
'muted' => 'text-muted-foreground',
|
|
1701
1757
|
'primary' => 'text-primary',
|
|
1702
1758
|
];
|
|
1703
|
-
|
|
1704
1759
|
$classes = implode(' ', [
|
|
1705
1760
|
$levels[$level] ?? $levels[1],
|
|
1706
1761
|
$colors[$color] ?? $colors['default'],
|
|
1707
1762
|
]);
|
|
1708
|
-
|
|
1709
1763
|
$tag = 'h' . min(max((int)$level, 1), 6);
|
|
1710
1764
|
@endphp
|
|
1711
|
-
|
|
1712
1765
|
<{{ $tag }} {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="heading">
|
|
1713
1766
|
{{ $slot }}
|
|
1714
1767
|
</{{ $tag }}>
|
|
@@ -1718,7 +1771,6 @@ $tag = 'h' . min(max((int)$level, 1), 6);
|
|
|
1718
1771
|
'color' => 'default',
|
|
1719
1772
|
'as' => 'p',
|
|
1720
1773
|
])
|
|
1721
|
-
|
|
1722
1774
|
@php
|
|
1723
1775
|
$variants = [
|
|
1724
1776
|
'body' => 'text-[16px] leading-[24px] font-normal tracking-normal',
|
|
@@ -1728,7 +1780,6 @@ $variants = [
|
|
|
1728
1780
|
'overline' => 'text-[12px] leading-[16px] font-semibold tracking-wider uppercase text-muted-foreground',
|
|
1729
1781
|
'code' => 'text-[14px] leading-[20px] font-normal tracking-normal font-mono',
|
|
1730
1782
|
];
|
|
1731
|
-
|
|
1732
1783
|
$colors = [
|
|
1733
1784
|
'default' => 'text-foreground',
|
|
1734
1785
|
'foreground' => 'text-foreground',
|
|
@@ -1739,13 +1790,11 @@ $colors = [
|
|
|
1739
1790
|
'danger' => 'text-danger',
|
|
1740
1791
|
'info' => 'text-info',
|
|
1741
1792
|
];
|
|
1742
|
-
|
|
1743
1793
|
$classes = implode(' ', [
|
|
1744
1794
|
$variants[$variant] ?? $variants['body'],
|
|
1745
1795
|
$colors[$color] ?? $colors['default'],
|
|
1746
1796
|
]);
|
|
1747
1797
|
@endphp
|
|
1748
|
-
|
|
1749
1798
|
<{{ $as }} {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="text">
|
|
1750
1799
|
{{ $slot }}
|
|
1751
1800
|
</{{ $as }}>
|
|
@@ -1753,20 +1802,17 @@ $classes = implode(' ', [
|
|
|
1753
1802
|
},
|
|
1754
1803
|
},
|
|
1755
1804
|
};
|
|
1756
|
-
|
|
1757
1805
|
// ---------------------------------------------------------------------------
|
|
1758
1806
|
// Starter kit scaffolding command
|
|
1759
1807
|
// ---------------------------------------------------------------------------
|
|
1760
|
-
|
|
1761
1808
|
async function cmdInitWithTemplate(positional, flags) {
|
|
1762
1809
|
log();
|
|
1763
1810
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Create a new project")}`);
|
|
1764
1811
|
log();
|
|
1765
|
-
|
|
1766
1812
|
// 1. Pick framework
|
|
1767
1813
|
let framework;
|
|
1768
|
-
const templateFlag =
|
|
1769
|
-
|
|
1814
|
+
const templateFlag =
|
|
1815
|
+
typeof flags.template === "string" ? flags.template : null;
|
|
1770
1816
|
if (templateFlag) {
|
|
1771
1817
|
framework = FRAMEWORKS.find((f) => f.name === templateFlag);
|
|
1772
1818
|
if (!framework) {
|
|
@@ -1788,27 +1834,21 @@ async function cmdInitWithTemplate(positional, flags) {
|
|
|
1788
1834
|
}
|
|
1789
1835
|
log();
|
|
1790
1836
|
}
|
|
1791
|
-
|
|
1792
1837
|
logStep("✓", `Framework: ${c("cyan", framework.label)}`);
|
|
1793
|
-
|
|
1794
1838
|
// 2. Get project name
|
|
1795
1839
|
let projectName = positional[0];
|
|
1796
1840
|
if (!projectName) {
|
|
1797
1841
|
projectName = await promptText("Project name:", "my-unified-app");
|
|
1798
1842
|
}
|
|
1799
|
-
|
|
1800
1843
|
const targetDir = resolve(process.cwd(), projectName);
|
|
1801
1844
|
logStep("✓", `Project: ${c("cyan", projectName)}`);
|
|
1802
1845
|
log();
|
|
1803
|
-
|
|
1804
1846
|
// 3. Run the official scaffolding command
|
|
1805
1847
|
logStep("📦", `Scaffolding ${c("cyan", framework.label)} project...`);
|
|
1806
1848
|
log();
|
|
1807
|
-
|
|
1808
1849
|
const scaffoldCmd = framework.scaffoldCmd(projectName);
|
|
1809
1850
|
logStep(" ", c("dim", `> ${scaffoldCmd}`));
|
|
1810
1851
|
log();
|
|
1811
|
-
|
|
1812
1852
|
const scaffoldOk = runCmd(scaffoldCmd, process.cwd());
|
|
1813
1853
|
if (!scaffoldOk) {
|
|
1814
1854
|
logError(
|
|
@@ -1819,40 +1859,35 @@ async function cmdInitWithTemplate(positional, flags) {
|
|
|
1819
1859
|
);
|
|
1820
1860
|
process.exit(1);
|
|
1821
1861
|
}
|
|
1822
|
-
|
|
1823
1862
|
if (!existsSync(targetDir)) {
|
|
1824
|
-
logError(
|
|
1863
|
+
logError(
|
|
1864
|
+
`Expected directory "${projectName}" was not created by the scaffolding tool.`,
|
|
1865
|
+
);
|
|
1825
1866
|
process.exit(1);
|
|
1826
1867
|
}
|
|
1827
|
-
|
|
1828
1868
|
log();
|
|
1829
1869
|
logStep("✓", c("green", `${framework.label} project scaffolded`));
|
|
1830
|
-
|
|
1831
1870
|
// 4. Install Unified UI + extra deps
|
|
1832
1871
|
logStep("📦", "Installing Unified UI design system...");
|
|
1833
|
-
|
|
1834
1872
|
const pm = await detectPackageManager(targetDir);
|
|
1835
1873
|
const allDeps = [...framework.deps];
|
|
1836
1874
|
const allDevDeps = [...framework.devDeps];
|
|
1837
|
-
|
|
1838
1875
|
if (allDeps.length > 0) {
|
|
1839
1876
|
const depCmd = getInstallCommand(pm, allDeps);
|
|
1840
1877
|
logStep(" ", c("dim", depCmd));
|
|
1841
1878
|
runCmd(depCmd, targetDir, "pipe");
|
|
1842
1879
|
}
|
|
1843
|
-
|
|
1844
1880
|
if (allDevDeps.length > 0) {
|
|
1845
|
-
const devDepCmd = getInstallCommand(pm, allDevDeps)
|
|
1881
|
+
const devDepCmd = getInstallCommand(pm, allDevDeps)
|
|
1882
|
+
.replace(" add ", " add -D ")
|
|
1883
|
+
.replace(" install ", " install -D ");
|
|
1846
1884
|
logStep(" ", c("dim", devDepCmd));
|
|
1847
1885
|
runCmd(devDepCmd, targetDir, "pipe");
|
|
1848
1886
|
}
|
|
1849
|
-
|
|
1850
1887
|
logStep("✓", c("green", "Dependencies installed"));
|
|
1851
|
-
|
|
1852
1888
|
// 5. Apply overlay files
|
|
1853
1889
|
log();
|
|
1854
1890
|
logStep("✏️ ", "Applying Unified UI starter files...");
|
|
1855
|
-
|
|
1856
1891
|
const overlay = OVERLAYS[framework.name];
|
|
1857
1892
|
if (overlay) {
|
|
1858
1893
|
for (const [filePath, content] of Object.entries(overlay.files)) {
|
|
@@ -1861,22 +1896,28 @@ async function cmdInitWithTemplate(positional, flags) {
|
|
|
1861
1896
|
logStep("✓", c("green", filePath));
|
|
1862
1897
|
}
|
|
1863
1898
|
}
|
|
1864
|
-
|
|
1865
1899
|
// 6. Git commit (if git was initialized by the scaffold tool)
|
|
1866
1900
|
const gitDir = join(targetDir, ".git");
|
|
1867
1901
|
if (existsSync(gitDir)) {
|
|
1868
1902
|
runCmd("git add -A", targetDir, "pipe");
|
|
1869
|
-
runCmd(
|
|
1903
|
+
runCmd(
|
|
1904
|
+
'git commit -m "chore: add Unified UI design system" --no-verify',
|
|
1905
|
+
targetDir,
|
|
1906
|
+
"pipe",
|
|
1907
|
+
);
|
|
1870
1908
|
logStep("✓", c("green", "Committed Unified UI changes"));
|
|
1871
1909
|
} else {
|
|
1872
1910
|
// Initialize git if it wasn't done by the scaffold tool
|
|
1873
1911
|
if (runCmd("git init", targetDir, "pipe")) {
|
|
1874
1912
|
runCmd("git add -A", targetDir, "pipe");
|
|
1875
|
-
runCmd(
|
|
1913
|
+
runCmd(
|
|
1914
|
+
'git commit -m "chore: initial commit with Unified UI" --no-verify',
|
|
1915
|
+
targetDir,
|
|
1916
|
+
"pipe",
|
|
1917
|
+
);
|
|
1876
1918
|
logStep("✓", c("green", "Initialized git repository"));
|
|
1877
1919
|
}
|
|
1878
1920
|
}
|
|
1879
|
-
|
|
1880
1921
|
// 7. Print success
|
|
1881
1922
|
log();
|
|
1882
1923
|
logStep("🎉", c("green", `Project "${projectName}" is ready!`));
|
|
@@ -1884,40 +1925,39 @@ async function cmdInitWithTemplate(positional, flags) {
|
|
|
1884
1925
|
log(` ${c("dim", "Next steps:")}`);
|
|
1885
1926
|
log();
|
|
1886
1927
|
log(` ${c("cyan", `cd ${projectName}`)}`);
|
|
1887
|
-
|
|
1888
1928
|
if (framework.name === "laravel-blade") {
|
|
1889
1929
|
log(` ${c("cyan", "npm run dev")}`);
|
|
1890
1930
|
log(` ${c("cyan", "php artisan serve")}`);
|
|
1891
1931
|
} else {
|
|
1892
1932
|
log(` ${c("cyan", "npm run dev")}`);
|
|
1893
1933
|
}
|
|
1894
|
-
|
|
1895
1934
|
log();
|
|
1896
|
-
|
|
1897
1935
|
if (framework.name === "vuejs" || framework.name === "laravel-blade") {
|
|
1898
|
-
log(
|
|
1899
|
-
|
|
1936
|
+
log(
|
|
1937
|
+
` ${c("dim", "Note: This template includes design tokens (CSS variables + Tailwind")}`,
|
|
1938
|
+
);
|
|
1939
|
+
log(
|
|
1940
|
+
` ${c("dim", "utilities) only. React components are not available in this framework.")}`,
|
|
1941
|
+
);
|
|
1900
1942
|
log(` ${c("dim", "See: https://www.unified-ui.space/docs/tokens")}`);
|
|
1901
1943
|
log();
|
|
1902
1944
|
} else {
|
|
1903
1945
|
log(` ${c("dim", "Start adding components:")}`);
|
|
1904
|
-
log(
|
|
1946
|
+
log(
|
|
1947
|
+
` ${c("cyan", "npx @work-rjkashyap/unified-ui add button card badge")}`,
|
|
1948
|
+
);
|
|
1905
1949
|
log();
|
|
1906
1950
|
}
|
|
1907
1951
|
}
|
|
1908
|
-
|
|
1909
1952
|
async function cmdInit(positional = [], flags = {}) {
|
|
1910
1953
|
// If --template flag is present, run the full scaffolding flow
|
|
1911
1954
|
if (flags.template) {
|
|
1912
1955
|
return cmdInitWithTemplate(positional, flags);
|
|
1913
1956
|
}
|
|
1914
|
-
|
|
1915
1957
|
log();
|
|
1916
1958
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Initialize project")}`);
|
|
1917
1959
|
log();
|
|
1918
|
-
|
|
1919
1960
|
const config = loadConfig();
|
|
1920
|
-
|
|
1921
1961
|
if (existsSync(join(config.root, CONFIG_FILE))) {
|
|
1922
1962
|
const overwrite = await confirm(
|
|
1923
1963
|
`${c("yellow", CONFIG_FILE)} already exists. Overwrite?`,
|
|
@@ -1928,10 +1968,8 @@ async function cmdInit(positional = [], flags = {}) {
|
|
|
1928
1968
|
return;
|
|
1929
1969
|
}
|
|
1930
1970
|
}
|
|
1931
|
-
|
|
1932
1971
|
saveConfig(DEFAULT_CONFIG);
|
|
1933
1972
|
logStep("✓", `Created ${c("cyan", CONFIG_FILE)}`);
|
|
1934
|
-
|
|
1935
1973
|
// Create directories
|
|
1936
1974
|
const srcDir = join(config.root, config.srcDir);
|
|
1937
1975
|
const dirs = [
|
|
@@ -1939,16 +1977,13 @@ async function cmdInit(positional = [], flags = {}) {
|
|
|
1939
1977
|
join(srcDir, "lib"),
|
|
1940
1978
|
join(srcDir, "styles"),
|
|
1941
1979
|
];
|
|
1942
|
-
|
|
1943
1980
|
for (const dir of dirs) {
|
|
1944
1981
|
mkdirSync(dir, { recursive: true });
|
|
1945
|
-
logStep("✓", `Created ${c("dim", dir.replace(config.root
|
|
1982
|
+
logStep("✓", `Created ${c("dim", dir.replace(`${config.root}/`, ""))}`);
|
|
1946
1983
|
}
|
|
1947
|
-
|
|
1948
1984
|
// Fetch and write the base utilities (cn + focus-ring) and styles
|
|
1949
1985
|
log();
|
|
1950
1986
|
logStep("📡", "Fetching base utilities from registry...");
|
|
1951
|
-
|
|
1952
1987
|
const baseItems = ["cn", "focus-ring", "styles"];
|
|
1953
1988
|
for (const name of baseItems) {
|
|
1954
1989
|
try {
|
|
@@ -1962,28 +1997,28 @@ async function cmdInit(positional = [], flags = {}) {
|
|
|
1962
1997
|
logStep("⚠", c("yellow", `Could not fetch ${name} — add manually later`));
|
|
1963
1998
|
}
|
|
1964
1999
|
}
|
|
1965
|
-
|
|
1966
2000
|
// Install base npm deps
|
|
1967
2001
|
await installNpmDeps(
|
|
1968
|
-
["class-variance-authority", "clsx", "tailwind-merge"],
|
|
2002
|
+
["class-variance-authority", "clsx", "tailwind-merge", "lucide-react"],
|
|
1969
2003
|
config.root,
|
|
1970
2004
|
);
|
|
1971
|
-
|
|
2005
|
+
// Set up Tailwind CSS for Vite projects
|
|
2006
|
+
log();
|
|
2007
|
+
await setupTailwindForVite(config.root, config);
|
|
1972
2008
|
log();
|
|
1973
2009
|
logStep("🎉", c("green", "Project initialized! Start adding components:"));
|
|
1974
2010
|
log();
|
|
1975
2011
|
log(` ${c("cyan", "npx @work-rjkashyap/unified-ui add button")}`);
|
|
1976
|
-
log(
|
|
2012
|
+
log(
|
|
2013
|
+
` ${c("cyan", "npx @work-rjkashyap/unified-ui add card badge tabs")}`,
|
|
2014
|
+
);
|
|
1977
2015
|
log();
|
|
1978
2016
|
}
|
|
1979
|
-
|
|
1980
2017
|
async function cmdAdd(names, flags = {}) {
|
|
1981
2018
|
log();
|
|
1982
2019
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Add components")}`);
|
|
1983
2020
|
log();
|
|
1984
|
-
|
|
1985
2021
|
const config = loadConfig();
|
|
1986
|
-
|
|
1987
2022
|
if (!existsSync(join(config.root, CONFIG_FILE)) && !flags.yes) {
|
|
1988
2023
|
const init = await confirm(
|
|
1989
2024
|
`No ${c("cyan", CONFIG_FILE)} found. Initialize first?`,
|
|
@@ -1993,7 +2028,6 @@ async function cmdAdd(names, flags = {}) {
|
|
|
1993
2028
|
log();
|
|
1994
2029
|
}
|
|
1995
2030
|
}
|
|
1996
|
-
|
|
1997
2031
|
// If --all, fetch index and add everything
|
|
1998
2032
|
if (flags.all) {
|
|
1999
2033
|
logStep("📡", "Fetching full registry index...");
|
|
@@ -2008,27 +2042,27 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2008
2042
|
return;
|
|
2009
2043
|
}
|
|
2010
2044
|
}
|
|
2011
|
-
|
|
2012
2045
|
if (names.length === 0) {
|
|
2013
|
-
logError(
|
|
2046
|
+
logError(
|
|
2047
|
+
"No component names specified. Usage: npx @work-rjkashyap/unified-ui add <component...>",
|
|
2048
|
+
);
|
|
2014
2049
|
return;
|
|
2015
2050
|
}
|
|
2016
|
-
|
|
2017
2051
|
// Resolve the full dependency tree
|
|
2018
|
-
logStep(
|
|
2052
|
+
logStep(
|
|
2053
|
+
"🔍",
|
|
2054
|
+
`Resolving dependencies for: ${c("cyan", names.join(", "))}...`,
|
|
2055
|
+
);
|
|
2019
2056
|
const tree = await resolveFullDependencyTree(names, REGISTRY_BASE_URL);
|
|
2020
|
-
|
|
2021
2057
|
if (tree.size === 0) {
|
|
2022
2058
|
logError("No components resolved. Check the names and try again.");
|
|
2023
2059
|
return;
|
|
2024
2060
|
}
|
|
2025
|
-
|
|
2026
2061
|
// Summarize what will be installed
|
|
2027
2062
|
const components = [];
|
|
2028
2063
|
const utils = [];
|
|
2029
2064
|
const styles = [];
|
|
2030
2065
|
const allNpmDeps = new Set();
|
|
2031
|
-
|
|
2032
2066
|
for (const [name, item] of tree) {
|
|
2033
2067
|
switch (item.type) {
|
|
2034
2068
|
case "unified-ui:component":
|
|
@@ -2041,12 +2075,10 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2041
2075
|
styles.push(name);
|
|
2042
2076
|
break;
|
|
2043
2077
|
}
|
|
2044
|
-
|
|
2045
2078
|
for (const dep of item.dependencies || []) {
|
|
2046
2079
|
allNpmDeps.add(dep);
|
|
2047
2080
|
}
|
|
2048
2081
|
}
|
|
2049
|
-
|
|
2050
2082
|
log();
|
|
2051
2083
|
if (components.length > 0) {
|
|
2052
2084
|
logStep("🧩", `Components: ${c("cyan", components.join(", "))}`);
|
|
@@ -2058,7 +2090,6 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2058
2090
|
logStep("📦", `Packages: ${c("dim", [...allNpmDeps].join(", "))}`);
|
|
2059
2091
|
}
|
|
2060
2092
|
log();
|
|
2061
|
-
|
|
2062
2093
|
// Confirm unless --yes
|
|
2063
2094
|
if (!flags.yes) {
|
|
2064
2095
|
const proceed = await confirm("Proceed with installation?");
|
|
@@ -2069,11 +2100,9 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2069
2100
|
}
|
|
2070
2101
|
log();
|
|
2071
2102
|
}
|
|
2072
|
-
|
|
2073
2103
|
// Write files
|
|
2074
2104
|
const results = [];
|
|
2075
2105
|
const overwrite = flags.overwrite || false;
|
|
2076
|
-
|
|
2077
2106
|
for (const [_name, item] of tree) {
|
|
2078
2107
|
for (const file of item.files) {
|
|
2079
2108
|
const targetPath = resolveTargetPath(config, file);
|
|
@@ -2081,30 +2110,23 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2081
2110
|
results.push(result);
|
|
2082
2111
|
}
|
|
2083
2112
|
}
|
|
2084
|
-
|
|
2085
2113
|
// Report file results
|
|
2086
2114
|
const created = results.filter((r) => r.status === "created");
|
|
2087
2115
|
const skipped = results.filter((r) => r.status === "skipped");
|
|
2088
|
-
|
|
2089
2116
|
for (const r of created) {
|
|
2090
|
-
logStep("✓", c("green", r.path.replace(config.root
|
|
2117
|
+
logStep("✓", c("green", r.path.replace(`${config.root}/`, "")));
|
|
2091
2118
|
}
|
|
2092
|
-
|
|
2093
2119
|
if (skipped.length > 0) {
|
|
2094
2120
|
log();
|
|
2095
2121
|
for (const r of skipped) {
|
|
2096
2122
|
logStep(
|
|
2097
2123
|
"↩",
|
|
2098
|
-
`${c("dim", r.path.replace(config.root
|
|
2124
|
+
`${c("dim", r.path.replace(`${config.root}/`, ""))} ${c("yellow", "(exists, skipped)")}`,
|
|
2099
2125
|
);
|
|
2100
2126
|
}
|
|
2101
2127
|
log();
|
|
2102
|
-
logStep(
|
|
2103
|
-
"💡",
|
|
2104
|
-
c("dim", "Use --overwrite to replace existing files."),
|
|
2105
|
-
);
|
|
2128
|
+
logStep("💡", c("dim", "Use --overwrite to replace existing files."));
|
|
2106
2129
|
}
|
|
2107
|
-
|
|
2108
2130
|
// Install npm dependencies
|
|
2109
2131
|
const depsToInstall = [...allNpmDeps].filter((dep) => {
|
|
2110
2132
|
// Check if already in package.json
|
|
@@ -2122,28 +2144,20 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2122
2144
|
return true;
|
|
2123
2145
|
}
|
|
2124
2146
|
});
|
|
2125
|
-
|
|
2126
2147
|
if (depsToInstall.length > 0) {
|
|
2127
2148
|
log();
|
|
2128
2149
|
await installNpmDeps(depsToInstall, config.root);
|
|
2129
2150
|
}
|
|
2130
|
-
|
|
2131
2151
|
log();
|
|
2132
|
-
logStep(
|
|
2133
|
-
"🎉",
|
|
2134
|
-
c("green", `Done! ${created.length} file(s) added.`),
|
|
2135
|
-
);
|
|
2152
|
+
logStep("🎉", c("green", `Done! ${created.length} file(s) added.`));
|
|
2136
2153
|
log();
|
|
2137
2154
|
}
|
|
2138
|
-
|
|
2139
2155
|
async function cmdList() {
|
|
2140
2156
|
log();
|
|
2141
2157
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Available components")}`);
|
|
2142
2158
|
log();
|
|
2143
|
-
|
|
2144
2159
|
try {
|
|
2145
2160
|
const index = await fetchJSON(`${REGISTRY_BASE_URL}/index.json`);
|
|
2146
|
-
|
|
2147
2161
|
// Group by category
|
|
2148
2162
|
const groups = {};
|
|
2149
2163
|
for (const item of index.items) {
|
|
@@ -2152,13 +2166,11 @@ async function cmdList() {
|
|
|
2152
2166
|
if (!groups[cat]) groups[cat] = [];
|
|
2153
2167
|
groups[cat].push(item);
|
|
2154
2168
|
}
|
|
2155
|
-
|
|
2156
2169
|
// Find category labels
|
|
2157
2170
|
const catLabels = {};
|
|
2158
2171
|
for (const cat of index.categories || []) {
|
|
2159
2172
|
catLabels[cat.name] = cat.label;
|
|
2160
2173
|
}
|
|
2161
|
-
|
|
2162
2174
|
for (const [cat, items] of Object.entries(groups)) {
|
|
2163
2175
|
log(` ${c("bold", catLabels[cat] || cat)}`);
|
|
2164
2176
|
for (const item of items) {
|
|
@@ -2166,27 +2178,27 @@ async function cmdList() {
|
|
|
2166
2178
|
item.registryDependencies?.length > 0
|
|
2167
2179
|
? c("dim", ` → ${item.registryDependencies.join(", ")}`)
|
|
2168
2180
|
: "";
|
|
2169
|
-
log(
|
|
2181
|
+
log(
|
|
2182
|
+
` ${c("cyan", item.name.padEnd(22))} ${c("dim", item.description || "")}${deps}`,
|
|
2183
|
+
);
|
|
2170
2184
|
}
|
|
2171
2185
|
log();
|
|
2172
2186
|
}
|
|
2173
|
-
|
|
2174
2187
|
log(` ${c("dim", `${index.totalItems} items total`)}`);
|
|
2175
2188
|
log();
|
|
2176
|
-
log(
|
|
2189
|
+
log(
|
|
2190
|
+
` ${c("dim", "Add a component:")} npx @work-rjkashyap/unified-ui add ${c("cyan", "<name>")}`,
|
|
2191
|
+
);
|
|
2177
2192
|
log();
|
|
2178
2193
|
} catch (err) {
|
|
2179
2194
|
logError(`Could not fetch registry: ${err.message}`);
|
|
2180
2195
|
}
|
|
2181
2196
|
}
|
|
2182
|
-
|
|
2183
2197
|
async function cmdDiff(names) {
|
|
2184
2198
|
log();
|
|
2185
2199
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Diff local vs registry")}`);
|
|
2186
2200
|
log();
|
|
2187
|
-
|
|
2188
2201
|
const config = loadConfig();
|
|
2189
|
-
|
|
2190
2202
|
for (const name of names) {
|
|
2191
2203
|
try {
|
|
2192
2204
|
const item = await fetchJSON(`${REGISTRY_BASE_URL}/${name}.json`);
|
|
@@ -2196,10 +2208,8 @@ async function cmdDiff(names) {
|
|
|
2196
2208
|
logStep("✗", `${c("red", name)}: not installed locally`);
|
|
2197
2209
|
continue;
|
|
2198
2210
|
}
|
|
2199
|
-
|
|
2200
2211
|
const localContent = readFileSync(targetPath, "utf-8");
|
|
2201
2212
|
const registryContent = rewriteContentImports(file.content, config);
|
|
2202
|
-
|
|
2203
2213
|
if (localContent === registryContent) {
|
|
2204
2214
|
logStep("✓", `${c("green", name)}: up to date`);
|
|
2205
2215
|
} else {
|
|
@@ -2210,37 +2220,65 @@ async function cmdDiff(names) {
|
|
|
2210
2220
|
logStep("✗", `${c("red", name)}: ${err.message}`);
|
|
2211
2221
|
}
|
|
2212
2222
|
}
|
|
2213
|
-
|
|
2214
2223
|
log();
|
|
2215
2224
|
}
|
|
2216
|
-
|
|
2217
2225
|
function cmdHelp() {
|
|
2218
2226
|
log();
|
|
2219
2227
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Component Registry CLI")}`);
|
|
2220
2228
|
log();
|
|
2221
2229
|
log(" Usage:");
|
|
2222
|
-
log(
|
|
2230
|
+
log(
|
|
2231
|
+
` ${c("cyan", "npx @work-rjkashyap/unified-ui")} ${c("green", "<command>")} [options]`,
|
|
2232
|
+
);
|
|
2223
2233
|
log();
|
|
2224
2234
|
log(" Commands:");
|
|
2225
|
-
log(
|
|
2226
|
-
|
|
2227
|
-
|
|
2235
|
+
log(
|
|
2236
|
+
` ${c("green", "init")} Initialize existing project (copy-paste mode)`,
|
|
2237
|
+
);
|
|
2238
|
+
log(
|
|
2239
|
+
` ${c("green", "init")} -t <framework> Scaffold a new project with full setup`,
|
|
2240
|
+
);
|
|
2241
|
+
log(
|
|
2242
|
+
` ${c("green", "add")} <component...> Add component(s) with dependencies`,
|
|
2243
|
+
);
|
|
2228
2244
|
log(` ${c("green", "add")} --all Add all components`);
|
|
2229
|
-
log(
|
|
2230
|
-
|
|
2231
|
-
|
|
2245
|
+
log(
|
|
2246
|
+
` ${c("green", "list")} List all available components`,
|
|
2247
|
+
);
|
|
2248
|
+
log(
|
|
2249
|
+
` ${c("green", "diff")} <component...> Compare local files with registry`,
|
|
2250
|
+
);
|
|
2251
|
+
log(
|
|
2252
|
+
` ${c("green", "help")} Show this help message`,
|
|
2253
|
+
);
|
|
2232
2254
|
log();
|
|
2233
2255
|
log(" Templates (for init -t):");
|
|
2234
|
-
log(
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
log(
|
|
2256
|
+
log(
|
|
2257
|
+
` ${c("cyan", "vite-react")} Vite + React 19 SPA with full component library`,
|
|
2258
|
+
);
|
|
2259
|
+
log(
|
|
2260
|
+
` ${c("cyan", "nextjs")} Next.js App Router with SSR + full component library`,
|
|
2261
|
+
);
|
|
2262
|
+
log(
|
|
2263
|
+
` ${c("cyan", "vuejs")} Vue 3 + Vite with UI components & Tailwind theme`,
|
|
2264
|
+
);
|
|
2265
|
+
log(
|
|
2266
|
+
` ${c("cyan", "laravel-blade")} Laravel with Blade UI components & Tailwind theme`,
|
|
2267
|
+
);
|
|
2238
2268
|
log();
|
|
2239
2269
|
log(" Options:");
|
|
2240
|
-
log(
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
log(
|
|
2270
|
+
log(
|
|
2271
|
+
` ${c("yellow", "--template, -t")} Framework template (with 'init')`,
|
|
2272
|
+
);
|
|
2273
|
+
log(
|
|
2274
|
+
` ${c("yellow", "--yes, -y")} Skip confirmation prompts`,
|
|
2275
|
+
);
|
|
2276
|
+
log(
|
|
2277
|
+
` ${c("yellow", "--overwrite")} Overwrite existing files`,
|
|
2278
|
+
);
|
|
2279
|
+
log(
|
|
2280
|
+
` ${c("yellow", "--all")} Add all components (with 'add')`,
|
|
2281
|
+
);
|
|
2244
2282
|
log();
|
|
2245
2283
|
log(" Examples:");
|
|
2246
2284
|
log(` ${c("dim", "# Scaffold a new project (interactive)")} `);
|
|
@@ -2265,17 +2303,14 @@ function cmdHelp() {
|
|
|
2265
2303
|
log(` Registry: ${c("cyan", REGISTRY_BASE_URL)}`);
|
|
2266
2304
|
log();
|
|
2267
2305
|
}
|
|
2268
|
-
|
|
2269
2306
|
// ---------------------------------------------------------------------------
|
|
2270
2307
|
// Argument parsing
|
|
2271
2308
|
// ---------------------------------------------------------------------------
|
|
2272
|
-
|
|
2273
2309
|
function parseArgs(argv) {
|
|
2274
2310
|
const args = argv.slice(2);
|
|
2275
2311
|
const command = args[0];
|
|
2276
2312
|
const flags = {};
|
|
2277
2313
|
const positional = [];
|
|
2278
|
-
|
|
2279
2314
|
for (let i = 1; i < args.length; i++) {
|
|
2280
2315
|
const arg = args[i];
|
|
2281
2316
|
if (arg === "--yes" || arg === "-y") {
|
|
@@ -2303,17 +2338,13 @@ function parseArgs(argv) {
|
|
|
2303
2338
|
positional.push(arg);
|
|
2304
2339
|
}
|
|
2305
2340
|
}
|
|
2306
|
-
|
|
2307
2341
|
return { command, positional, flags };
|
|
2308
2342
|
}
|
|
2309
|
-
|
|
2310
2343
|
// ---------------------------------------------------------------------------
|
|
2311
2344
|
// Main
|
|
2312
2345
|
// ---------------------------------------------------------------------------
|
|
2313
|
-
|
|
2314
2346
|
async function main() {
|
|
2315
2347
|
const { command, positional, flags } = parseArgs(process.argv);
|
|
2316
|
-
|
|
2317
2348
|
// Allow custom registry URL
|
|
2318
2349
|
if (flags.registryUrl) {
|
|
2319
2350
|
// Override the global — we use a let binding workaround below.
|
|
@@ -2321,7 +2352,6 @@ async function main() {
|
|
|
2321
2352
|
// For simplicity, we set an env var that's already checked at the top.
|
|
2322
2353
|
process.env.UNIFIED_UI_REGISTRY_URL = flags.registryUrl;
|
|
2323
2354
|
}
|
|
2324
|
-
|
|
2325
2355
|
switch (command) {
|
|
2326
2356
|
case "init":
|
|
2327
2357
|
await cmdInit(positional, flags);
|
|
@@ -2343,11 +2373,12 @@ async function main() {
|
|
|
2343
2373
|
cmdHelp();
|
|
2344
2374
|
break;
|
|
2345
2375
|
default:
|
|
2346
|
-
logError(
|
|
2376
|
+
logError(
|
|
2377
|
+
`Unknown command: "${command}". Run "npx @work-rjkashyap/unified-ui help" for usage.`,
|
|
2378
|
+
);
|
|
2347
2379
|
process.exit(1);
|
|
2348
2380
|
}
|
|
2349
2381
|
}
|
|
2350
|
-
|
|
2351
2382
|
main().catch((err) => {
|
|
2352
2383
|
logError(err.message);
|
|
2353
2384
|
process.exit(1);
|