@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/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>
@@ -434,26 +385,24 @@ createRoot(document.getElementById("root")!).render(
434
385
  </StrictMode>,
435
386
  );
436
387
  `,
437
- "src/App.tsx": `import { Button, Heading, Text } from "@work-rjkashyap/unified-ui/components";
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
- <Text variant="muted">
398
+ <Body color="muted">
449
399
  Your starter project is ready. Start building!
450
- </Text>
400
+ </Body>
451
401
  </div>
452
-
453
402
  <div className="flex items-center justify-center gap-3">
454
- <Button variant="default">Get Started</Button>
403
+ <Button variant="primary">Get Started</Button>
455
404
  <Button
456
- variant="outline"
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 { Button, Heading, Text } from "@work-rjkashyap/unified-ui/components";
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
- <Text variant="muted">
467
+ <Body color="muted">
526
468
  Your Next.js project is ready. Start building!
527
- </Text>
469
+ </Body>
528
470
  </div>
529
-
530
471
  <div className="flex items-center justify-center gap-3">
531
- <Button variant="default">Get Started</Button>
472
+ <Button variant="primary">Get Started</Button>
532
473
  <Button
533
- variant="outline"
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 = typeof flags.template === "string" ? flags.template : null;
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(`Expected directory "${projectName}" was not created by the scaffolding tool.`);
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).replace(" add ", " add -D ").replace(" install ", " install -D ");
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('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
+ );
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('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
+ );
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(` ${c("dim", "Note: This template includes design tokens (CSS variables + Tailwind")}`);
1897
- 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
+ );
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(` ${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
+ );
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(` ${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
+ );
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("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
+ );
2012
1804
  return;
2013
1805
  }
2014
-
2015
1806
  // Resolve the full dependency tree
2016
- logStep("🔍", `Resolving dependencies for: ${c("cyan", names.join(", "))}...`);
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 + "/", ""))} ${c("yellow", "(exists, skipped)")}`,
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(` ${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
+ );
2168
1939
  }
2169
1940
  log();
2170
1941
  }
2171
-
2172
1942
  log(` ${c("dim", `${index.totalItems} items total`)}`);
2173
1943
  log();
2174
- 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
+ );
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(` ${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
+ );
2221
1988
  log();
2222
1989
  log(" Commands:");
2223
- log(` ${c("green", "init")} Initialize existing project (copy-paste mode)`);
2224
- log(` ${c("green", "init")} -t <framework> Scaffold a new project with full setup`);
2225
- 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
+ );
2226
1999
  log(` ${c("green", "add")} --all Add all components`);
2227
- log(` ${c("green", "list")} List all available components`);
2228
- log(` ${c("green", "diff")} <component...> Compare local files with registry`);
2229
- 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
+ );
2230
2009
  log();
2231
2010
  log(" Templates (for init -t):");
2232
- log(` ${c("cyan", "vite-react")} Vite + React 19 SPA with full component library`);
2233
- log(` ${c("cyan", "nextjs")} Next.js App Router with SSR + full component library`);
2234
- log(` ${c("cyan", "vuejs")} Vue 3 + Vite with UI components & Tailwind theme`);
2235
- 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
+ );
2236
2023
  log();
2237
2024
  log(" Options:");
2238
- log(` ${c("yellow", "--template, -t")} Framework template (with 'init')`);
2239
- log(` ${c("yellow", "--yes, -y")} Skip confirmation prompts`);
2240
- log(` ${c("yellow", "--overwrite")} Overwrite existing files`);
2241
- 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
+ );
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(`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
+ );
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);