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