@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/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-rajeshwar.vercel.app/r/<name>.json
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-rajeshwar.vercel.app/r";
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) => `npm create vite@latest ${name} -- --template react-ts`,
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) => `npx create-next-app@latest ${name} --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --yes`,
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-rajeshwar.vercel.app/r/schema/config.json",
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(` ${c("cyan", String(i + 1))}. ${c("bold", opt.label.padEnd(18))} ${c("dim", opt.description)}`);
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}):`) } `, (answer) => {
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(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
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) + "\n");
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"))) return "bun";
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 (err) {
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 = typeof flags.template === "string" ? flags.template : null;
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(`Expected directory "${projectName}" was not created by the scaffolding tool.`);
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).replace(" add ", " add -D ").replace(" install ", " install -D ");
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('git commit -m "chore: add Unified UI design system" --no-verify', targetDir, "pipe");
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('git commit -m "chore: initial commit with Unified UI" --no-verify', targetDir, "pipe");
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(` ${c("dim", "Note: This template includes design tokens (CSS variables + Tailwind")}`);
1899
- log(` ${c("dim", "utilities) only. React components are not available in this framework.")}`);
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(` ${c("cyan", "npx @work-rjkashyap/unified-ui add button card badge")}`);
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(` ${c("cyan", "npx @work-rjkashyap/unified-ui add card badge tabs")}`);
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("No component names specified. Usage: npx @work-rjkashyap/unified-ui add <component...>");
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("🔍", `Resolving dependencies for: ${c("cyan", names.join(", "))}...`);
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 + "/", ""))} ${c("yellow", "(exists, skipped)")}`,
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(` ${c("cyan", item.name.padEnd(22))} ${c("dim", item.description || "")}${deps}`);
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(` ${c("dim", "Add a component:")} npx @work-rjkashyap/unified-ui add ${c("cyan", "<name>")}`);
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(` ${c("cyan", "npx @work-rjkashyap/unified-ui")} ${c("green", "<command>")} [options]`);
1985
+ log(
1986
+ ` ${c("cyan", "npx @work-rjkashyap/unified-ui")} ${c("green", "<command>")} [options]`,
1987
+ );
2223
1988
  log();
2224
1989
  log(" Commands:");
2225
- log(` ${c("green", "init")} Initialize existing project (copy-paste mode)`);
2226
- log(` ${c("green", "init")} -t <framework> Scaffold a new project with full setup`);
2227
- log(` ${c("green", "add")} <component...> Add component(s) with dependencies`);
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(` ${c("green", "list")} List all available components`);
2230
- log(` ${c("green", "diff")} <component...> Compare local files with registry`);
2231
- log(` ${c("green", "help")} Show this help message`);
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(` ${c("cyan", "vite-react")} Vite + React 19 SPA with full component library`);
2235
- log(` ${c("cyan", "nextjs")} Next.js App Router with SSR + full component library`);
2236
- log(` ${c("cyan", "vuejs")} Vue 3 + Vite with UI components & Tailwind theme`);
2237
- log(` ${c("cyan", "laravel-blade")} Laravel with Blade UI components & Tailwind theme`);
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(` ${c("yellow", "--template, -t")} Framework template (with 'init')`);
2241
- log(` ${c("yellow", "--yes, -y")} Skip confirmation prompts`);
2242
- log(` ${c("yellow", "--overwrite")} Overwrite existing files`);
2243
- log(` ${c("yellow", "--all")} Add all components (with 'add')`);
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(`Unknown command: "${command}". Run "npx @work-rjkashyap/unified-ui help" for usage.`);
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);