@work-rjkashyap/unified-ui 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/bin/cli.mjs +119 -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
|
-
|
|
33
30
|
// ---------------------------------------------------------------------------
|
|
34
31
|
// Config
|
|
35
32
|
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
33
|
const REGISTRY_BASE_URL =
|
|
38
34
|
process.env.UNIFIED_UI_REGISTRY_URL ||
|
|
39
|
-
"https://unified-ui
|
|
40
|
-
|
|
35
|
+
"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,47 @@ 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) {
|
|
369
330
|
logStep(
|
|
370
331
|
"⚠",
|
|
371
332
|
c("yellow", `Auto-install failed. Run manually:\n ${cmd}`),
|
|
372
333
|
);
|
|
373
334
|
}
|
|
374
335
|
}
|
|
375
|
-
|
|
376
336
|
// ---------------------------------------------------------------------------
|
|
377
337
|
// File writer
|
|
378
338
|
// ---------------------------------------------------------------------------
|
|
379
|
-
|
|
380
339
|
function writeFile(targetPath, content, config, overwrite = false) {
|
|
381
340
|
const rewritten = rewriteContentImports(content, config);
|
|
382
|
-
|
|
383
341
|
if (existsSync(targetPath) && !overwrite) {
|
|
384
342
|
return { path: targetPath, status: "skipped" };
|
|
385
343
|
}
|
|
386
|
-
|
|
387
344
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
388
345
|
writeFileSync(targetPath, rewritten);
|
|
389
346
|
return { path: targetPath, status: "created" };
|
|
390
347
|
}
|
|
391
|
-
|
|
392
348
|
// ---------------------------------------------------------------------------
|
|
393
349
|
// Commands
|
|
394
350
|
// ---------------------------------------------------------------------------
|
|
395
|
-
|
|
396
351
|
// ---------------------------------------------------------------------------
|
|
397
352
|
// Starter kit overlays (embedded content)
|
|
398
353
|
// ---------------------------------------------------------------------------
|
|
399
|
-
|
|
400
354
|
const OVERLAYS = {
|
|
401
355
|
"vite-react": {
|
|
402
356
|
files: {
|
|
403
357
|
"vite.config.ts": `import tailwindcss from "@tailwindcss/vite";
|
|
404
358
|
import react from "@vitejs/plugin-react";
|
|
405
359
|
import { defineConfig } from "vite";
|
|
406
|
-
|
|
407
360
|
export default defineConfig({
|
|
408
361
|
plugins: [react(), tailwindcss()],
|
|
409
362
|
resolve: {
|
|
@@ -415,7 +368,6 @@ export default defineConfig({
|
|
|
415
368
|
`,
|
|
416
369
|
"src/index.css": `@import "tailwindcss";
|
|
417
370
|
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
418
|
-
|
|
419
371
|
body {
|
|
420
372
|
min-height: 100svh;
|
|
421
373
|
}
|
|
@@ -425,7 +377,6 @@ import { createRoot } from "react-dom/client";
|
|
|
425
377
|
import { DSThemeProvider } from "@work-rjkashyap/unified-ui/theme";
|
|
426
378
|
import App from "./App";
|
|
427
379
|
import "./index.css";
|
|
428
|
-
|
|
429
380
|
createRoot(document.getElementById("root")!).render(
|
|
430
381
|
<StrictMode>
|
|
431
382
|
<DSThemeProvider manageHtmlClass>
|
|
@@ -437,10 +388,8 @@ createRoot(document.getElementById("root")!).render(
|
|
|
437
388
|
"src/App.tsx": `import { Button } from "@work-rjkashyap/unified-ui/components";
|
|
438
389
|
import { Heading, Body } from "@work-rjkashyap/unified-ui/primitives";
|
|
439
390
|
import { useDSTheme } from "@work-rjkashyap/unified-ui/theme";
|
|
440
|
-
|
|
441
391
|
function App() {
|
|
442
392
|
const { theme, setTheme } = useDSTheme();
|
|
443
|
-
|
|
444
393
|
return (
|
|
445
394
|
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-8 text-foreground">
|
|
446
395
|
<div className="w-full max-w-md space-y-6 rounded-lg border border-border bg-card p-8">
|
|
@@ -450,7 +399,6 @@ function App() {
|
|
|
450
399
|
Your starter project is ready. Start building!
|
|
451
400
|
</Body>
|
|
452
401
|
</div>
|
|
453
|
-
|
|
454
402
|
<div className="flex items-center justify-center gap-3">
|
|
455
403
|
<Button variant="primary">Get Started</Button>
|
|
456
404
|
<Button
|
|
@@ -464,17 +412,14 @@ function App() {
|
|
|
464
412
|
</div>
|
|
465
413
|
);
|
|
466
414
|
}
|
|
467
|
-
|
|
468
415
|
export default App;
|
|
469
416
|
`,
|
|
470
417
|
},
|
|
471
418
|
},
|
|
472
|
-
|
|
473
419
|
nextjs: {
|
|
474
420
|
files: {
|
|
475
421
|
"src/app/globals.css": `@import "tailwindcss";
|
|
476
422
|
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
477
|
-
|
|
478
423
|
body {
|
|
479
424
|
min-height: 100svh;
|
|
480
425
|
}
|
|
@@ -483,12 +428,10 @@ body {
|
|
|
483
428
|
import { ThemeProvider } from "next-themes";
|
|
484
429
|
import { DSThemeProvider } from "@work-rjkashyap/unified-ui/theme";
|
|
485
430
|
import "./globals.css";
|
|
486
|
-
|
|
487
431
|
export const metadata: Metadata = {
|
|
488
432
|
title: "Unified UI App",
|
|
489
433
|
description: "Built with Unified UI and Next.js",
|
|
490
434
|
};
|
|
491
|
-
|
|
492
435
|
export default function RootLayout({
|
|
493
436
|
children,
|
|
494
437
|
}: {
|
|
@@ -511,14 +454,11 @@ export default function RootLayout({
|
|
|
511
454
|
}
|
|
512
455
|
`,
|
|
513
456
|
"src/app/page.tsx": `"use client";
|
|
514
|
-
|
|
515
457
|
import { Button } from "@work-rjkashyap/unified-ui/components";
|
|
516
458
|
import { Heading, Body } from "@work-rjkashyap/unified-ui/primitives";
|
|
517
459
|
import { useDSTheme } from "@work-rjkashyap/unified-ui/theme";
|
|
518
|
-
|
|
519
460
|
export default function Home() {
|
|
520
461
|
const { theme, setTheme } = useDSTheme();
|
|
521
|
-
|
|
522
462
|
return (
|
|
523
463
|
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-8 text-foreground">
|
|
524
464
|
<div className="w-full max-w-md space-y-6 rounded-lg border border-border bg-card p-8">
|
|
@@ -528,7 +468,6 @@ export default function Home() {
|
|
|
528
468
|
Your Next.js project is ready. Start building!
|
|
529
469
|
</Body>
|
|
530
470
|
</div>
|
|
531
|
-
|
|
532
471
|
<div className="flex items-center justify-center gap-3">
|
|
533
472
|
<Button variant="primary">Get Started</Button>
|
|
534
473
|
<Button
|
|
@@ -545,13 +484,11 @@ export default function Home() {
|
|
|
545
484
|
`,
|
|
546
485
|
},
|
|
547
486
|
},
|
|
548
|
-
|
|
549
487
|
vuejs: {
|
|
550
488
|
files: {
|
|
551
489
|
"vite.config.ts": `import tailwindcss from "@tailwindcss/vite";
|
|
552
490
|
import vue from "@vitejs/plugin-vue";
|
|
553
491
|
import { defineConfig } from "vite";
|
|
554
|
-
|
|
555
492
|
export default defineConfig({
|
|
556
493
|
plugins: [vue(), tailwindcss()],
|
|
557
494
|
resolve: {
|
|
@@ -563,7 +500,6 @@ export default defineConfig({
|
|
|
563
500
|
`,
|
|
564
501
|
"src/style.css": `@import "tailwindcss";
|
|
565
502
|
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
566
|
-
|
|
567
503
|
body {
|
|
568
504
|
min-height: 100svh;
|
|
569
505
|
}
|
|
@@ -571,12 +507,10 @@ body {
|
|
|
571
507
|
"src/main.ts": `import { createApp } from "vue";
|
|
572
508
|
import App from "./App.vue";
|
|
573
509
|
import "./style.css";
|
|
574
|
-
|
|
575
510
|
createApp(App).mount("#app");
|
|
576
511
|
`,
|
|
577
512
|
"src/lib/cn.ts": `import { type ClassValue, clsx } from "clsx";
|
|
578
513
|
import { twMerge } from "tailwind-merge";
|
|
579
|
-
|
|
580
514
|
export function cn(...inputs: ClassValue[]) {
|
|
581
515
|
return twMerge(clsx(inputs));
|
|
582
516
|
}
|
|
@@ -596,10 +530,8 @@ import {
|
|
|
596
530
|
UiText,
|
|
597
531
|
} from "./components/ui";
|
|
598
532
|
import { ref } from "vue";
|
|
599
|
-
|
|
600
533
|
const email = ref("");
|
|
601
534
|
</script>
|
|
602
|
-
|
|
603
535
|
<template>
|
|
604
536
|
<div
|
|
605
537
|
class="flex min-h-svh flex-col items-center justify-center gap-8 bg-background p-8 text-foreground"
|
|
@@ -611,7 +543,6 @@ const email = ref("");
|
|
|
611
543
|
Your Vue.js project is ready with components. Start building!
|
|
612
544
|
</UiText>
|
|
613
545
|
</UiCardHeader>
|
|
614
|
-
|
|
615
546
|
<UiCardBody class="space-y-6">
|
|
616
547
|
<!-- Buttons -->
|
|
617
548
|
<div class="space-y-2">
|
|
@@ -624,7 +555,6 @@ const email = ref("");
|
|
|
624
555
|
<UiButton variant="primary" :loading="true" size="sm">Loading</UiButton>
|
|
625
556
|
</div>
|
|
626
557
|
</div>
|
|
627
|
-
|
|
628
558
|
<!-- Badges -->
|
|
629
559
|
<div class="space-y-2">
|
|
630
560
|
<UiText variant="label">Badges</UiText>
|
|
@@ -638,19 +568,16 @@ const email = ref("");
|
|
|
638
568
|
<UiBadge variant="outline">Outline</UiBadge>
|
|
639
569
|
</div>
|
|
640
570
|
</div>
|
|
641
|
-
|
|
642
571
|
<!-- Input -->
|
|
643
572
|
<div class="space-y-2">
|
|
644
573
|
<UiText variant="label">Input</UiText>
|
|
645
574
|
<UiInput v-model="email" placeholder="you@example.com" />
|
|
646
575
|
</div>
|
|
647
|
-
|
|
648
576
|
<!-- Alert -->
|
|
649
577
|
<UiAlert variant="info" title="All set!">
|
|
650
578
|
Your design system components are working in Vue.
|
|
651
579
|
</UiAlert>
|
|
652
580
|
</UiCardBody>
|
|
653
|
-
|
|
654
581
|
<UiCardFooter class="justify-between">
|
|
655
582
|
<UiButton variant="primary">Get Started</UiButton>
|
|
656
583
|
<ThemeToggle />
|
|
@@ -661,9 +588,7 @@ const email = ref("");
|
|
|
661
588
|
`,
|
|
662
589
|
"src/components/ThemeToggle.vue": `<script setup lang="ts">
|
|
663
590
|
import { ref, onMounted } from "vue";
|
|
664
|
-
|
|
665
591
|
const theme = ref<"light" | "dark">("light");
|
|
666
|
-
|
|
667
592
|
onMounted(() => {
|
|
668
593
|
const stored = localStorage.getItem("theme");
|
|
669
594
|
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
@@ -671,18 +596,15 @@ onMounted(() => {
|
|
|
671
596
|
(stored as "light" | "dark") || (prefersDark ? "dark" : "light");
|
|
672
597
|
applyTheme();
|
|
673
598
|
});
|
|
674
|
-
|
|
675
599
|
function toggle() {
|
|
676
600
|
theme.value = theme.value === "dark" ? "light" : "dark";
|
|
677
601
|
applyTheme();
|
|
678
602
|
}
|
|
679
|
-
|
|
680
603
|
function applyTheme() {
|
|
681
604
|
document.documentElement.classList.toggle("dark", theme.value === "dark");
|
|
682
605
|
localStorage.setItem("theme", theme.value);
|
|
683
606
|
}
|
|
684
607
|
</script>
|
|
685
|
-
|
|
686
608
|
<template>
|
|
687
609
|
<button
|
|
688
610
|
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 +628,8 @@ export { default as UiText } from "./Text.vue";
|
|
|
706
628
|
"src/components/ui/Button.vue": `<script setup lang="ts">
|
|
707
629
|
import { computed, type HTMLAttributes } from "vue";
|
|
708
630
|
import { cn } from "@/lib/cn";
|
|
709
|
-
|
|
710
631
|
type Variant = "primary" | "secondary" | "ghost" | "danger";
|
|
711
632
|
type Size = "sm" | "md" | "lg";
|
|
712
|
-
|
|
713
633
|
interface Props {
|
|
714
634
|
variant?: Variant;
|
|
715
635
|
size?: Size;
|
|
@@ -720,7 +640,6 @@ interface Props {
|
|
|
720
640
|
as?: string;
|
|
721
641
|
class?: HTMLAttributes["class"];
|
|
722
642
|
}
|
|
723
|
-
|
|
724
643
|
const props = withDefaults(defineProps<Props>(), {
|
|
725
644
|
variant: "primary",
|
|
726
645
|
size: "md",
|
|
@@ -730,7 +649,6 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
730
649
|
loading: false,
|
|
731
650
|
disabled: false,
|
|
732
651
|
});
|
|
733
|
-
|
|
734
652
|
const variantClasses: Record<Variant, string> = {
|
|
735
653
|
primary:
|
|
736
654
|
"bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active",
|
|
@@ -741,19 +659,16 @@ const variantClasses: Record<Variant, string> = {
|
|
|
741
659
|
danger:
|
|
742
660
|
"bg-danger text-danger-foreground hover:bg-danger-hover active:bg-danger-active",
|
|
743
661
|
};
|
|
744
|
-
|
|
745
662
|
const sizeClasses: Record<Size, string> = {
|
|
746
663
|
sm: "h-8 px-3 text-xs gap-1.5",
|
|
747
664
|
md: "h-[var(--ds-control-height,36px)] px-[var(--ds-padding-button-x,16px)] text-sm gap-2",
|
|
748
665
|
lg: "h-10 px-5 text-sm gap-2",
|
|
749
666
|
};
|
|
750
|
-
|
|
751
667
|
const iconOnlySizeClasses: Record<Size, string> = {
|
|
752
668
|
sm: "w-8 !px-0",
|
|
753
669
|
md: "w-9 !px-0",
|
|
754
670
|
lg: "w-10 !px-0",
|
|
755
671
|
};
|
|
756
|
-
|
|
757
672
|
const classes = computed(() =>
|
|
758
673
|
cn(
|
|
759
674
|
// base
|
|
@@ -776,7 +691,6 @@ const classes = computed(() =>
|
|
|
776
691
|
),
|
|
777
692
|
);
|
|
778
693
|
</script>
|
|
779
|
-
|
|
780
694
|
<template>
|
|
781
695
|
<component
|
|
782
696
|
:is="as"
|
|
@@ -814,7 +728,6 @@ const classes = computed(() =>
|
|
|
814
728
|
"src/components/ui/Badge.vue": `<script setup lang="ts">
|
|
815
729
|
import { computed, type HTMLAttributes } from "vue";
|
|
816
730
|
import { cn } from "@/lib/cn";
|
|
817
|
-
|
|
818
731
|
type Variant =
|
|
819
732
|
| "default"
|
|
820
733
|
| "primary"
|
|
@@ -825,22 +738,18 @@ type Variant =
|
|
|
825
738
|
| "info"
|
|
826
739
|
| "outline";
|
|
827
740
|
type Size = "sm" | "md" | "lg";
|
|
828
|
-
|
|
829
741
|
interface Props {
|
|
830
742
|
variant?: Variant;
|
|
831
743
|
size?: Size;
|
|
832
744
|
dismissible?: boolean;
|
|
833
745
|
class?: HTMLAttributes["class"];
|
|
834
746
|
}
|
|
835
|
-
|
|
836
747
|
const props = withDefaults(defineProps<Props>(), {
|
|
837
748
|
variant: "default",
|
|
838
749
|
size: "md",
|
|
839
750
|
dismissible: false,
|
|
840
751
|
});
|
|
841
|
-
|
|
842
752
|
const emit = defineEmits<{ dismiss: [] }>();
|
|
843
|
-
|
|
844
753
|
const variantClasses: Record<Variant, string> = {
|
|
845
754
|
default: "bg-muted text-foreground border border-transparent",
|
|
846
755
|
primary:
|
|
@@ -855,13 +764,11 @@ const variantClasses: Record<Variant, string> = {
|
|
|
855
764
|
info: "bg-info-muted text-info-muted-foreground border border-transparent",
|
|
856
765
|
outline: "bg-transparent text-foreground border border-border",
|
|
857
766
|
};
|
|
858
|
-
|
|
859
767
|
const sizeClasses: Record<Size, string> = {
|
|
860
768
|
sm: "px-2 py-0.5 text-[11px] gap-1",
|
|
861
769
|
md: "px-2.5 py-1 text-xs gap-1.5",
|
|
862
770
|
lg: "px-3 py-1.5 text-sm gap-2",
|
|
863
771
|
};
|
|
864
|
-
|
|
865
772
|
const classes = computed(() =>
|
|
866
773
|
cn(
|
|
867
774
|
"inline-flex items-center gap-1.5 rounded-full font-medium leading-none whitespace-nowrap",
|
|
@@ -873,7 +780,6 @@ const classes = computed(() =>
|
|
|
873
780
|
),
|
|
874
781
|
);
|
|
875
782
|
</script>
|
|
876
|
-
|
|
877
783
|
<template>
|
|
878
784
|
<span :class="classes" data-ds data-ds-component="badge">
|
|
879
785
|
<slot />
|
|
@@ -904,10 +810,8 @@ const classes = computed(() =>
|
|
|
904
810
|
"src/components/ui/Card.vue": `<script setup lang="ts">
|
|
905
811
|
import { computed, provide, type HTMLAttributes, type InjectionKey } from "vue";
|
|
906
812
|
import { cn } from "@/lib/cn";
|
|
907
|
-
|
|
908
813
|
type Variant = "default" | "outlined" | "elevated" | "interactive";
|
|
909
814
|
type Padding = "compact" | "comfortable";
|
|
910
|
-
|
|
911
815
|
interface Props {
|
|
912
816
|
variant?: Variant;
|
|
913
817
|
padding?: Padding;
|
|
@@ -915,17 +819,14 @@ interface Props {
|
|
|
915
819
|
as?: string;
|
|
916
820
|
class?: HTMLAttributes["class"];
|
|
917
821
|
}
|
|
918
|
-
|
|
919
822
|
const props = withDefaults(defineProps<Props>(), {
|
|
920
823
|
variant: "default",
|
|
921
824
|
padding: "compact",
|
|
922
825
|
as: "div",
|
|
923
826
|
fullWidth: false,
|
|
924
827
|
});
|
|
925
|
-
|
|
926
828
|
export const cardPaddingKey = Symbol("cardPadding") as InjectionKey<Padding>;
|
|
927
829
|
provide(cardPaddingKey, props.padding);
|
|
928
|
-
|
|
929
830
|
const variantClasses: Record<Variant, string> = {
|
|
930
831
|
default: "bg-surface border border-border",
|
|
931
832
|
outlined: "bg-transparent border border-border-strong",
|
|
@@ -933,7 +834,6 @@ const variantClasses: Record<Variant, string> = {
|
|
|
933
834
|
interactive:
|
|
934
835
|
"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
836
|
};
|
|
936
|
-
|
|
937
837
|
const classes = computed(() =>
|
|
938
838
|
cn(
|
|
939
839
|
"flex flex-col rounded-md overflow-hidden text-sm text-foreground",
|
|
@@ -943,7 +843,6 @@ const classes = computed(() =>
|
|
|
943
843
|
),
|
|
944
844
|
);
|
|
945
845
|
</script>
|
|
946
|
-
|
|
947
846
|
<template>
|
|
948
847
|
<component :is="as" :class="classes" data-ds data-ds-component="card">
|
|
949
848
|
<slot />
|
|
@@ -954,14 +853,11 @@ const classes = computed(() =>
|
|
|
954
853
|
import { inject, computed, type HTMLAttributes } from "vue";
|
|
955
854
|
import { cn } from "@/lib/cn";
|
|
956
855
|
import { cardPaddingKey } from "./Card.vue";
|
|
957
|
-
|
|
958
856
|
interface Props {
|
|
959
857
|
class?: HTMLAttributes["class"];
|
|
960
858
|
}
|
|
961
|
-
|
|
962
859
|
const props = defineProps<Props>();
|
|
963
860
|
const padding = inject(cardPaddingKey, "compact");
|
|
964
|
-
|
|
965
861
|
const classes = computed(() =>
|
|
966
862
|
cn(
|
|
967
863
|
"flex flex-col",
|
|
@@ -970,7 +866,6 @@ const classes = computed(() =>
|
|
|
970
866
|
),
|
|
971
867
|
);
|
|
972
868
|
</script>
|
|
973
|
-
|
|
974
869
|
<template>
|
|
975
870
|
<div :class="classes" data-ds data-ds-component="card-header">
|
|
976
871
|
<slot />
|
|
@@ -981,14 +876,11 @@ const classes = computed(() =>
|
|
|
981
876
|
import { inject, computed, type HTMLAttributes } from "vue";
|
|
982
877
|
import { cn } from "@/lib/cn";
|
|
983
878
|
import { cardPaddingKey } from "./Card.vue";
|
|
984
|
-
|
|
985
879
|
interface Props {
|
|
986
880
|
class?: HTMLAttributes["class"];
|
|
987
881
|
}
|
|
988
|
-
|
|
989
882
|
const props = defineProps<Props>();
|
|
990
883
|
const padding = inject(cardPaddingKey, "compact");
|
|
991
|
-
|
|
992
884
|
const classes = computed(() =>
|
|
993
885
|
cn(
|
|
994
886
|
"flex flex-col flex-1",
|
|
@@ -997,7 +889,6 @@ const classes = computed(() =>
|
|
|
997
889
|
),
|
|
998
890
|
);
|
|
999
891
|
</script>
|
|
1000
|
-
|
|
1001
892
|
<template>
|
|
1002
893
|
<div :class="classes" data-ds data-ds-component="card-body">
|
|
1003
894
|
<slot />
|
|
@@ -1008,14 +899,11 @@ const classes = computed(() =>
|
|
|
1008
899
|
import { inject, computed, type HTMLAttributes } from "vue";
|
|
1009
900
|
import { cn } from "@/lib/cn";
|
|
1010
901
|
import { cardPaddingKey } from "./Card.vue";
|
|
1011
|
-
|
|
1012
902
|
interface Props {
|
|
1013
903
|
class?: HTMLAttributes["class"];
|
|
1014
904
|
}
|
|
1015
|
-
|
|
1016
905
|
const props = defineProps<Props>();
|
|
1017
906
|
const padding = inject(cardPaddingKey, "compact");
|
|
1018
|
-
|
|
1019
907
|
const classes = computed(() =>
|
|
1020
908
|
cn(
|
|
1021
909
|
"flex items-center",
|
|
@@ -1024,7 +912,6 @@ const classes = computed(() =>
|
|
|
1024
912
|
),
|
|
1025
913
|
);
|
|
1026
914
|
</script>
|
|
1027
|
-
|
|
1028
915
|
<template>
|
|
1029
916
|
<div :class="classes" data-ds data-ds-component="card-footer">
|
|
1030
917
|
<slot />
|
|
@@ -1034,25 +921,20 @@ const classes = computed(() =>
|
|
|
1034
921
|
"src/components/ui/Input.vue": `<script setup lang="ts">
|
|
1035
922
|
import { computed, type HTMLAttributes } from "vue";
|
|
1036
923
|
import { cn } from "@/lib/cn";
|
|
1037
|
-
|
|
1038
924
|
type Variant = "default" | "error" | "success";
|
|
1039
925
|
type Size = "sm" | "md" | "lg";
|
|
1040
|
-
|
|
1041
926
|
interface Props {
|
|
1042
927
|
variant?: Variant;
|
|
1043
928
|
size?: Size;
|
|
1044
929
|
disabled?: boolean;
|
|
1045
930
|
class?: HTMLAttributes["class"];
|
|
1046
931
|
}
|
|
1047
|
-
|
|
1048
932
|
const props = withDefaults(defineProps<Props>(), {
|
|
1049
933
|
variant: "default",
|
|
1050
934
|
size: "md",
|
|
1051
935
|
disabled: false,
|
|
1052
936
|
});
|
|
1053
|
-
|
|
1054
937
|
const model = defineModel<string>();
|
|
1055
|
-
|
|
1056
938
|
const variantClasses: Record<Variant, string> = {
|
|
1057
939
|
default:
|
|
1058
940
|
"border-input hover:border-border-strong focus-visible:border-border-strong",
|
|
@@ -1061,13 +943,11 @@ const variantClasses: Record<Variant, string> = {
|
|
|
1061
943
|
success:
|
|
1062
944
|
"border-success text-foreground focus-visible:border-success placeholder:text-input-placeholder",
|
|
1063
945
|
};
|
|
1064
|
-
|
|
1065
946
|
const sizeClasses: Record<Size, string> = {
|
|
1066
947
|
sm: "h-8 px-2.5 text-xs",
|
|
1067
948
|
md: "h-[var(--ds-control-height,36px)] px-3 text-sm",
|
|
1068
949
|
lg: "h-10 px-3.5 text-sm",
|
|
1069
950
|
};
|
|
1070
|
-
|
|
1071
951
|
const classes = computed(() =>
|
|
1072
952
|
cn(
|
|
1073
953
|
"flex w-full text-sm leading-5 rounded-md border bg-background text-input-foreground",
|
|
@@ -1082,7 +962,6 @@ const classes = computed(() =>
|
|
|
1082
962
|
),
|
|
1083
963
|
);
|
|
1084
964
|
</script>
|
|
1085
|
-
|
|
1086
965
|
<template>
|
|
1087
966
|
<input
|
|
1088
967
|
v-model="model"
|
|
@@ -1096,23 +975,18 @@ const classes = computed(() =>
|
|
|
1096
975
|
"src/components/ui/Alert.vue": `<script setup lang="ts">
|
|
1097
976
|
import { computed, ref, type HTMLAttributes } from "vue";
|
|
1098
977
|
import { cn } from "@/lib/cn";
|
|
1099
|
-
|
|
1100
978
|
type Variant = "info" | "success" | "warning" | "danger" | "default";
|
|
1101
|
-
|
|
1102
979
|
interface Props {
|
|
1103
980
|
variant?: Variant;
|
|
1104
981
|
title?: string;
|
|
1105
982
|
dismissible?: boolean;
|
|
1106
983
|
class?: HTMLAttributes["class"];
|
|
1107
984
|
}
|
|
1108
|
-
|
|
1109
985
|
const props = withDefaults(defineProps<Props>(), {
|
|
1110
986
|
variant: "info",
|
|
1111
987
|
dismissible: false,
|
|
1112
988
|
});
|
|
1113
|
-
|
|
1114
989
|
const dismissed = ref(false);
|
|
1115
|
-
|
|
1116
990
|
const variantClasses: Record<Variant, string> = {
|
|
1117
991
|
info: "bg-info-muted text-info-muted-foreground border-info/20",
|
|
1118
992
|
success: "bg-success-muted text-success-muted-foreground border-success/20",
|
|
@@ -1120,7 +994,6 @@ const variantClasses: Record<Variant, string> = {
|
|
|
1120
994
|
danger: "bg-danger-muted text-danger-muted-foreground border-danger/20",
|
|
1121
995
|
default: "bg-muted text-muted-foreground border-border",
|
|
1122
996
|
};
|
|
1123
|
-
|
|
1124
997
|
const iconColorClasses: Record<Variant, string> = {
|
|
1125
998
|
info: "text-info",
|
|
1126
999
|
success: "text-success",
|
|
@@ -1128,7 +1001,6 @@ const iconColorClasses: Record<Variant, string> = {
|
|
|
1128
1001
|
danger: "text-danger",
|
|
1129
1002
|
default: "text-muted-foreground",
|
|
1130
1003
|
};
|
|
1131
|
-
|
|
1132
1004
|
const classes = computed(() =>
|
|
1133
1005
|
cn(
|
|
1134
1006
|
"relative flex gap-3 rounded-md p-4 text-sm leading-5 border",
|
|
@@ -1137,7 +1009,6 @@ const classes = computed(() =>
|
|
|
1137
1009
|
props.class,
|
|
1138
1010
|
),
|
|
1139
1011
|
);
|
|
1140
|
-
|
|
1141
1012
|
// SVG icon paths by variant
|
|
1142
1013
|
const iconPaths: Record<Variant, string> = {
|
|
1143
1014
|
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 +1018,6 @@ const iconPaths: Record<Variant, string> = {
|
|
|
1147
1018
|
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
1019
|
};
|
|
1149
1020
|
</script>
|
|
1150
|
-
|
|
1151
1021
|
<template>
|
|
1152
1022
|
<div
|
|
1153
1023
|
v-if="!dismissed"
|
|
@@ -1201,42 +1071,34 @@ const iconPaths: Record<Variant, string> = {
|
|
|
1201
1071
|
"src/components/ui/Heading.vue": `<script setup lang="ts">
|
|
1202
1072
|
import { computed, type HTMLAttributes } from "vue";
|
|
1203
1073
|
import { cn } from "@/lib/cn";
|
|
1204
|
-
|
|
1205
1074
|
type Level = 1 | 2 | 3 | 4;
|
|
1206
1075
|
type Color = "default" | "foreground" | "muted" | "primary";
|
|
1207
|
-
|
|
1208
1076
|
interface Props {
|
|
1209
1077
|
level?: Level;
|
|
1210
1078
|
color?: Color;
|
|
1211
1079
|
class?: HTMLAttributes["class"];
|
|
1212
1080
|
}
|
|
1213
|
-
|
|
1214
1081
|
const props = withDefaults(defineProps<Props>(), {
|
|
1215
1082
|
level: 1,
|
|
1216
1083
|
color: "default",
|
|
1217
1084
|
});
|
|
1218
|
-
|
|
1219
1085
|
const levelClasses: Record<Level, string> = {
|
|
1220
1086
|
1: "text-[30px] leading-[36px] font-bold tracking-tight",
|
|
1221
1087
|
2: "text-[24px] leading-[32px] font-semibold tracking-tight",
|
|
1222
1088
|
3: "text-[20px] leading-[28px] font-semibold tracking-normal",
|
|
1223
1089
|
4: "text-[18px] leading-[28px] font-medium tracking-normal",
|
|
1224
1090
|
};
|
|
1225
|
-
|
|
1226
1091
|
const colorClasses: Record<Color, string> = {
|
|
1227
1092
|
default: "text-foreground",
|
|
1228
1093
|
foreground: "text-foreground",
|
|
1229
1094
|
muted: "text-muted-foreground",
|
|
1230
1095
|
primary: "text-primary",
|
|
1231
1096
|
};
|
|
1232
|
-
|
|
1233
1097
|
const tag = computed(() => \`h\${props.level}\` as const);
|
|
1234
|
-
|
|
1235
1098
|
const classes = computed(() =>
|
|
1236
1099
|
cn(levelClasses[props.level], colorClasses[props.color], props.class),
|
|
1237
1100
|
);
|
|
1238
1101
|
</script>
|
|
1239
|
-
|
|
1240
1102
|
<template>
|
|
1241
1103
|
<component :is="tag" :class="classes" data-ds data-ds-component="heading">
|
|
1242
1104
|
<slot />
|
|
@@ -1246,7 +1108,6 @@ const classes = computed(() =>
|
|
|
1246
1108
|
"src/components/ui/Text.vue": `<script setup lang="ts">
|
|
1247
1109
|
import { computed, type HTMLAttributes } from "vue";
|
|
1248
1110
|
import { cn } from "@/lib/cn";
|
|
1249
|
-
|
|
1250
1111
|
type Variant = "body" | "bodySm" | "caption" | "label" | "overline" | "code";
|
|
1251
1112
|
type Color =
|
|
1252
1113
|
| "default"
|
|
@@ -1257,20 +1118,17 @@ type Color =
|
|
|
1257
1118
|
| "warning"
|
|
1258
1119
|
| "danger"
|
|
1259
1120
|
| "info";
|
|
1260
|
-
|
|
1261
1121
|
interface Props {
|
|
1262
1122
|
variant?: Variant;
|
|
1263
1123
|
color?: Color;
|
|
1264
1124
|
as?: string;
|
|
1265
1125
|
class?: HTMLAttributes["class"];
|
|
1266
1126
|
}
|
|
1267
|
-
|
|
1268
1127
|
const props = withDefaults(defineProps<Props>(), {
|
|
1269
1128
|
variant: "body",
|
|
1270
1129
|
color: "default",
|
|
1271
1130
|
as: "p",
|
|
1272
1131
|
});
|
|
1273
|
-
|
|
1274
1132
|
const variantClasses: Record<Variant, string> = {
|
|
1275
1133
|
body: "text-[16px] leading-[24px] font-normal tracking-normal",
|
|
1276
1134
|
bodySm: "text-[14px] leading-[20px] font-normal tracking-normal",
|
|
@@ -1281,7 +1139,6 @@ const variantClasses: Record<Variant, string> = {
|
|
|
1281
1139
|
"text-[12px] leading-[16px] font-semibold tracking-wider uppercase text-muted-foreground",
|
|
1282
1140
|
code: "text-[14px] leading-[20px] font-normal tracking-normal font-mono",
|
|
1283
1141
|
};
|
|
1284
|
-
|
|
1285
1142
|
const colorClasses: Record<Color, string> = {
|
|
1286
1143
|
default: "text-foreground",
|
|
1287
1144
|
foreground: "text-foreground",
|
|
@@ -1292,12 +1149,10 @@ const colorClasses: Record<Color, string> = {
|
|
|
1292
1149
|
danger: "text-danger",
|
|
1293
1150
|
info: "text-info",
|
|
1294
1151
|
};
|
|
1295
|
-
|
|
1296
1152
|
const classes = computed(() =>
|
|
1297
1153
|
cn(variantClasses[props.variant], colorClasses[props.color], props.class),
|
|
1298
1154
|
);
|
|
1299
1155
|
</script>
|
|
1300
|
-
|
|
1301
1156
|
<template>
|
|
1302
1157
|
<component :is="as" :class="classes" data-ds data-ds-component="text">
|
|
1303
1158
|
<slot />
|
|
@@ -1306,13 +1161,11 @@ const classes = computed(() =>
|
|
|
1306
1161
|
`,
|
|
1307
1162
|
},
|
|
1308
1163
|
},
|
|
1309
|
-
|
|
1310
1164
|
"laravel-blade": {
|
|
1311
1165
|
files: {
|
|
1312
1166
|
"vite.config.js": `import tailwindcss from "@tailwindcss/vite";
|
|
1313
1167
|
import laravel from "laravel-vite-plugin";
|
|
1314
1168
|
import { defineConfig } from "vite";
|
|
1315
|
-
|
|
1316
1169
|
export default defineConfig({
|
|
1317
1170
|
plugins: [
|
|
1318
1171
|
laravel({
|
|
@@ -1328,7 +1181,6 @@ export default defineConfig({
|
|
|
1328
1181
|
`,
|
|
1329
1182
|
"resources/js/app.js": `// Unified UI — Laravel Blade Starter
|
|
1330
1183
|
// Design tokens are loaded via CSS. This file handles theme toggling.
|
|
1331
|
-
|
|
1332
1184
|
function initTheme() {
|
|
1333
1185
|
const stored = localStorage.getItem("theme");
|
|
1334
1186
|
const prefersDark = window.matchMedia(
|
|
@@ -1337,15 +1189,12 @@ function initTheme() {
|
|
|
1337
1189
|
const theme = stored || (prefersDark ? "dark" : "light");
|
|
1338
1190
|
document.documentElement.classList.toggle("dark", theme === "dark");
|
|
1339
1191
|
}
|
|
1340
|
-
|
|
1341
1192
|
function toggleTheme() {
|
|
1342
1193
|
const isDark = document.documentElement.classList.toggle("dark");
|
|
1343
1194
|
localStorage.setItem("theme", isDark ? "dark" : "light");
|
|
1344
1195
|
}
|
|
1345
|
-
|
|
1346
1196
|
// Initialize on load
|
|
1347
1197
|
initTheme();
|
|
1348
|
-
|
|
1349
1198
|
// Expose globally for Blade onclick handlers
|
|
1350
1199
|
window.toggleTheme = toggleTheme;
|
|
1351
1200
|
`,
|
|
@@ -1364,7 +1213,6 @@ window.toggleTheme = toggleTheme;
|
|
|
1364
1213
|
</html>
|
|
1365
1214
|
`,
|
|
1366
1215
|
"resources/views/welcome.blade.php": `@extends('layouts.app')
|
|
1367
|
-
|
|
1368
1216
|
@section('content')
|
|
1369
1217
|
<div class="flex min-h-svh flex-col items-center justify-center gap-8 p-8">
|
|
1370
1218
|
<x-ui.card class="w-full max-w-lg">
|
|
@@ -1374,7 +1222,6 @@ window.toggleTheme = toggleTheme;
|
|
|
1374
1222
|
Your Laravel project is ready with components. Start building!
|
|
1375
1223
|
</x-ui.text>
|
|
1376
1224
|
</x-ui.card-header>
|
|
1377
|
-
|
|
1378
1225
|
<x-ui.card-body class="space-y-6">
|
|
1379
1226
|
{{-- Buttons --}}
|
|
1380
1227
|
<div class="space-y-2">
|
|
@@ -1387,7 +1234,6 @@ window.toggleTheme = toggleTheme;
|
|
|
1387
1234
|
<x-ui.button variant="primary" :loading="true" size="sm">Loading</x-ui.button>
|
|
1388
1235
|
</div>
|
|
1389
1236
|
</div>
|
|
1390
|
-
|
|
1391
1237
|
{{-- Badges --}}
|
|
1392
1238
|
<div class="space-y-2">
|
|
1393
1239
|
<x-ui.text variant="label">Badges</x-ui.text>
|
|
@@ -1401,19 +1247,16 @@ window.toggleTheme = toggleTheme;
|
|
|
1401
1247
|
<x-ui.badge variant="outline">Outline</x-ui.badge>
|
|
1402
1248
|
</div>
|
|
1403
1249
|
</div>
|
|
1404
|
-
|
|
1405
1250
|
{{-- Input --}}
|
|
1406
1251
|
<div class="space-y-2">
|
|
1407
1252
|
<x-ui.text variant="label">Input</x-ui.text>
|
|
1408
1253
|
<x-ui.input placeholder="you@example.com" />
|
|
1409
1254
|
</div>
|
|
1410
|
-
|
|
1411
1255
|
{{-- Alert --}}
|
|
1412
1256
|
<x-ui.alert variant="info" title="All set!">
|
|
1413
1257
|
Your design system components are working in Laravel.
|
|
1414
1258
|
</x-ui.alert>
|
|
1415
1259
|
</x-ui.card-body>
|
|
1416
|
-
|
|
1417
1260
|
<x-ui.card-footer class="justify-between">
|
|
1418
1261
|
<x-ui.button variant="primary">Get Started</x-ui.button>
|
|
1419
1262
|
<x-ui.button variant="secondary" onclick="toggleTheme()">Toggle Theme</x-ui.button>
|
|
@@ -1431,7 +1274,6 @@ window.toggleTheme = toggleTheme;
|
|
|
1431
1274
|
'loading' => false,
|
|
1432
1275
|
'disabled' => false,
|
|
1433
1276
|
])
|
|
1434
|
-
|
|
1435
1277
|
@php
|
|
1436
1278
|
$variants = [
|
|
1437
1279
|
'primary' => 'bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active',
|
|
@@ -1439,19 +1281,16 @@ $variants = [
|
|
|
1439
1281
|
'ghost' => 'bg-transparent text-foreground hover:bg-muted hover:text-foreground active:bg-secondary-active',
|
|
1440
1282
|
'danger' => 'bg-danger text-danger-foreground hover:bg-danger-hover active:bg-danger-active',
|
|
1441
1283
|
];
|
|
1442
|
-
|
|
1443
1284
|
$sizes = [
|
|
1444
1285
|
'sm' => 'h-8 px-3 text-xs gap-1.5',
|
|
1445
1286
|
'md' => 'h-[var(--ds-control-height,36px)] px-[var(--ds-padding-button-x,16px)] text-sm gap-2',
|
|
1446
1287
|
'lg' => 'h-10 px-5 text-sm gap-2',
|
|
1447
1288
|
];
|
|
1448
|
-
|
|
1449
1289
|
$iconOnlySizes = [
|
|
1450
1290
|
'sm' => 'w-8 !px-0',
|
|
1451
1291
|
'md' => 'w-9 !px-0',
|
|
1452
1292
|
'lg' => 'w-10 !px-0',
|
|
1453
1293
|
];
|
|
1454
|
-
|
|
1455
1294
|
$classes = implode(' ', array_filter([
|
|
1456
1295
|
'inline-flex items-center justify-center gap-2 text-sm font-medium leading-5 rounded-md',
|
|
1457
1296
|
'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 +1304,6 @@ $classes = implode(' ', array_filter([
|
|
|
1465
1304
|
$loading ? 'pointer-events-none opacity-70' : '',
|
|
1466
1305
|
]));
|
|
1467
1306
|
@endphp
|
|
1468
|
-
|
|
1469
1307
|
<{{ $as }}
|
|
1470
1308
|
{{ $attributes->merge(['class' => $classes, 'disabled' => $disabled || $loading]) }}
|
|
1471
1309
|
data-ds
|
|
@@ -1486,7 +1324,6 @@ $classes = implode(' ', array_filter([
|
|
|
1486
1324
|
'size' => 'md',
|
|
1487
1325
|
'dismissible' => false,
|
|
1488
1326
|
])
|
|
1489
|
-
|
|
1490
1327
|
@php
|
|
1491
1328
|
$variants = [
|
|
1492
1329
|
'default' => 'bg-muted text-foreground border border-transparent',
|
|
@@ -1498,13 +1335,11 @@ $variants = [
|
|
|
1498
1335
|
'info' => 'bg-info-muted text-info-muted-foreground border border-transparent',
|
|
1499
1336
|
'outline' => 'bg-transparent text-foreground border border-border',
|
|
1500
1337
|
];
|
|
1501
|
-
|
|
1502
1338
|
$sizes = [
|
|
1503
1339
|
'sm' => 'px-2 py-0.5 text-[11px] gap-1',
|
|
1504
1340
|
'md' => 'px-2.5 py-1 text-xs gap-1.5',
|
|
1505
1341
|
'lg' => 'px-3 py-1.5 text-sm gap-2',
|
|
1506
1342
|
];
|
|
1507
|
-
|
|
1508
1343
|
$classes = implode(' ', [
|
|
1509
1344
|
'inline-flex items-center gap-1.5 rounded-full font-medium leading-none whitespace-nowrap',
|
|
1510
1345
|
'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 +1348,6 @@ $classes = implode(' ', [
|
|
|
1513
1348
|
$sizes[$size] ?? $sizes['md'],
|
|
1514
1349
|
]);
|
|
1515
1350
|
@endphp
|
|
1516
|
-
|
|
1517
1351
|
<span {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="badge">
|
|
1518
1352
|
{{ $slot }}
|
|
1519
1353
|
@if($dismissible)
|
|
@@ -1533,7 +1367,6 @@ $classes = implode(' ', [
|
|
|
1533
1367
|
'fullWidth' => false,
|
|
1534
1368
|
'as' => 'div',
|
|
1535
1369
|
])
|
|
1536
|
-
|
|
1537
1370
|
@php
|
|
1538
1371
|
$variants = [
|
|
1539
1372
|
'default' => 'bg-surface border border-border',
|
|
@@ -1541,50 +1374,42 @@ $variants = [
|
|
|
1541
1374
|
'elevated' => 'bg-surface-raised border border-border-muted shadow-md',
|
|
1542
1375
|
'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
1376
|
];
|
|
1544
|
-
|
|
1545
1377
|
$classes = implode(' ', array_filter([
|
|
1546
1378
|
'flex flex-col rounded-md overflow-hidden text-sm text-foreground',
|
|
1547
1379
|
$variants[$variant] ?? $variants['default'],
|
|
1548
1380
|
$fullWidth ? 'w-full' : '',
|
|
1549
1381
|
]));
|
|
1550
1382
|
@endphp
|
|
1551
|
-
|
|
1552
1383
|
<{{ $as }} {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card" data-ds-padding="{{ $padding }}">
|
|
1553
1384
|
{{ $slot }}
|
|
1554
1385
|
</{{ $as }}>
|
|
1555
1386
|
`,
|
|
1556
1387
|
"resources/views/components/ui/card-header.blade.php": `@aware(['padding' => 'compact'])
|
|
1557
|
-
|
|
1558
1388
|
@php
|
|
1559
1389
|
$classes = $padding === 'comfortable'
|
|
1560
1390
|
? 'flex flex-col px-6 pt-6 gap-1.5'
|
|
1561
1391
|
: 'flex flex-col px-[var(--ds-padding-card,16px)] pt-[var(--ds-padding-card,16px)] gap-1';
|
|
1562
1392
|
@endphp
|
|
1563
|
-
|
|
1564
1393
|
<div {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card-header">
|
|
1565
1394
|
{{ $slot }}
|
|
1566
1395
|
</div>
|
|
1567
1396
|
`,
|
|
1568
1397
|
"resources/views/components/ui/card-body.blade.php": `@aware(['padding' => 'compact'])
|
|
1569
|
-
|
|
1570
1398
|
@php
|
|
1571
1399
|
$classes = $padding === 'comfortable'
|
|
1572
1400
|
? 'flex flex-col flex-1 px-6 py-4 gap-4'
|
|
1573
1401
|
: 'flex flex-col flex-1 px-[var(--ds-padding-card,16px)] py-3 gap-[var(--ds-gap-default,0.75rem)]';
|
|
1574
1402
|
@endphp
|
|
1575
|
-
|
|
1576
1403
|
<div {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card-body">
|
|
1577
1404
|
{{ $slot }}
|
|
1578
1405
|
</div>
|
|
1579
1406
|
`,
|
|
1580
1407
|
"resources/views/components/ui/card-footer.blade.php": `@aware(['padding' => 'compact'])
|
|
1581
|
-
|
|
1582
1408
|
@php
|
|
1583
1409
|
$classes = $padding === 'comfortable'
|
|
1584
1410
|
? 'flex items-center px-6 pb-6 gap-3'
|
|
1585
1411
|
: 'flex items-center px-[var(--ds-padding-card,16px)] pb-[var(--ds-padding-card,16px)] gap-2';
|
|
1586
1412
|
@endphp
|
|
1587
|
-
|
|
1588
1413
|
<div {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card-footer">
|
|
1589
1414
|
{{ $slot }}
|
|
1590
1415
|
</div>
|
|
@@ -1594,20 +1419,17 @@ $classes = $padding === 'comfortable'
|
|
|
1594
1419
|
'size' => 'md',
|
|
1595
1420
|
'disabled' => false,
|
|
1596
1421
|
])
|
|
1597
|
-
|
|
1598
1422
|
@php
|
|
1599
1423
|
$variants = [
|
|
1600
1424
|
'default' => 'border-input hover:border-border-strong focus-visible:border-border-strong',
|
|
1601
1425
|
'error' => 'border-danger text-foreground focus-visible:border-danger placeholder:text-input-placeholder',
|
|
1602
1426
|
'success' => 'border-success text-foreground focus-visible:border-success placeholder:text-input-placeholder',
|
|
1603
1427
|
];
|
|
1604
|
-
|
|
1605
1428
|
$sizes = [
|
|
1606
1429
|
'sm' => 'h-8 px-2.5 text-xs',
|
|
1607
1430
|
'md' => 'h-[var(--ds-control-height,36px)] px-3 text-sm',
|
|
1608
1431
|
'lg' => 'h-10 px-3.5 text-sm',
|
|
1609
1432
|
];
|
|
1610
|
-
|
|
1611
1433
|
$classes = implode(' ', [
|
|
1612
1434
|
'flex w-full text-sm leading-5 rounded-md border bg-background text-input-foreground',
|
|
1613
1435
|
'placeholder:text-input-placeholder',
|
|
@@ -1619,7 +1441,6 @@ $classes = implode(' ', [
|
|
|
1619
1441
|
$sizes[$size] ?? $sizes['md'],
|
|
1620
1442
|
]);
|
|
1621
1443
|
@endphp
|
|
1622
|
-
|
|
1623
1444
|
<input {{ $attributes->merge(['class' => $classes, 'disabled' => $disabled, 'type' => 'text']) }} data-ds data-ds-component="input" />
|
|
1624
1445
|
`,
|
|
1625
1446
|
"resources/views/components/ui/alert.blade.php": `@props([
|
|
@@ -1627,7 +1448,6 @@ $classes = implode(' ', [
|
|
|
1627
1448
|
'title' => null,
|
|
1628
1449
|
'dismissible' => false,
|
|
1629
1450
|
])
|
|
1630
|
-
|
|
1631
1451
|
@php
|
|
1632
1452
|
$variants = [
|
|
1633
1453
|
'info' => 'bg-info-muted text-info-muted-foreground border-info/20',
|
|
@@ -1636,7 +1456,6 @@ $variants = [
|
|
|
1636
1456
|
'danger' => 'bg-danger-muted text-danger-muted-foreground border-danger/20',
|
|
1637
1457
|
'default' => 'bg-muted text-muted-foreground border-border',
|
|
1638
1458
|
];
|
|
1639
|
-
|
|
1640
1459
|
$iconColors = [
|
|
1641
1460
|
'info' => 'text-info',
|
|
1642
1461
|
'success' => 'text-success',
|
|
@@ -1644,7 +1463,6 @@ $iconColors = [
|
|
|
1644
1463
|
'danger' => 'text-danger',
|
|
1645
1464
|
'default' => 'text-muted-foreground',
|
|
1646
1465
|
];
|
|
1647
|
-
|
|
1648
1466
|
$iconPaths = [
|
|
1649
1467
|
'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
1468
|
'success' => 'M9 12l2 2 4-4m6 2a10 10 0 11-20 0 10 10 0 0120 0z',
|
|
@@ -1652,14 +1470,12 @@ $iconPaths = [
|
|
|
1652
1470
|
'danger' => 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a10 10 0 11-20 0 10 10 0 0120 0z',
|
|
1653
1471
|
'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
1472
|
];
|
|
1655
|
-
|
|
1656
1473
|
$classes = implode(' ', [
|
|
1657
1474
|
'relative flex gap-3 rounded-md p-4 text-sm leading-5 border',
|
|
1658
1475
|
'transition-colors duration-[var(--duration-fast,150ms)]',
|
|
1659
1476
|
$variants[$variant] ?? $variants['info'],
|
|
1660
1477
|
]);
|
|
1661
1478
|
@endphp
|
|
1662
|
-
|
|
1663
1479
|
<div {{ $attributes->merge(['class' => $classes]) }} role="alert" data-ds data-ds-component="alert">
|
|
1664
1480
|
<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
1481
|
<path d="{{ $iconPaths[$variant] ?? $iconPaths['info'] }}"/>
|
|
@@ -1685,7 +1501,6 @@ $classes = implode(' ', [
|
|
|
1685
1501
|
'level' => 1,
|
|
1686
1502
|
'color' => 'default',
|
|
1687
1503
|
])
|
|
1688
|
-
|
|
1689
1504
|
@php
|
|
1690
1505
|
$levels = [
|
|
1691
1506
|
1 => 'text-[30px] leading-[36px] font-bold tracking-tight',
|
|
@@ -1693,22 +1508,18 @@ $levels = [
|
|
|
1693
1508
|
3 => 'text-[20px] leading-[28px] font-semibold tracking-normal',
|
|
1694
1509
|
4 => 'text-[18px] leading-[28px] font-medium tracking-normal',
|
|
1695
1510
|
];
|
|
1696
|
-
|
|
1697
1511
|
$colors = [
|
|
1698
1512
|
'default' => 'text-foreground',
|
|
1699
1513
|
'foreground' => 'text-foreground',
|
|
1700
1514
|
'muted' => 'text-muted-foreground',
|
|
1701
1515
|
'primary' => 'text-primary',
|
|
1702
1516
|
];
|
|
1703
|
-
|
|
1704
1517
|
$classes = implode(' ', [
|
|
1705
1518
|
$levels[$level] ?? $levels[1],
|
|
1706
1519
|
$colors[$color] ?? $colors['default'],
|
|
1707
1520
|
]);
|
|
1708
|
-
|
|
1709
1521
|
$tag = 'h' . min(max((int)$level, 1), 6);
|
|
1710
1522
|
@endphp
|
|
1711
|
-
|
|
1712
1523
|
<{{ $tag }} {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="heading">
|
|
1713
1524
|
{{ $slot }}
|
|
1714
1525
|
</{{ $tag }}>
|
|
@@ -1718,7 +1529,6 @@ $tag = 'h' . min(max((int)$level, 1), 6);
|
|
|
1718
1529
|
'color' => 'default',
|
|
1719
1530
|
'as' => 'p',
|
|
1720
1531
|
])
|
|
1721
|
-
|
|
1722
1532
|
@php
|
|
1723
1533
|
$variants = [
|
|
1724
1534
|
'body' => 'text-[16px] leading-[24px] font-normal tracking-normal',
|
|
@@ -1728,7 +1538,6 @@ $variants = [
|
|
|
1728
1538
|
'overline' => 'text-[12px] leading-[16px] font-semibold tracking-wider uppercase text-muted-foreground',
|
|
1729
1539
|
'code' => 'text-[14px] leading-[20px] font-normal tracking-normal font-mono',
|
|
1730
1540
|
];
|
|
1731
|
-
|
|
1732
1541
|
$colors = [
|
|
1733
1542
|
'default' => 'text-foreground',
|
|
1734
1543
|
'foreground' => 'text-foreground',
|
|
@@ -1739,13 +1548,11 @@ $colors = [
|
|
|
1739
1548
|
'danger' => 'text-danger',
|
|
1740
1549
|
'info' => 'text-info',
|
|
1741
1550
|
];
|
|
1742
|
-
|
|
1743
1551
|
$classes = implode(' ', [
|
|
1744
1552
|
$variants[$variant] ?? $variants['body'],
|
|
1745
1553
|
$colors[$color] ?? $colors['default'],
|
|
1746
1554
|
]);
|
|
1747
1555
|
@endphp
|
|
1748
|
-
|
|
1749
1556
|
<{{ $as }} {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="text">
|
|
1750
1557
|
{{ $slot }}
|
|
1751
1558
|
</{{ $as }}>
|
|
@@ -1753,20 +1560,17 @@ $classes = implode(' ', [
|
|
|
1753
1560
|
},
|
|
1754
1561
|
},
|
|
1755
1562
|
};
|
|
1756
|
-
|
|
1757
1563
|
// ---------------------------------------------------------------------------
|
|
1758
1564
|
// Starter kit scaffolding command
|
|
1759
1565
|
// ---------------------------------------------------------------------------
|
|
1760
|
-
|
|
1761
1566
|
async function cmdInitWithTemplate(positional, flags) {
|
|
1762
1567
|
log();
|
|
1763
1568
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Create a new project")}`);
|
|
1764
1569
|
log();
|
|
1765
|
-
|
|
1766
1570
|
// 1. Pick framework
|
|
1767
1571
|
let framework;
|
|
1768
|
-
const templateFlag =
|
|
1769
|
-
|
|
1572
|
+
const templateFlag =
|
|
1573
|
+
typeof flags.template === "string" ? flags.template : null;
|
|
1770
1574
|
if (templateFlag) {
|
|
1771
1575
|
framework = FRAMEWORKS.find((f) => f.name === templateFlag);
|
|
1772
1576
|
if (!framework) {
|
|
@@ -1788,27 +1592,21 @@ async function cmdInitWithTemplate(positional, flags) {
|
|
|
1788
1592
|
}
|
|
1789
1593
|
log();
|
|
1790
1594
|
}
|
|
1791
|
-
|
|
1792
1595
|
logStep("✓", `Framework: ${c("cyan", framework.label)}`);
|
|
1793
|
-
|
|
1794
1596
|
// 2. Get project name
|
|
1795
1597
|
let projectName = positional[0];
|
|
1796
1598
|
if (!projectName) {
|
|
1797
1599
|
projectName = await promptText("Project name:", "my-unified-app");
|
|
1798
1600
|
}
|
|
1799
|
-
|
|
1800
1601
|
const targetDir = resolve(process.cwd(), projectName);
|
|
1801
1602
|
logStep("✓", `Project: ${c("cyan", projectName)}`);
|
|
1802
1603
|
log();
|
|
1803
|
-
|
|
1804
1604
|
// 3. Run the official scaffolding command
|
|
1805
1605
|
logStep("📦", `Scaffolding ${c("cyan", framework.label)} project...`);
|
|
1806
1606
|
log();
|
|
1807
|
-
|
|
1808
1607
|
const scaffoldCmd = framework.scaffoldCmd(projectName);
|
|
1809
1608
|
logStep(" ", c("dim", `> ${scaffoldCmd}`));
|
|
1810
1609
|
log();
|
|
1811
|
-
|
|
1812
1610
|
const scaffoldOk = runCmd(scaffoldCmd, process.cwd());
|
|
1813
1611
|
if (!scaffoldOk) {
|
|
1814
1612
|
logError(
|
|
@@ -1819,40 +1617,35 @@ async function cmdInitWithTemplate(positional, flags) {
|
|
|
1819
1617
|
);
|
|
1820
1618
|
process.exit(1);
|
|
1821
1619
|
}
|
|
1822
|
-
|
|
1823
1620
|
if (!existsSync(targetDir)) {
|
|
1824
|
-
logError(
|
|
1621
|
+
logError(
|
|
1622
|
+
`Expected directory "${projectName}" was not created by the scaffolding tool.`,
|
|
1623
|
+
);
|
|
1825
1624
|
process.exit(1);
|
|
1826
1625
|
}
|
|
1827
|
-
|
|
1828
1626
|
log();
|
|
1829
1627
|
logStep("✓", c("green", `${framework.label} project scaffolded`));
|
|
1830
|
-
|
|
1831
1628
|
// 4. Install Unified UI + extra deps
|
|
1832
1629
|
logStep("📦", "Installing Unified UI design system...");
|
|
1833
|
-
|
|
1834
1630
|
const pm = await detectPackageManager(targetDir);
|
|
1835
1631
|
const allDeps = [...framework.deps];
|
|
1836
1632
|
const allDevDeps = [...framework.devDeps];
|
|
1837
|
-
|
|
1838
1633
|
if (allDeps.length > 0) {
|
|
1839
1634
|
const depCmd = getInstallCommand(pm, allDeps);
|
|
1840
1635
|
logStep(" ", c("dim", depCmd));
|
|
1841
1636
|
runCmd(depCmd, targetDir, "pipe");
|
|
1842
1637
|
}
|
|
1843
|
-
|
|
1844
1638
|
if (allDevDeps.length > 0) {
|
|
1845
|
-
const devDepCmd = getInstallCommand(pm, allDevDeps)
|
|
1639
|
+
const devDepCmd = getInstallCommand(pm, allDevDeps)
|
|
1640
|
+
.replace(" add ", " add -D ")
|
|
1641
|
+
.replace(" install ", " install -D ");
|
|
1846
1642
|
logStep(" ", c("dim", devDepCmd));
|
|
1847
1643
|
runCmd(devDepCmd, targetDir, "pipe");
|
|
1848
1644
|
}
|
|
1849
|
-
|
|
1850
1645
|
logStep("✓", c("green", "Dependencies installed"));
|
|
1851
|
-
|
|
1852
1646
|
// 5. Apply overlay files
|
|
1853
1647
|
log();
|
|
1854
1648
|
logStep("✏️ ", "Applying Unified UI starter files...");
|
|
1855
|
-
|
|
1856
1649
|
const overlay = OVERLAYS[framework.name];
|
|
1857
1650
|
if (overlay) {
|
|
1858
1651
|
for (const [filePath, content] of Object.entries(overlay.files)) {
|
|
@@ -1861,22 +1654,28 @@ async function cmdInitWithTemplate(positional, flags) {
|
|
|
1861
1654
|
logStep("✓", c("green", filePath));
|
|
1862
1655
|
}
|
|
1863
1656
|
}
|
|
1864
|
-
|
|
1865
1657
|
// 6. Git commit (if git was initialized by the scaffold tool)
|
|
1866
1658
|
const gitDir = join(targetDir, ".git");
|
|
1867
1659
|
if (existsSync(gitDir)) {
|
|
1868
1660
|
runCmd("git add -A", targetDir, "pipe");
|
|
1869
|
-
runCmd(
|
|
1661
|
+
runCmd(
|
|
1662
|
+
'git commit -m "chore: add Unified UI design system" --no-verify',
|
|
1663
|
+
targetDir,
|
|
1664
|
+
"pipe",
|
|
1665
|
+
);
|
|
1870
1666
|
logStep("✓", c("green", "Committed Unified UI changes"));
|
|
1871
1667
|
} else {
|
|
1872
1668
|
// Initialize git if it wasn't done by the scaffold tool
|
|
1873
1669
|
if (runCmd("git init", targetDir, "pipe")) {
|
|
1874
1670
|
runCmd("git add -A", targetDir, "pipe");
|
|
1875
|
-
runCmd(
|
|
1671
|
+
runCmd(
|
|
1672
|
+
'git commit -m "chore: initial commit with Unified UI" --no-verify',
|
|
1673
|
+
targetDir,
|
|
1674
|
+
"pipe",
|
|
1675
|
+
);
|
|
1876
1676
|
logStep("✓", c("green", "Initialized git repository"));
|
|
1877
1677
|
}
|
|
1878
1678
|
}
|
|
1879
|
-
|
|
1880
1679
|
// 7. Print success
|
|
1881
1680
|
log();
|
|
1882
1681
|
logStep("🎉", c("green", `Project "${projectName}" is ready!`));
|
|
@@ -1884,40 +1683,39 @@ async function cmdInitWithTemplate(positional, flags) {
|
|
|
1884
1683
|
log(` ${c("dim", "Next steps:")}`);
|
|
1885
1684
|
log();
|
|
1886
1685
|
log(` ${c("cyan", `cd ${projectName}`)}`);
|
|
1887
|
-
|
|
1888
1686
|
if (framework.name === "laravel-blade") {
|
|
1889
1687
|
log(` ${c("cyan", "npm run dev")}`);
|
|
1890
1688
|
log(` ${c("cyan", "php artisan serve")}`);
|
|
1891
1689
|
} else {
|
|
1892
1690
|
log(` ${c("cyan", "npm run dev")}`);
|
|
1893
1691
|
}
|
|
1894
|
-
|
|
1895
1692
|
log();
|
|
1896
|
-
|
|
1897
1693
|
if (framework.name === "vuejs" || framework.name === "laravel-blade") {
|
|
1898
|
-
log(
|
|
1899
|
-
|
|
1694
|
+
log(
|
|
1695
|
+
` ${c("dim", "Note: This template includes design tokens (CSS variables + Tailwind")}`,
|
|
1696
|
+
);
|
|
1697
|
+
log(
|
|
1698
|
+
` ${c("dim", "utilities) only. React components are not available in this framework.")}`,
|
|
1699
|
+
);
|
|
1900
1700
|
log(` ${c("dim", "See: https://www.unified-ui.space/docs/tokens")}`);
|
|
1901
1701
|
log();
|
|
1902
1702
|
} else {
|
|
1903
1703
|
log(` ${c("dim", "Start adding components:")}`);
|
|
1904
|
-
log(
|
|
1704
|
+
log(
|
|
1705
|
+
` ${c("cyan", "npx @work-rjkashyap/unified-ui add button card badge")}`,
|
|
1706
|
+
);
|
|
1905
1707
|
log();
|
|
1906
1708
|
}
|
|
1907
1709
|
}
|
|
1908
|
-
|
|
1909
1710
|
async function cmdInit(positional = [], flags = {}) {
|
|
1910
1711
|
// If --template flag is present, run the full scaffolding flow
|
|
1911
1712
|
if (flags.template) {
|
|
1912
1713
|
return cmdInitWithTemplate(positional, flags);
|
|
1913
1714
|
}
|
|
1914
|
-
|
|
1915
1715
|
log();
|
|
1916
1716
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Initialize project")}`);
|
|
1917
1717
|
log();
|
|
1918
|
-
|
|
1919
1718
|
const config = loadConfig();
|
|
1920
|
-
|
|
1921
1719
|
if (existsSync(join(config.root, CONFIG_FILE))) {
|
|
1922
1720
|
const overwrite = await confirm(
|
|
1923
1721
|
`${c("yellow", CONFIG_FILE)} already exists. Overwrite?`,
|
|
@@ -1928,10 +1726,8 @@ async function cmdInit(positional = [], flags = {}) {
|
|
|
1928
1726
|
return;
|
|
1929
1727
|
}
|
|
1930
1728
|
}
|
|
1931
|
-
|
|
1932
1729
|
saveConfig(DEFAULT_CONFIG);
|
|
1933
1730
|
logStep("✓", `Created ${c("cyan", CONFIG_FILE)}`);
|
|
1934
|
-
|
|
1935
1731
|
// Create directories
|
|
1936
1732
|
const srcDir = join(config.root, config.srcDir);
|
|
1937
1733
|
const dirs = [
|
|
@@ -1939,16 +1735,13 @@ async function cmdInit(positional = [], flags = {}) {
|
|
|
1939
1735
|
join(srcDir, "lib"),
|
|
1940
1736
|
join(srcDir, "styles"),
|
|
1941
1737
|
];
|
|
1942
|
-
|
|
1943
1738
|
for (const dir of dirs) {
|
|
1944
1739
|
mkdirSync(dir, { recursive: true });
|
|
1945
|
-
logStep("✓", `Created ${c("dim", dir.replace(config.root
|
|
1740
|
+
logStep("✓", `Created ${c("dim", dir.replace(`${config.root}/`, ""))}`);
|
|
1946
1741
|
}
|
|
1947
|
-
|
|
1948
1742
|
// Fetch and write the base utilities (cn + focus-ring) and styles
|
|
1949
1743
|
log();
|
|
1950
1744
|
logStep("📡", "Fetching base utilities from registry...");
|
|
1951
|
-
|
|
1952
1745
|
const baseItems = ["cn", "focus-ring", "styles"];
|
|
1953
1746
|
for (const name of baseItems) {
|
|
1954
1747
|
try {
|
|
@@ -1962,28 +1755,25 @@ async function cmdInit(positional = [], flags = {}) {
|
|
|
1962
1755
|
logStep("⚠", c("yellow", `Could not fetch ${name} — add manually later`));
|
|
1963
1756
|
}
|
|
1964
1757
|
}
|
|
1965
|
-
|
|
1966
1758
|
// Install base npm deps
|
|
1967
1759
|
await installNpmDeps(
|
|
1968
1760
|
["class-variance-authority", "clsx", "tailwind-merge"],
|
|
1969
1761
|
config.root,
|
|
1970
1762
|
);
|
|
1971
|
-
|
|
1972
1763
|
log();
|
|
1973
1764
|
logStep("🎉", c("green", "Project initialized! Start adding components:"));
|
|
1974
1765
|
log();
|
|
1975
1766
|
log(` ${c("cyan", "npx @work-rjkashyap/unified-ui add button")}`);
|
|
1976
|
-
log(
|
|
1767
|
+
log(
|
|
1768
|
+
` ${c("cyan", "npx @work-rjkashyap/unified-ui add card badge tabs")}`,
|
|
1769
|
+
);
|
|
1977
1770
|
log();
|
|
1978
1771
|
}
|
|
1979
|
-
|
|
1980
1772
|
async function cmdAdd(names, flags = {}) {
|
|
1981
1773
|
log();
|
|
1982
1774
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Add components")}`);
|
|
1983
1775
|
log();
|
|
1984
|
-
|
|
1985
1776
|
const config = loadConfig();
|
|
1986
|
-
|
|
1987
1777
|
if (!existsSync(join(config.root, CONFIG_FILE)) && !flags.yes) {
|
|
1988
1778
|
const init = await confirm(
|
|
1989
1779
|
`No ${c("cyan", CONFIG_FILE)} found. Initialize first?`,
|
|
@@ -1993,7 +1783,6 @@ async function cmdAdd(names, flags = {}) {
|
|
|
1993
1783
|
log();
|
|
1994
1784
|
}
|
|
1995
1785
|
}
|
|
1996
|
-
|
|
1997
1786
|
// If --all, fetch index and add everything
|
|
1998
1787
|
if (flags.all) {
|
|
1999
1788
|
logStep("📡", "Fetching full registry index...");
|
|
@@ -2008,27 +1797,27 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2008
1797
|
return;
|
|
2009
1798
|
}
|
|
2010
1799
|
}
|
|
2011
|
-
|
|
2012
1800
|
if (names.length === 0) {
|
|
2013
|
-
logError(
|
|
1801
|
+
logError(
|
|
1802
|
+
"No component names specified. Usage: npx @work-rjkashyap/unified-ui add <component...>",
|
|
1803
|
+
);
|
|
2014
1804
|
return;
|
|
2015
1805
|
}
|
|
2016
|
-
|
|
2017
1806
|
// Resolve the full dependency tree
|
|
2018
|
-
logStep(
|
|
1807
|
+
logStep(
|
|
1808
|
+
"🔍",
|
|
1809
|
+
`Resolving dependencies for: ${c("cyan", names.join(", "))}...`,
|
|
1810
|
+
);
|
|
2019
1811
|
const tree = await resolveFullDependencyTree(names, REGISTRY_BASE_URL);
|
|
2020
|
-
|
|
2021
1812
|
if (tree.size === 0) {
|
|
2022
1813
|
logError("No components resolved. Check the names and try again.");
|
|
2023
1814
|
return;
|
|
2024
1815
|
}
|
|
2025
|
-
|
|
2026
1816
|
// Summarize what will be installed
|
|
2027
1817
|
const components = [];
|
|
2028
1818
|
const utils = [];
|
|
2029
1819
|
const styles = [];
|
|
2030
1820
|
const allNpmDeps = new Set();
|
|
2031
|
-
|
|
2032
1821
|
for (const [name, item] of tree) {
|
|
2033
1822
|
switch (item.type) {
|
|
2034
1823
|
case "unified-ui:component":
|
|
@@ -2041,12 +1830,10 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2041
1830
|
styles.push(name);
|
|
2042
1831
|
break;
|
|
2043
1832
|
}
|
|
2044
|
-
|
|
2045
1833
|
for (const dep of item.dependencies || []) {
|
|
2046
1834
|
allNpmDeps.add(dep);
|
|
2047
1835
|
}
|
|
2048
1836
|
}
|
|
2049
|
-
|
|
2050
1837
|
log();
|
|
2051
1838
|
if (components.length > 0) {
|
|
2052
1839
|
logStep("🧩", `Components: ${c("cyan", components.join(", "))}`);
|
|
@@ -2058,7 +1845,6 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2058
1845
|
logStep("📦", `Packages: ${c("dim", [...allNpmDeps].join(", "))}`);
|
|
2059
1846
|
}
|
|
2060
1847
|
log();
|
|
2061
|
-
|
|
2062
1848
|
// Confirm unless --yes
|
|
2063
1849
|
if (!flags.yes) {
|
|
2064
1850
|
const proceed = await confirm("Proceed with installation?");
|
|
@@ -2069,11 +1855,9 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2069
1855
|
}
|
|
2070
1856
|
log();
|
|
2071
1857
|
}
|
|
2072
|
-
|
|
2073
1858
|
// Write files
|
|
2074
1859
|
const results = [];
|
|
2075
1860
|
const overwrite = flags.overwrite || false;
|
|
2076
|
-
|
|
2077
1861
|
for (const [_name, item] of tree) {
|
|
2078
1862
|
for (const file of item.files) {
|
|
2079
1863
|
const targetPath = resolveTargetPath(config, file);
|
|
@@ -2081,30 +1865,23 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2081
1865
|
results.push(result);
|
|
2082
1866
|
}
|
|
2083
1867
|
}
|
|
2084
|
-
|
|
2085
1868
|
// Report file results
|
|
2086
1869
|
const created = results.filter((r) => r.status === "created");
|
|
2087
1870
|
const skipped = results.filter((r) => r.status === "skipped");
|
|
2088
|
-
|
|
2089
1871
|
for (const r of created) {
|
|
2090
|
-
logStep("✓", c("green", r.path.replace(config.root
|
|
1872
|
+
logStep("✓", c("green", r.path.replace(`${config.root}/`, "")));
|
|
2091
1873
|
}
|
|
2092
|
-
|
|
2093
1874
|
if (skipped.length > 0) {
|
|
2094
1875
|
log();
|
|
2095
1876
|
for (const r of skipped) {
|
|
2096
1877
|
logStep(
|
|
2097
1878
|
"↩",
|
|
2098
|
-
`${c("dim", r.path.replace(config.root
|
|
1879
|
+
`${c("dim", r.path.replace(`${config.root}/`, ""))} ${c("yellow", "(exists, skipped)")}`,
|
|
2099
1880
|
);
|
|
2100
1881
|
}
|
|
2101
1882
|
log();
|
|
2102
|
-
logStep(
|
|
2103
|
-
"💡",
|
|
2104
|
-
c("dim", "Use --overwrite to replace existing files."),
|
|
2105
|
-
);
|
|
1883
|
+
logStep("💡", c("dim", "Use --overwrite to replace existing files."));
|
|
2106
1884
|
}
|
|
2107
|
-
|
|
2108
1885
|
// Install npm dependencies
|
|
2109
1886
|
const depsToInstall = [...allNpmDeps].filter((dep) => {
|
|
2110
1887
|
// Check if already in package.json
|
|
@@ -2122,28 +1899,20 @@ async function cmdAdd(names, flags = {}) {
|
|
|
2122
1899
|
return true;
|
|
2123
1900
|
}
|
|
2124
1901
|
});
|
|
2125
|
-
|
|
2126
1902
|
if (depsToInstall.length > 0) {
|
|
2127
1903
|
log();
|
|
2128
1904
|
await installNpmDeps(depsToInstall, config.root);
|
|
2129
1905
|
}
|
|
2130
|
-
|
|
2131
1906
|
log();
|
|
2132
|
-
logStep(
|
|
2133
|
-
"🎉",
|
|
2134
|
-
c("green", `Done! ${created.length} file(s) added.`),
|
|
2135
|
-
);
|
|
1907
|
+
logStep("🎉", c("green", `Done! ${created.length} file(s) added.`));
|
|
2136
1908
|
log();
|
|
2137
1909
|
}
|
|
2138
|
-
|
|
2139
1910
|
async function cmdList() {
|
|
2140
1911
|
log();
|
|
2141
1912
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Available components")}`);
|
|
2142
1913
|
log();
|
|
2143
|
-
|
|
2144
1914
|
try {
|
|
2145
1915
|
const index = await fetchJSON(`${REGISTRY_BASE_URL}/index.json`);
|
|
2146
|
-
|
|
2147
1916
|
// Group by category
|
|
2148
1917
|
const groups = {};
|
|
2149
1918
|
for (const item of index.items) {
|
|
@@ -2152,13 +1921,11 @@ async function cmdList() {
|
|
|
2152
1921
|
if (!groups[cat]) groups[cat] = [];
|
|
2153
1922
|
groups[cat].push(item);
|
|
2154
1923
|
}
|
|
2155
|
-
|
|
2156
1924
|
// Find category labels
|
|
2157
1925
|
const catLabels = {};
|
|
2158
1926
|
for (const cat of index.categories || []) {
|
|
2159
1927
|
catLabels[cat.name] = cat.label;
|
|
2160
1928
|
}
|
|
2161
|
-
|
|
2162
1929
|
for (const [cat, items] of Object.entries(groups)) {
|
|
2163
1930
|
log(` ${c("bold", catLabels[cat] || cat)}`);
|
|
2164
1931
|
for (const item of items) {
|
|
@@ -2166,27 +1933,27 @@ async function cmdList() {
|
|
|
2166
1933
|
item.registryDependencies?.length > 0
|
|
2167
1934
|
? c("dim", ` → ${item.registryDependencies.join(", ")}`)
|
|
2168
1935
|
: "";
|
|
2169
|
-
log(
|
|
1936
|
+
log(
|
|
1937
|
+
` ${c("cyan", item.name.padEnd(22))} ${c("dim", item.description || "")}${deps}`,
|
|
1938
|
+
);
|
|
2170
1939
|
}
|
|
2171
1940
|
log();
|
|
2172
1941
|
}
|
|
2173
|
-
|
|
2174
1942
|
log(` ${c("dim", `${index.totalItems} items total`)}`);
|
|
2175
1943
|
log();
|
|
2176
|
-
log(
|
|
1944
|
+
log(
|
|
1945
|
+
` ${c("dim", "Add a component:")} npx @work-rjkashyap/unified-ui add ${c("cyan", "<name>")}`,
|
|
1946
|
+
);
|
|
2177
1947
|
log();
|
|
2178
1948
|
} catch (err) {
|
|
2179
1949
|
logError(`Could not fetch registry: ${err.message}`);
|
|
2180
1950
|
}
|
|
2181
1951
|
}
|
|
2182
|
-
|
|
2183
1952
|
async function cmdDiff(names) {
|
|
2184
1953
|
log();
|
|
2185
1954
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Diff local vs registry")}`);
|
|
2186
1955
|
log();
|
|
2187
|
-
|
|
2188
1956
|
const config = loadConfig();
|
|
2189
|
-
|
|
2190
1957
|
for (const name of names) {
|
|
2191
1958
|
try {
|
|
2192
1959
|
const item = await fetchJSON(`${REGISTRY_BASE_URL}/${name}.json`);
|
|
@@ -2196,10 +1963,8 @@ async function cmdDiff(names) {
|
|
|
2196
1963
|
logStep("✗", `${c("red", name)}: not installed locally`);
|
|
2197
1964
|
continue;
|
|
2198
1965
|
}
|
|
2199
|
-
|
|
2200
1966
|
const localContent = readFileSync(targetPath, "utf-8");
|
|
2201
1967
|
const registryContent = rewriteContentImports(file.content, config);
|
|
2202
|
-
|
|
2203
1968
|
if (localContent === registryContent) {
|
|
2204
1969
|
logStep("✓", `${c("green", name)}: up to date`);
|
|
2205
1970
|
} else {
|
|
@@ -2210,37 +1975,65 @@ async function cmdDiff(names) {
|
|
|
2210
1975
|
logStep("✗", `${c("red", name)}: ${err.message}`);
|
|
2211
1976
|
}
|
|
2212
1977
|
}
|
|
2213
|
-
|
|
2214
1978
|
log();
|
|
2215
1979
|
}
|
|
2216
|
-
|
|
2217
1980
|
function cmdHelp() {
|
|
2218
1981
|
log();
|
|
2219
1982
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Component Registry CLI")}`);
|
|
2220
1983
|
log();
|
|
2221
1984
|
log(" Usage:");
|
|
2222
|
-
log(
|
|
1985
|
+
log(
|
|
1986
|
+
` ${c("cyan", "npx @work-rjkashyap/unified-ui")} ${c("green", "<command>")} [options]`,
|
|
1987
|
+
);
|
|
2223
1988
|
log();
|
|
2224
1989
|
log(" Commands:");
|
|
2225
|
-
log(
|
|
2226
|
-
|
|
2227
|
-
|
|
1990
|
+
log(
|
|
1991
|
+
` ${c("green", "init")} Initialize existing project (copy-paste mode)`,
|
|
1992
|
+
);
|
|
1993
|
+
log(
|
|
1994
|
+
` ${c("green", "init")} -t <framework> Scaffold a new project with full setup`,
|
|
1995
|
+
);
|
|
1996
|
+
log(
|
|
1997
|
+
` ${c("green", "add")} <component...> Add component(s) with dependencies`,
|
|
1998
|
+
);
|
|
2228
1999
|
log(` ${c("green", "add")} --all Add all components`);
|
|
2229
|
-
log(
|
|
2230
|
-
|
|
2231
|
-
|
|
2000
|
+
log(
|
|
2001
|
+
` ${c("green", "list")} List all available components`,
|
|
2002
|
+
);
|
|
2003
|
+
log(
|
|
2004
|
+
` ${c("green", "diff")} <component...> Compare local files with registry`,
|
|
2005
|
+
);
|
|
2006
|
+
log(
|
|
2007
|
+
` ${c("green", "help")} Show this help message`,
|
|
2008
|
+
);
|
|
2232
2009
|
log();
|
|
2233
2010
|
log(" Templates (for init -t):");
|
|
2234
|
-
log(
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
log(
|
|
2011
|
+
log(
|
|
2012
|
+
` ${c("cyan", "vite-react")} Vite + React 19 SPA with full component library`,
|
|
2013
|
+
);
|
|
2014
|
+
log(
|
|
2015
|
+
` ${c("cyan", "nextjs")} Next.js App Router with SSR + full component library`,
|
|
2016
|
+
);
|
|
2017
|
+
log(
|
|
2018
|
+
` ${c("cyan", "vuejs")} Vue 3 + Vite with UI components & Tailwind theme`,
|
|
2019
|
+
);
|
|
2020
|
+
log(
|
|
2021
|
+
` ${c("cyan", "laravel-blade")} Laravel with Blade UI components & Tailwind theme`,
|
|
2022
|
+
);
|
|
2238
2023
|
log();
|
|
2239
2024
|
log(" Options:");
|
|
2240
|
-
log(
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
log(
|
|
2025
|
+
log(
|
|
2026
|
+
` ${c("yellow", "--template, -t")} Framework template (with 'init')`,
|
|
2027
|
+
);
|
|
2028
|
+
log(
|
|
2029
|
+
` ${c("yellow", "--yes, -y")} Skip confirmation prompts`,
|
|
2030
|
+
);
|
|
2031
|
+
log(
|
|
2032
|
+
` ${c("yellow", "--overwrite")} Overwrite existing files`,
|
|
2033
|
+
);
|
|
2034
|
+
log(
|
|
2035
|
+
` ${c("yellow", "--all")} Add all components (with 'add')`,
|
|
2036
|
+
);
|
|
2244
2037
|
log();
|
|
2245
2038
|
log(" Examples:");
|
|
2246
2039
|
log(` ${c("dim", "# Scaffold a new project (interactive)")} `);
|
|
@@ -2265,17 +2058,14 @@ function cmdHelp() {
|
|
|
2265
2058
|
log(` Registry: ${c("cyan", REGISTRY_BASE_URL)}`);
|
|
2266
2059
|
log();
|
|
2267
2060
|
}
|
|
2268
|
-
|
|
2269
2061
|
// ---------------------------------------------------------------------------
|
|
2270
2062
|
// Argument parsing
|
|
2271
2063
|
// ---------------------------------------------------------------------------
|
|
2272
|
-
|
|
2273
2064
|
function parseArgs(argv) {
|
|
2274
2065
|
const args = argv.slice(2);
|
|
2275
2066
|
const command = args[0];
|
|
2276
2067
|
const flags = {};
|
|
2277
2068
|
const positional = [];
|
|
2278
|
-
|
|
2279
2069
|
for (let i = 1; i < args.length; i++) {
|
|
2280
2070
|
const arg = args[i];
|
|
2281
2071
|
if (arg === "--yes" || arg === "-y") {
|
|
@@ -2303,17 +2093,13 @@ function parseArgs(argv) {
|
|
|
2303
2093
|
positional.push(arg);
|
|
2304
2094
|
}
|
|
2305
2095
|
}
|
|
2306
|
-
|
|
2307
2096
|
return { command, positional, flags };
|
|
2308
2097
|
}
|
|
2309
|
-
|
|
2310
2098
|
// ---------------------------------------------------------------------------
|
|
2311
2099
|
// Main
|
|
2312
2100
|
// ---------------------------------------------------------------------------
|
|
2313
|
-
|
|
2314
2101
|
async function main() {
|
|
2315
2102
|
const { command, positional, flags } = parseArgs(process.argv);
|
|
2316
|
-
|
|
2317
2103
|
// Allow custom registry URL
|
|
2318
2104
|
if (flags.registryUrl) {
|
|
2319
2105
|
// Override the global — we use a let binding workaround below.
|
|
@@ -2321,7 +2107,6 @@ async function main() {
|
|
|
2321
2107
|
// For simplicity, we set an env var that's already checked at the top.
|
|
2322
2108
|
process.env.UNIFIED_UI_REGISTRY_URL = flags.registryUrl;
|
|
2323
2109
|
}
|
|
2324
|
-
|
|
2325
2110
|
switch (command) {
|
|
2326
2111
|
case "init":
|
|
2327
2112
|
await cmdInit(positional, flags);
|
|
@@ -2343,11 +2128,12 @@ async function main() {
|
|
|
2343
2128
|
cmdHelp();
|
|
2344
2129
|
break;
|
|
2345
2130
|
default:
|
|
2346
|
-
logError(
|
|
2131
|
+
logError(
|
|
2132
|
+
`Unknown command: "${command}". Run "npx @work-rjkashyap/unified-ui help" for usage.`,
|
|
2133
|
+
);
|
|
2347
2134
|
process.exit(1);
|
|
2348
2135
|
}
|
|
2349
2136
|
}
|
|
2350
|
-
|
|
2351
2137
|
main().catch((err) => {
|
|
2352
2138
|
logError(err.message);
|
|
2353
2139
|
process.exit(1);
|