@work-rjkashyap/unified-ui 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -2
- package/bin/cli.mjs +1635 -4
- package/dist/{chunk-BB5WRUPU.mjs → chunk-AQJ7H5SF.mjs} +1138 -281
- package/dist/{chunk-3K2CXZ6H.cjs → chunk-EUHL6H76.cjs} +1155 -277
- package/dist/components.cjs +373 -289
- package/dist/components.d.cts +780 -37
- package/dist/components.d.ts +780 -37
- package/dist/components.mjs +1 -1
- package/dist/index.cjs +373 -289
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/theme.d.cts +1 -1
- package/dist/theme.d.ts +1 -1
- package/package.json +4 -3
package/bin/cli.mjs
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
29
29
|
import { dirname, join, resolve } from "node:path";
|
|
30
30
|
import { createInterface } from "node:readline";
|
|
31
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
31
32
|
|
|
32
33
|
// ---------------------------------------------------------------------------
|
|
33
34
|
// Config
|
|
@@ -39,6 +40,45 @@ const REGISTRY_BASE_URL =
|
|
|
39
40
|
|
|
40
41
|
const CONFIG_FILE = "unified-ui.json";
|
|
41
42
|
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Starter kit templates
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
const FRAMEWORKS = [
|
|
48
|
+
{
|
|
49
|
+
name: "vite-react",
|
|
50
|
+
label: "Vite + React",
|
|
51
|
+
description: "Vite + React 19 SPA with full component library",
|
|
52
|
+
scaffoldCmd: (name) => `npm create vite@latest ${name} -- --template react-ts`,
|
|
53
|
+
deps: ["@work-rjkashyap/unified-ui"],
|
|
54
|
+
devDeps: ["@tailwindcss/vite", "tailwindcss"],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "nextjs",
|
|
58
|
+
label: "Next.js",
|
|
59
|
+
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`,
|
|
61
|
+
deps: ["@work-rjkashyap/unified-ui", "next-themes"],
|
|
62
|
+
devDeps: [],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "vuejs",
|
|
66
|
+
label: "Vue.js",
|
|
67
|
+
description: "Vue 3 + Vite with UI components & Tailwind theme",
|
|
68
|
+
scaffoldCmd: (name) => `npm create vue@latest ${name} -- --typescript`,
|
|
69
|
+
deps: ["@work-rjkashyap/unified-ui", "clsx", "tailwind-merge"],
|
|
70
|
+
devDeps: ["@tailwindcss/vite", "tailwindcss"],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "laravel-blade",
|
|
74
|
+
label: "Laravel Blade",
|
|
75
|
+
description: "Laravel with Blade UI components & Tailwind theme",
|
|
76
|
+
scaffoldCmd: (name) => `composer create-project laravel/laravel ${name}`,
|
|
77
|
+
deps: ["@work-rjkashyap/unified-ui"],
|
|
78
|
+
devDeps: ["@tailwindcss/vite", "tailwindcss"],
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
42
82
|
const DEFAULT_CONFIG = {
|
|
43
83
|
$schema: "https://unified-ui-rajeshwar.vercel.app/r/schema/config.json",
|
|
44
84
|
srcDir: "src",
|
|
@@ -90,6 +130,53 @@ async function confirm(question) {
|
|
|
90
130
|
});
|
|
91
131
|
}
|
|
92
132
|
|
|
133
|
+
async function promptText(question, defaultValue = "") {
|
|
134
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
135
|
+
const hint = defaultValue ? ` ${c("dim", `(${defaultValue})`)}` : "";
|
|
136
|
+
return new Promise((res) => {
|
|
137
|
+
rl.question(` ${question}${hint} `, (answer) => {
|
|
138
|
+
rl.close();
|
|
139
|
+
res(answer.trim() || defaultValue);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function promptSelect(question, options) {
|
|
145
|
+
log(` ${question}`);
|
|
146
|
+
log();
|
|
147
|
+
for (let i = 0; i < options.length; i++) {
|
|
148
|
+
const opt = options[i];
|
|
149
|
+
log(` ${c("cyan", String(i + 1))}. ${c("bold", opt.label.padEnd(18))} ${c("dim", opt.description)}`);
|
|
150
|
+
}
|
|
151
|
+
log();
|
|
152
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
153
|
+
return new Promise((res) => {
|
|
154
|
+
rl.question(` ${c("dim", `Select (1-${options.length}):`) } `, (answer) => {
|
|
155
|
+
rl.close();
|
|
156
|
+
const idx = parseInt(answer.trim(), 10) - 1;
|
|
157
|
+
res(idx >= 0 && idx < options.length ? options[idx] : null);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function runCmd(cmd, cwd, stdio = "inherit") {
|
|
163
|
+
try {
|
|
164
|
+
execSync(cmd, { cwd, stdio });
|
|
165
|
+
return true;
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function ensureDir(dir) {
|
|
172
|
+
mkdirSync(dir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function writeOverlay(targetPath, content) {
|
|
176
|
+
ensureDir(dirname(targetPath));
|
|
177
|
+
writeFileSync(targetPath, content);
|
|
178
|
+
}
|
|
179
|
+
|
|
93
180
|
async function fetchJSON(url) {
|
|
94
181
|
const response = await fetch(url);
|
|
95
182
|
if (!response.ok) {
|
|
@@ -306,7 +393,1523 @@ function writeFile(targetPath, content, config, overwrite = false) {
|
|
|
306
393
|
// Commands
|
|
307
394
|
// ---------------------------------------------------------------------------
|
|
308
395
|
|
|
309
|
-
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// Starter kit overlays (embedded content)
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
|
|
400
|
+
const OVERLAYS = {
|
|
401
|
+
"vite-react": {
|
|
402
|
+
files: {
|
|
403
|
+
"vite.config.ts": `import tailwindcss from "@tailwindcss/vite";
|
|
404
|
+
import react from "@vitejs/plugin-react";
|
|
405
|
+
import { defineConfig } from "vite";
|
|
406
|
+
|
|
407
|
+
export default defineConfig({
|
|
408
|
+
plugins: [react(), tailwindcss()],
|
|
409
|
+
resolve: {
|
|
410
|
+
alias: {
|
|
411
|
+
"@": "/src",
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
`,
|
|
416
|
+
"src/index.css": `@import "tailwindcss";
|
|
417
|
+
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
418
|
+
|
|
419
|
+
body {
|
|
420
|
+
min-height: 100svh;
|
|
421
|
+
}
|
|
422
|
+
`,
|
|
423
|
+
"src/main.tsx": `import { StrictMode } from "react";
|
|
424
|
+
import { createRoot } from "react-dom/client";
|
|
425
|
+
import { DSThemeProvider } from "@work-rjkashyap/unified-ui/theme";
|
|
426
|
+
import App from "./App";
|
|
427
|
+
import "./index.css";
|
|
428
|
+
|
|
429
|
+
createRoot(document.getElementById("root")!).render(
|
|
430
|
+
<StrictMode>
|
|
431
|
+
<DSThemeProvider manageHtmlClass>
|
|
432
|
+
<App />
|
|
433
|
+
</DSThemeProvider>
|
|
434
|
+
</StrictMode>,
|
|
435
|
+
);
|
|
436
|
+
`,
|
|
437
|
+
"src/App.tsx": `import { Button, Heading, Text } from "@work-rjkashyap/unified-ui/components";
|
|
438
|
+
import { useDSTheme } from "@work-rjkashyap/unified-ui/theme";
|
|
439
|
+
|
|
440
|
+
function App() {
|
|
441
|
+
const { theme, setTheme } = useDSTheme();
|
|
442
|
+
|
|
443
|
+
return (
|
|
444
|
+
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-8 text-foreground">
|
|
445
|
+
<div className="w-full max-w-md space-y-6 rounded-lg border border-border bg-card p-8">
|
|
446
|
+
<div className="space-y-2 text-center">
|
|
447
|
+
<Heading level={1}>Unified UI</Heading>
|
|
448
|
+
<Text variant="muted">
|
|
449
|
+
Your starter project is ready. Start building!
|
|
450
|
+
</Text>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
<div className="flex items-center justify-center gap-3">
|
|
454
|
+
<Button variant="default">Get Started</Button>
|
|
455
|
+
<Button
|
|
456
|
+
variant="outline"
|
|
457
|
+
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
458
|
+
>
|
|
459
|
+
Toggle Theme
|
|
460
|
+
</Button>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export default App;
|
|
468
|
+
`,
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
nextjs: {
|
|
473
|
+
files: {
|
|
474
|
+
"src/app/globals.css": `@import "tailwindcss";
|
|
475
|
+
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
476
|
+
|
|
477
|
+
body {
|
|
478
|
+
min-height: 100svh;
|
|
479
|
+
}
|
|
480
|
+
`,
|
|
481
|
+
"src/app/layout.tsx": `import type { Metadata } from "next";
|
|
482
|
+
import { ThemeProvider } from "next-themes";
|
|
483
|
+
import { DSThemeProvider } from "@work-rjkashyap/unified-ui/theme";
|
|
484
|
+
import "./globals.css";
|
|
485
|
+
|
|
486
|
+
export const metadata: Metadata = {
|
|
487
|
+
title: "Unified UI App",
|
|
488
|
+
description: "Built with Unified UI and Next.js",
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
export default function RootLayout({
|
|
492
|
+
children,
|
|
493
|
+
}: {
|
|
494
|
+
children: React.ReactNode;
|
|
495
|
+
}) {
|
|
496
|
+
return (
|
|
497
|
+
<html lang="en" suppressHydrationWarning>
|
|
498
|
+
<body>
|
|
499
|
+
<ThemeProvider
|
|
500
|
+
attribute="class"
|
|
501
|
+
defaultTheme="system"
|
|
502
|
+
enableSystem
|
|
503
|
+
disableTransitionOnChange
|
|
504
|
+
>
|
|
505
|
+
<DSThemeProvider>{children}</DSThemeProvider>
|
|
506
|
+
</ThemeProvider>
|
|
507
|
+
</body>
|
|
508
|
+
</html>
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
`,
|
|
512
|
+
"src/app/page.tsx": `"use client";
|
|
513
|
+
|
|
514
|
+
import { Button, Heading, Text } from "@work-rjkashyap/unified-ui/components";
|
|
515
|
+
import { useDSTheme } from "@work-rjkashyap/unified-ui/theme";
|
|
516
|
+
|
|
517
|
+
export default function Home() {
|
|
518
|
+
const { theme, setTheme } = useDSTheme();
|
|
519
|
+
|
|
520
|
+
return (
|
|
521
|
+
<div className="flex min-h-svh flex-col items-center justify-center gap-6 bg-background p-8 text-foreground">
|
|
522
|
+
<div className="w-full max-w-md space-y-6 rounded-lg border border-border bg-card p-8">
|
|
523
|
+
<div className="space-y-2 text-center">
|
|
524
|
+
<Heading level={1}>Unified UI</Heading>
|
|
525
|
+
<Text variant="muted">
|
|
526
|
+
Your Next.js project is ready. Start building!
|
|
527
|
+
</Text>
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
<div className="flex items-center justify-center gap-3">
|
|
531
|
+
<Button variant="default">Get Started</Button>
|
|
532
|
+
<Button
|
|
533
|
+
variant="outline"
|
|
534
|
+
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
535
|
+
>
|
|
536
|
+
Toggle Theme
|
|
537
|
+
</Button>
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
</div>
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
`,
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
vuejs: {
|
|
548
|
+
files: {
|
|
549
|
+
"vite.config.ts": `import tailwindcss from "@tailwindcss/vite";
|
|
550
|
+
import vue from "@vitejs/plugin-vue";
|
|
551
|
+
import { defineConfig } from "vite";
|
|
552
|
+
|
|
553
|
+
export default defineConfig({
|
|
554
|
+
plugins: [vue(), tailwindcss()],
|
|
555
|
+
resolve: {
|
|
556
|
+
alias: {
|
|
557
|
+
"@": "/src",
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
`,
|
|
562
|
+
"src/style.css": `@import "tailwindcss";
|
|
563
|
+
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
564
|
+
|
|
565
|
+
body {
|
|
566
|
+
min-height: 100svh;
|
|
567
|
+
}
|
|
568
|
+
`,
|
|
569
|
+
"src/main.ts": `import { createApp } from "vue";
|
|
570
|
+
import App from "./App.vue";
|
|
571
|
+
import "./style.css";
|
|
572
|
+
|
|
573
|
+
createApp(App).mount("#app");
|
|
574
|
+
`,
|
|
575
|
+
"src/lib/cn.ts": `import { type ClassValue, clsx } from "clsx";
|
|
576
|
+
import { twMerge } from "tailwind-merge";
|
|
577
|
+
|
|
578
|
+
export function cn(...inputs: ClassValue[]) {
|
|
579
|
+
return twMerge(clsx(inputs));
|
|
580
|
+
}
|
|
581
|
+
`,
|
|
582
|
+
"src/App.vue": `<script setup lang="ts">
|
|
583
|
+
import ThemeToggle from "./components/ThemeToggle.vue";
|
|
584
|
+
import {
|
|
585
|
+
UiButton,
|
|
586
|
+
UiBadge,
|
|
587
|
+
UiCard,
|
|
588
|
+
UiCardHeader,
|
|
589
|
+
UiCardBody,
|
|
590
|
+
UiCardFooter,
|
|
591
|
+
UiInput,
|
|
592
|
+
UiAlert,
|
|
593
|
+
UiHeading,
|
|
594
|
+
UiText,
|
|
595
|
+
} from "./components/ui";
|
|
596
|
+
import { ref } from "vue";
|
|
597
|
+
|
|
598
|
+
const email = ref("");
|
|
599
|
+
</script>
|
|
600
|
+
|
|
601
|
+
<template>
|
|
602
|
+
<div
|
|
603
|
+
class="flex min-h-svh flex-col items-center justify-center gap-8 bg-background p-8 text-foreground"
|
|
604
|
+
>
|
|
605
|
+
<UiCard class="w-full max-w-lg">
|
|
606
|
+
<UiCardHeader>
|
|
607
|
+
<UiHeading :level="2">Unified UI</UiHeading>
|
|
608
|
+
<UiText variant="bodySm" color="muted">
|
|
609
|
+
Your Vue.js project is ready with components. Start building!
|
|
610
|
+
</UiText>
|
|
611
|
+
</UiCardHeader>
|
|
612
|
+
|
|
613
|
+
<UiCardBody class="space-y-6">
|
|
614
|
+
<!-- Buttons -->
|
|
615
|
+
<div class="space-y-2">
|
|
616
|
+
<UiText variant="label">Buttons</UiText>
|
|
617
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
618
|
+
<UiButton variant="primary">Primary</UiButton>
|
|
619
|
+
<UiButton variant="secondary">Secondary</UiButton>
|
|
620
|
+
<UiButton variant="ghost">Ghost</UiButton>
|
|
621
|
+
<UiButton variant="danger" size="sm">Danger</UiButton>
|
|
622
|
+
<UiButton variant="primary" :loading="true" size="sm">Loading</UiButton>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
|
|
626
|
+
<!-- Badges -->
|
|
627
|
+
<div class="space-y-2">
|
|
628
|
+
<UiText variant="label">Badges</UiText>
|
|
629
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
630
|
+
<UiBadge variant="default">Default</UiBadge>
|
|
631
|
+
<UiBadge variant="primary">Primary</UiBadge>
|
|
632
|
+
<UiBadge variant="success">Success</UiBadge>
|
|
633
|
+
<UiBadge variant="warning">Warning</UiBadge>
|
|
634
|
+
<UiBadge variant="danger">Danger</UiBadge>
|
|
635
|
+
<UiBadge variant="info">Info</UiBadge>
|
|
636
|
+
<UiBadge variant="outline">Outline</UiBadge>
|
|
637
|
+
</div>
|
|
638
|
+
</div>
|
|
639
|
+
|
|
640
|
+
<!-- Input -->
|
|
641
|
+
<div class="space-y-2">
|
|
642
|
+
<UiText variant="label">Input</UiText>
|
|
643
|
+
<UiInput v-model="email" placeholder="you@example.com" />
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<!-- Alert -->
|
|
647
|
+
<UiAlert variant="info" title="All set!">
|
|
648
|
+
Your design system components are working in Vue.
|
|
649
|
+
</UiAlert>
|
|
650
|
+
</UiCardBody>
|
|
651
|
+
|
|
652
|
+
<UiCardFooter class="justify-between">
|
|
653
|
+
<UiButton variant="primary">Get Started</UiButton>
|
|
654
|
+
<ThemeToggle />
|
|
655
|
+
</UiCardFooter>
|
|
656
|
+
</UiCard>
|
|
657
|
+
</div>
|
|
658
|
+
</template>
|
|
659
|
+
`,
|
|
660
|
+
"src/components/ThemeToggle.vue": `<script setup lang="ts">
|
|
661
|
+
import { ref, onMounted } from "vue";
|
|
662
|
+
|
|
663
|
+
const theme = ref<"light" | "dark">("light");
|
|
664
|
+
|
|
665
|
+
onMounted(() => {
|
|
666
|
+
const stored = localStorage.getItem("theme");
|
|
667
|
+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
668
|
+
theme.value =
|
|
669
|
+
(stored as "light" | "dark") || (prefersDark ? "dark" : "light");
|
|
670
|
+
applyTheme();
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
function toggle() {
|
|
674
|
+
theme.value = theme.value === "dark" ? "light" : "dark";
|
|
675
|
+
applyTheme();
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function applyTheme() {
|
|
679
|
+
document.documentElement.classList.toggle("dark", theme.value === "dark");
|
|
680
|
+
localStorage.setItem("theme", theme.value);
|
|
681
|
+
}
|
|
682
|
+
</script>
|
|
683
|
+
|
|
684
|
+
<template>
|
|
685
|
+
<button
|
|
686
|
+
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"
|
|
687
|
+
@click="toggle"
|
|
688
|
+
>
|
|
689
|
+
Toggle {{ theme === "dark" ? "Light" : "Dark" }}
|
|
690
|
+
</button>
|
|
691
|
+
</template>
|
|
692
|
+
`,
|
|
693
|
+
"src/components/ui/index.ts": `export { default as UiButton } from "./Button.vue";
|
|
694
|
+
export { default as UiBadge } from "./Badge.vue";
|
|
695
|
+
export { default as UiCard } from "./Card.vue";
|
|
696
|
+
export { default as UiCardHeader } from "./CardHeader.vue";
|
|
697
|
+
export { default as UiCardBody } from "./CardBody.vue";
|
|
698
|
+
export { default as UiCardFooter } from "./CardFooter.vue";
|
|
699
|
+
export { default as UiInput } from "./Input.vue";
|
|
700
|
+
export { default as UiAlert } from "./Alert.vue";
|
|
701
|
+
export { default as UiHeading } from "./Heading.vue";
|
|
702
|
+
export { default as UiText } from "./Text.vue";
|
|
703
|
+
`,
|
|
704
|
+
"src/components/ui/Button.vue": `<script setup lang="ts">
|
|
705
|
+
import { computed, type HTMLAttributes } from "vue";
|
|
706
|
+
import { cn } from "@/lib/cn";
|
|
707
|
+
|
|
708
|
+
type Variant = "primary" | "secondary" | "ghost" | "danger";
|
|
709
|
+
type Size = "sm" | "md" | "lg";
|
|
710
|
+
|
|
711
|
+
interface Props {
|
|
712
|
+
variant?: Variant;
|
|
713
|
+
size?: Size;
|
|
714
|
+
fullWidth?: boolean;
|
|
715
|
+
iconOnly?: boolean;
|
|
716
|
+
loading?: boolean;
|
|
717
|
+
disabled?: boolean;
|
|
718
|
+
as?: string;
|
|
719
|
+
class?: HTMLAttributes["class"];
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
723
|
+
variant: "primary",
|
|
724
|
+
size: "md",
|
|
725
|
+
as: "button",
|
|
726
|
+
fullWidth: false,
|
|
727
|
+
iconOnly: false,
|
|
728
|
+
loading: false,
|
|
729
|
+
disabled: false,
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
const variantClasses: Record<Variant, string> = {
|
|
733
|
+
primary:
|
|
734
|
+
"bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active",
|
|
735
|
+
secondary:
|
|
736
|
+
"bg-secondary text-secondary-foreground border border-border hover:bg-secondary-hover active:bg-secondary-active",
|
|
737
|
+
ghost:
|
|
738
|
+
"bg-transparent text-foreground hover:bg-muted hover:text-foreground active:bg-secondary-active",
|
|
739
|
+
danger:
|
|
740
|
+
"bg-danger text-danger-foreground hover:bg-danger-hover active:bg-danger-active",
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
const sizeClasses: Record<Size, string> = {
|
|
744
|
+
sm: "h-8 px-3 text-xs gap-1.5",
|
|
745
|
+
md: "h-[var(--ds-control-height,36px)] px-[var(--ds-padding-button-x,16px)] text-sm gap-2",
|
|
746
|
+
lg: "h-10 px-5 text-sm gap-2",
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
const iconOnlySizeClasses: Record<Size, string> = {
|
|
750
|
+
sm: "w-8 !px-0",
|
|
751
|
+
md: "w-9 !px-0",
|
|
752
|
+
lg: "w-10 !px-0",
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
const classes = computed(() =>
|
|
756
|
+
cn(
|
|
757
|
+
// base
|
|
758
|
+
"inline-flex items-center justify-center gap-2 text-sm font-medium leading-5 rounded-md",
|
|
759
|
+
"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))]",
|
|
760
|
+
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
|
|
761
|
+
"disabled:pointer-events-none disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed select-none",
|
|
762
|
+
"active:scale-[0.98] disabled:active:scale-100",
|
|
763
|
+
// variant
|
|
764
|
+
variantClasses[props.variant],
|
|
765
|
+
// size
|
|
766
|
+
sizeClasses[props.size],
|
|
767
|
+
// icon only
|
|
768
|
+
props.iconOnly && iconOnlySizeClasses[props.size],
|
|
769
|
+
// fullWidth
|
|
770
|
+
props.fullWidth && "w-full",
|
|
771
|
+
// loading
|
|
772
|
+
props.loading && "pointer-events-none opacity-70",
|
|
773
|
+
props.class,
|
|
774
|
+
),
|
|
775
|
+
);
|
|
776
|
+
</script>
|
|
777
|
+
|
|
778
|
+
<template>
|
|
779
|
+
<component
|
|
780
|
+
:is="as"
|
|
781
|
+
:class="classes"
|
|
782
|
+
:disabled="disabled || loading"
|
|
783
|
+
data-ds
|
|
784
|
+
data-ds-component="button"
|
|
785
|
+
:data-ds-loading="loading || undefined"
|
|
786
|
+
>
|
|
787
|
+
<svg
|
|
788
|
+
v-if="loading"
|
|
789
|
+
class="h-4 w-4 animate-spin"
|
|
790
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
791
|
+
fill="none"
|
|
792
|
+
viewBox="0 0 24 24"
|
|
793
|
+
>
|
|
794
|
+
<circle
|
|
795
|
+
class="opacity-25"
|
|
796
|
+
cx="12"
|
|
797
|
+
cy="12"
|
|
798
|
+
r="10"
|
|
799
|
+
stroke="currentColor"
|
|
800
|
+
stroke-width="4"
|
|
801
|
+
/>
|
|
802
|
+
<path
|
|
803
|
+
class="opacity-75"
|
|
804
|
+
fill="currentColor"
|
|
805
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
806
|
+
/>
|
|
807
|
+
</svg>
|
|
808
|
+
<slot />
|
|
809
|
+
</component>
|
|
810
|
+
</template>
|
|
811
|
+
`,
|
|
812
|
+
"src/components/ui/Badge.vue": `<script setup lang="ts">
|
|
813
|
+
import { computed, type HTMLAttributes } from "vue";
|
|
814
|
+
import { cn } from "@/lib/cn";
|
|
815
|
+
|
|
816
|
+
type Variant =
|
|
817
|
+
| "default"
|
|
818
|
+
| "primary"
|
|
819
|
+
| "secondary"
|
|
820
|
+
| "success"
|
|
821
|
+
| "warning"
|
|
822
|
+
| "danger"
|
|
823
|
+
| "info"
|
|
824
|
+
| "outline";
|
|
825
|
+
type Size = "sm" | "md" | "lg";
|
|
826
|
+
|
|
827
|
+
interface Props {
|
|
828
|
+
variant?: Variant;
|
|
829
|
+
size?: Size;
|
|
830
|
+
dismissible?: boolean;
|
|
831
|
+
class?: HTMLAttributes["class"];
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
835
|
+
variant: "default",
|
|
836
|
+
size: "md",
|
|
837
|
+
dismissible: false,
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
const emit = defineEmits<{ dismiss: [] }>();
|
|
841
|
+
|
|
842
|
+
const variantClasses: Record<Variant, string> = {
|
|
843
|
+
default: "bg-muted text-foreground border border-transparent",
|
|
844
|
+
primary:
|
|
845
|
+
"bg-primary-muted text-primary-muted-foreground border border-transparent",
|
|
846
|
+
secondary: "bg-secondary text-secondary-foreground border border-border",
|
|
847
|
+
success:
|
|
848
|
+
"bg-success-muted text-success-muted-foreground border border-transparent",
|
|
849
|
+
warning:
|
|
850
|
+
"bg-warning-muted text-warning-muted-foreground border border-transparent",
|
|
851
|
+
danger:
|
|
852
|
+
"bg-danger-muted text-danger-muted-foreground border border-transparent",
|
|
853
|
+
info: "bg-info-muted text-info-muted-foreground border border-transparent",
|
|
854
|
+
outline: "bg-transparent text-foreground border border-border",
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
const sizeClasses: Record<Size, string> = {
|
|
858
|
+
sm: "px-2 py-0.5 text-[11px] gap-1",
|
|
859
|
+
md: "px-2.5 py-1 text-xs gap-1.5",
|
|
860
|
+
lg: "px-3 py-1.5 text-sm gap-2",
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
const classes = computed(() =>
|
|
864
|
+
cn(
|
|
865
|
+
"inline-flex items-center gap-1.5 rounded-full font-medium leading-none whitespace-nowrap",
|
|
866
|
+
"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))]",
|
|
867
|
+
"select-none shrink-0",
|
|
868
|
+
variantClasses[props.variant],
|
|
869
|
+
sizeClasses[props.size],
|
|
870
|
+
props.class,
|
|
871
|
+
),
|
|
872
|
+
);
|
|
873
|
+
</script>
|
|
874
|
+
|
|
875
|
+
<template>
|
|
876
|
+
<span :class="classes" data-ds data-ds-component="badge">
|
|
877
|
+
<slot />
|
|
878
|
+
<button
|
|
879
|
+
v-if="dismissible"
|
|
880
|
+
class="ml-0.5 inline-flex items-center justify-center rounded-full hover:bg-black/10 dark:hover:bg-white/10"
|
|
881
|
+
:class="size === 'sm' ? 'h-3 w-3' : size === 'md' ? 'h-3.5 w-3.5' : 'h-4 w-4'"
|
|
882
|
+
@click.stop="emit('dismiss')"
|
|
883
|
+
aria-label="Dismiss"
|
|
884
|
+
>
|
|
885
|
+
<svg
|
|
886
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
887
|
+
viewBox="0 0 24 24"
|
|
888
|
+
fill="none"
|
|
889
|
+
stroke="currentColor"
|
|
890
|
+
stroke-width="2"
|
|
891
|
+
stroke-linecap="round"
|
|
892
|
+
stroke-linejoin="round"
|
|
893
|
+
class="h-full w-full"
|
|
894
|
+
>
|
|
895
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
896
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
897
|
+
</svg>
|
|
898
|
+
</button>
|
|
899
|
+
</span>
|
|
900
|
+
</template>
|
|
901
|
+
`,
|
|
902
|
+
"src/components/ui/Card.vue": `<script setup lang="ts">
|
|
903
|
+
import { computed, provide, type HTMLAttributes, type InjectionKey } from "vue";
|
|
904
|
+
import { cn } from "@/lib/cn";
|
|
905
|
+
|
|
906
|
+
type Variant = "default" | "outlined" | "elevated" | "interactive";
|
|
907
|
+
type Padding = "compact" | "comfortable";
|
|
908
|
+
|
|
909
|
+
interface Props {
|
|
910
|
+
variant?: Variant;
|
|
911
|
+
padding?: Padding;
|
|
912
|
+
fullWidth?: boolean;
|
|
913
|
+
as?: string;
|
|
914
|
+
class?: HTMLAttributes["class"];
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
918
|
+
variant: "default",
|
|
919
|
+
padding: "compact",
|
|
920
|
+
as: "div",
|
|
921
|
+
fullWidth: false,
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
export const cardPaddingKey = Symbol("cardPadding") as InjectionKey<Padding>;
|
|
925
|
+
provide(cardPaddingKey, props.padding);
|
|
926
|
+
|
|
927
|
+
const variantClasses: Record<Variant, string> = {
|
|
928
|
+
default: "bg-surface border border-border",
|
|
929
|
+
outlined: "bg-transparent border border-border-strong",
|
|
930
|
+
elevated: "bg-surface-raised border border-border-muted shadow-md",
|
|
931
|
+
interactive:
|
|
932
|
+
"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
|
+
};
|
|
934
|
+
|
|
935
|
+
const classes = computed(() =>
|
|
936
|
+
cn(
|
|
937
|
+
"flex flex-col rounded-md overflow-hidden text-sm text-foreground",
|
|
938
|
+
variantClasses[props.variant],
|
|
939
|
+
props.fullWidth && "w-full",
|
|
940
|
+
props.class,
|
|
941
|
+
),
|
|
942
|
+
);
|
|
943
|
+
</script>
|
|
944
|
+
|
|
945
|
+
<template>
|
|
946
|
+
<component :is="as" :class="classes" data-ds data-ds-component="card">
|
|
947
|
+
<slot />
|
|
948
|
+
</component>
|
|
949
|
+
</template>
|
|
950
|
+
`,
|
|
951
|
+
"src/components/ui/CardHeader.vue": `<script setup lang="ts">
|
|
952
|
+
import { inject, computed, type HTMLAttributes } from "vue";
|
|
953
|
+
import { cn } from "@/lib/cn";
|
|
954
|
+
import { cardPaddingKey } from "./Card.vue";
|
|
955
|
+
|
|
956
|
+
interface Props {
|
|
957
|
+
class?: HTMLAttributes["class"];
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const props = defineProps<Props>();
|
|
961
|
+
const padding = inject(cardPaddingKey, "compact");
|
|
962
|
+
|
|
963
|
+
const classes = computed(() =>
|
|
964
|
+
cn(
|
|
965
|
+
"flex flex-col",
|
|
966
|
+
padding === "comfortable" ? "px-6 pt-6 gap-1.5" : "px-[var(--ds-padding-card,16px)] pt-[var(--ds-padding-card,16px)] gap-1",
|
|
967
|
+
props.class,
|
|
968
|
+
),
|
|
969
|
+
);
|
|
970
|
+
</script>
|
|
971
|
+
|
|
972
|
+
<template>
|
|
973
|
+
<div :class="classes" data-ds data-ds-component="card-header">
|
|
974
|
+
<slot />
|
|
975
|
+
</div>
|
|
976
|
+
</template>
|
|
977
|
+
`,
|
|
978
|
+
"src/components/ui/CardBody.vue": `<script setup lang="ts">
|
|
979
|
+
import { inject, computed, type HTMLAttributes } from "vue";
|
|
980
|
+
import { cn } from "@/lib/cn";
|
|
981
|
+
import { cardPaddingKey } from "./Card.vue";
|
|
982
|
+
|
|
983
|
+
interface Props {
|
|
984
|
+
class?: HTMLAttributes["class"];
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const props = defineProps<Props>();
|
|
988
|
+
const padding = inject(cardPaddingKey, "compact");
|
|
989
|
+
|
|
990
|
+
const classes = computed(() =>
|
|
991
|
+
cn(
|
|
992
|
+
"flex flex-col flex-1",
|
|
993
|
+
padding === "comfortable" ? "px-6 py-4 gap-4" : "px-[var(--ds-padding-card,16px)] py-3 gap-[var(--ds-gap-default,0.75rem)]",
|
|
994
|
+
props.class,
|
|
995
|
+
),
|
|
996
|
+
);
|
|
997
|
+
</script>
|
|
998
|
+
|
|
999
|
+
<template>
|
|
1000
|
+
<div :class="classes" data-ds data-ds-component="card-body">
|
|
1001
|
+
<slot />
|
|
1002
|
+
</div>
|
|
1003
|
+
</template>
|
|
1004
|
+
`,
|
|
1005
|
+
"src/components/ui/CardFooter.vue": `<script setup lang="ts">
|
|
1006
|
+
import { inject, computed, type HTMLAttributes } from "vue";
|
|
1007
|
+
import { cn } from "@/lib/cn";
|
|
1008
|
+
import { cardPaddingKey } from "./Card.vue";
|
|
1009
|
+
|
|
1010
|
+
interface Props {
|
|
1011
|
+
class?: HTMLAttributes["class"];
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const props = defineProps<Props>();
|
|
1015
|
+
const padding = inject(cardPaddingKey, "compact");
|
|
1016
|
+
|
|
1017
|
+
const classes = computed(() =>
|
|
1018
|
+
cn(
|
|
1019
|
+
"flex items-center",
|
|
1020
|
+
padding === "comfortable" ? "px-6 pb-6 gap-3" : "px-[var(--ds-padding-card,16px)] pb-[var(--ds-padding-card,16px)] gap-2",
|
|
1021
|
+
props.class,
|
|
1022
|
+
),
|
|
1023
|
+
);
|
|
1024
|
+
</script>
|
|
1025
|
+
|
|
1026
|
+
<template>
|
|
1027
|
+
<div :class="classes" data-ds data-ds-component="card-footer">
|
|
1028
|
+
<slot />
|
|
1029
|
+
</div>
|
|
1030
|
+
</template>
|
|
1031
|
+
`,
|
|
1032
|
+
"src/components/ui/Input.vue": `<script setup lang="ts">
|
|
1033
|
+
import { computed, type HTMLAttributes } from "vue";
|
|
1034
|
+
import { cn } from "@/lib/cn";
|
|
1035
|
+
|
|
1036
|
+
type Variant = "default" | "error" | "success";
|
|
1037
|
+
type Size = "sm" | "md" | "lg";
|
|
1038
|
+
|
|
1039
|
+
interface Props {
|
|
1040
|
+
variant?: Variant;
|
|
1041
|
+
size?: Size;
|
|
1042
|
+
disabled?: boolean;
|
|
1043
|
+
class?: HTMLAttributes["class"];
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
1047
|
+
variant: "default",
|
|
1048
|
+
size: "md",
|
|
1049
|
+
disabled: false,
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
const model = defineModel<string>();
|
|
1053
|
+
|
|
1054
|
+
const variantClasses: Record<Variant, string> = {
|
|
1055
|
+
default:
|
|
1056
|
+
"border-input hover:border-border-strong focus-visible:border-border-strong",
|
|
1057
|
+
error:
|
|
1058
|
+
"border-danger text-foreground focus-visible:border-danger placeholder:text-input-placeholder",
|
|
1059
|
+
success:
|
|
1060
|
+
"border-success text-foreground focus-visible:border-success placeholder:text-input-placeholder",
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
const sizeClasses: Record<Size, string> = {
|
|
1064
|
+
sm: "h-8 px-2.5 text-xs",
|
|
1065
|
+
md: "h-[var(--ds-control-height,36px)] px-3 text-sm",
|
|
1066
|
+
lg: "h-10 px-3.5 text-sm",
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
const classes = computed(() =>
|
|
1070
|
+
cn(
|
|
1071
|
+
"flex w-full text-sm leading-5 rounded-md border bg-background text-input-foreground",
|
|
1072
|
+
"placeholder:text-input-placeholder",
|
|
1073
|
+
"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))]",
|
|
1074
|
+
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
|
|
1075
|
+
"disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-disabled disabled:text-disabled-foreground",
|
|
1076
|
+
"read-only:bg-muted read-only:cursor-default",
|
|
1077
|
+
variantClasses[props.variant],
|
|
1078
|
+
sizeClasses[props.size],
|
|
1079
|
+
props.class,
|
|
1080
|
+
),
|
|
1081
|
+
);
|
|
1082
|
+
</script>
|
|
1083
|
+
|
|
1084
|
+
<template>
|
|
1085
|
+
<input
|
|
1086
|
+
v-model="model"
|
|
1087
|
+
:class="classes"
|
|
1088
|
+
:disabled="disabled"
|
|
1089
|
+
data-ds
|
|
1090
|
+
data-ds-component="input"
|
|
1091
|
+
/>
|
|
1092
|
+
</template>
|
|
1093
|
+
`,
|
|
1094
|
+
"src/components/ui/Alert.vue": `<script setup lang="ts">
|
|
1095
|
+
import { computed, ref, type HTMLAttributes } from "vue";
|
|
1096
|
+
import { cn } from "@/lib/cn";
|
|
1097
|
+
|
|
1098
|
+
type Variant = "info" | "success" | "warning" | "danger" | "default";
|
|
1099
|
+
|
|
1100
|
+
interface Props {
|
|
1101
|
+
variant?: Variant;
|
|
1102
|
+
title?: string;
|
|
1103
|
+
dismissible?: boolean;
|
|
1104
|
+
class?: HTMLAttributes["class"];
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
1108
|
+
variant: "info",
|
|
1109
|
+
dismissible: false,
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
const dismissed = ref(false);
|
|
1113
|
+
|
|
1114
|
+
const variantClasses: Record<Variant, string> = {
|
|
1115
|
+
info: "bg-info-muted text-info-muted-foreground border-info/20",
|
|
1116
|
+
success: "bg-success-muted text-success-muted-foreground border-success/20",
|
|
1117
|
+
warning: "bg-warning-muted text-warning-muted-foreground border-warning/20",
|
|
1118
|
+
danger: "bg-danger-muted text-danger-muted-foreground border-danger/20",
|
|
1119
|
+
default: "bg-muted text-muted-foreground border-border",
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
const iconColorClasses: Record<Variant, string> = {
|
|
1123
|
+
info: "text-info",
|
|
1124
|
+
success: "text-success",
|
|
1125
|
+
warning: "text-warning",
|
|
1126
|
+
danger: "text-danger",
|
|
1127
|
+
default: "text-muted-foreground",
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
const classes = computed(() =>
|
|
1131
|
+
cn(
|
|
1132
|
+
"relative flex gap-3 rounded-md p-4 text-sm leading-5 border",
|
|
1133
|
+
"transition-colors duration-[var(--duration-fast,150ms)]",
|
|
1134
|
+
variantClasses[props.variant],
|
|
1135
|
+
props.class,
|
|
1136
|
+
),
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
// SVG icon paths by variant
|
|
1140
|
+
const iconPaths: Record<Variant, string> = {
|
|
1141
|
+
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",
|
|
1142
|
+
success: "M9 12l2 2 4-4m6 2a10 10 0 11-20 0 10 10 0 0120 0z",
|
|
1143
|
+
warning: "M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z",
|
|
1144
|
+
danger: "M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a10 10 0 11-20 0 10 10 0 0120 0z",
|
|
1145
|
+
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
|
+
};
|
|
1147
|
+
</script>
|
|
1148
|
+
|
|
1149
|
+
<template>
|
|
1150
|
+
<div
|
|
1151
|
+
v-if="!dismissed"
|
|
1152
|
+
:class="classes"
|
|
1153
|
+
role="alert"
|
|
1154
|
+
data-ds
|
|
1155
|
+
data-ds-component="alert"
|
|
1156
|
+
>
|
|
1157
|
+
<svg
|
|
1158
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1159
|
+
viewBox="0 0 24 24"
|
|
1160
|
+
fill="none"
|
|
1161
|
+
stroke="currentColor"
|
|
1162
|
+
stroke-width="2"
|
|
1163
|
+
stroke-linecap="round"
|
|
1164
|
+
stroke-linejoin="round"
|
|
1165
|
+
class="h-4 w-4 shrink-0 mt-0.5"
|
|
1166
|
+
:class="iconColorClasses[variant]"
|
|
1167
|
+
>
|
|
1168
|
+
<path :d="iconPaths[variant]" />
|
|
1169
|
+
</svg>
|
|
1170
|
+
<div class="flex-1 space-y-1">
|
|
1171
|
+
<p v-if="title" class="font-medium leading-5">{{ title }}</p>
|
|
1172
|
+
<div class="text-sm leading-5">
|
|
1173
|
+
<slot />
|
|
1174
|
+
</div>
|
|
1175
|
+
</div>
|
|
1176
|
+
<button
|
|
1177
|
+
v-if="dismissible"
|
|
1178
|
+
class="absolute top-3 right-3 inline-flex items-center justify-center rounded-md h-6 w-6 hover:bg-black/10 dark:hover:bg-white/10"
|
|
1179
|
+
@click="dismissed = true"
|
|
1180
|
+
aria-label="Dismiss"
|
|
1181
|
+
>
|
|
1182
|
+
<svg
|
|
1183
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1184
|
+
viewBox="0 0 24 24"
|
|
1185
|
+
fill="none"
|
|
1186
|
+
stroke="currentColor"
|
|
1187
|
+
stroke-width="2"
|
|
1188
|
+
stroke-linecap="round"
|
|
1189
|
+
stroke-linejoin="round"
|
|
1190
|
+
class="h-4 w-4"
|
|
1191
|
+
>
|
|
1192
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
1193
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
1194
|
+
</svg>
|
|
1195
|
+
</button>
|
|
1196
|
+
</div>
|
|
1197
|
+
</template>
|
|
1198
|
+
`,
|
|
1199
|
+
"src/components/ui/Heading.vue": `<script setup lang="ts">
|
|
1200
|
+
import { computed, type HTMLAttributes } from "vue";
|
|
1201
|
+
import { cn } from "@/lib/cn";
|
|
1202
|
+
|
|
1203
|
+
type Level = 1 | 2 | 3 | 4;
|
|
1204
|
+
type Color = "default" | "foreground" | "muted" | "primary";
|
|
1205
|
+
|
|
1206
|
+
interface Props {
|
|
1207
|
+
level?: Level;
|
|
1208
|
+
color?: Color;
|
|
1209
|
+
class?: HTMLAttributes["class"];
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
1213
|
+
level: 1,
|
|
1214
|
+
color: "default",
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
const levelClasses: Record<Level, string> = {
|
|
1218
|
+
1: "text-[30px] leading-[36px] font-bold tracking-tight",
|
|
1219
|
+
2: "text-[24px] leading-[32px] font-semibold tracking-tight",
|
|
1220
|
+
3: "text-[20px] leading-[28px] font-semibold tracking-normal",
|
|
1221
|
+
4: "text-[18px] leading-[28px] font-medium tracking-normal",
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
const colorClasses: Record<Color, string> = {
|
|
1225
|
+
default: "text-foreground",
|
|
1226
|
+
foreground: "text-foreground",
|
|
1227
|
+
muted: "text-muted-foreground",
|
|
1228
|
+
primary: "text-primary",
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
const tag = computed(() => \`h\${props.level}\` as const);
|
|
1232
|
+
|
|
1233
|
+
const classes = computed(() =>
|
|
1234
|
+
cn(levelClasses[props.level], colorClasses[props.color], props.class),
|
|
1235
|
+
);
|
|
1236
|
+
</script>
|
|
1237
|
+
|
|
1238
|
+
<template>
|
|
1239
|
+
<component :is="tag" :class="classes" data-ds data-ds-component="heading">
|
|
1240
|
+
<slot />
|
|
1241
|
+
</component>
|
|
1242
|
+
</template>
|
|
1243
|
+
`,
|
|
1244
|
+
"src/components/ui/Text.vue": `<script setup lang="ts">
|
|
1245
|
+
import { computed, type HTMLAttributes } from "vue";
|
|
1246
|
+
import { cn } from "@/lib/cn";
|
|
1247
|
+
|
|
1248
|
+
type Variant = "body" | "bodySm" | "caption" | "label" | "overline" | "code";
|
|
1249
|
+
type Color =
|
|
1250
|
+
| "default"
|
|
1251
|
+
| "foreground"
|
|
1252
|
+
| "muted"
|
|
1253
|
+
| "primary"
|
|
1254
|
+
| "success"
|
|
1255
|
+
| "warning"
|
|
1256
|
+
| "danger"
|
|
1257
|
+
| "info";
|
|
1258
|
+
|
|
1259
|
+
interface Props {
|
|
1260
|
+
variant?: Variant;
|
|
1261
|
+
color?: Color;
|
|
1262
|
+
as?: string;
|
|
1263
|
+
class?: HTMLAttributes["class"];
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
1267
|
+
variant: "body",
|
|
1268
|
+
color: "default",
|
|
1269
|
+
as: "p",
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
const variantClasses: Record<Variant, string> = {
|
|
1273
|
+
body: "text-[16px] leading-[24px] font-normal tracking-normal",
|
|
1274
|
+
bodySm: "text-[14px] leading-[20px] font-normal tracking-normal",
|
|
1275
|
+
caption:
|
|
1276
|
+
"text-[12px] leading-[16px] font-normal tracking-wide text-muted-foreground",
|
|
1277
|
+
label: "text-[14px] leading-[20px] font-medium tracking-normal",
|
|
1278
|
+
overline:
|
|
1279
|
+
"text-[12px] leading-[16px] font-semibold tracking-wider uppercase text-muted-foreground",
|
|
1280
|
+
code: "text-[14px] leading-[20px] font-normal tracking-normal font-mono",
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
const colorClasses: Record<Color, string> = {
|
|
1284
|
+
default: "text-foreground",
|
|
1285
|
+
foreground: "text-foreground",
|
|
1286
|
+
muted: "text-muted-foreground",
|
|
1287
|
+
primary: "text-primary",
|
|
1288
|
+
success: "text-success",
|
|
1289
|
+
warning: "text-warning",
|
|
1290
|
+
danger: "text-danger",
|
|
1291
|
+
info: "text-info",
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
const classes = computed(() =>
|
|
1295
|
+
cn(variantClasses[props.variant], colorClasses[props.color], props.class),
|
|
1296
|
+
);
|
|
1297
|
+
</script>
|
|
1298
|
+
|
|
1299
|
+
<template>
|
|
1300
|
+
<component :is="as" :class="classes" data-ds data-ds-component="text">
|
|
1301
|
+
<slot />
|
|
1302
|
+
</component>
|
|
1303
|
+
</template>
|
|
1304
|
+
`,
|
|
1305
|
+
},
|
|
1306
|
+
},
|
|
1307
|
+
|
|
1308
|
+
"laravel-blade": {
|
|
1309
|
+
files: {
|
|
1310
|
+
"vite.config.js": `import tailwindcss from "@tailwindcss/vite";
|
|
1311
|
+
import laravel from "laravel-vite-plugin";
|
|
1312
|
+
import { defineConfig } from "vite";
|
|
1313
|
+
|
|
1314
|
+
export default defineConfig({
|
|
1315
|
+
plugins: [
|
|
1316
|
+
laravel({
|
|
1317
|
+
input: ["resources/css/app.css", "resources/js/app.js"],
|
|
1318
|
+
refresh: true,
|
|
1319
|
+
}),
|
|
1320
|
+
tailwindcss(),
|
|
1321
|
+
],
|
|
1322
|
+
});
|
|
1323
|
+
`,
|
|
1324
|
+
"resources/css/app.css": `@import "tailwindcss";
|
|
1325
|
+
@import "@work-rjkashyap/unified-ui/styles.css";
|
|
1326
|
+
`,
|
|
1327
|
+
"resources/js/app.js": `// Unified UI — Laravel Blade Starter
|
|
1328
|
+
// Design tokens are loaded via CSS. This file handles theme toggling.
|
|
1329
|
+
|
|
1330
|
+
function initTheme() {
|
|
1331
|
+
const stored = localStorage.getItem("theme");
|
|
1332
|
+
const prefersDark = window.matchMedia(
|
|
1333
|
+
"(prefers-color-scheme: dark)",
|
|
1334
|
+
).matches;
|
|
1335
|
+
const theme = stored || (prefersDark ? "dark" : "light");
|
|
1336
|
+
document.documentElement.classList.toggle("dark", theme === "dark");
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function toggleTheme() {
|
|
1340
|
+
const isDark = document.documentElement.classList.toggle("dark");
|
|
1341
|
+
localStorage.setItem("theme", isDark ? "dark" : "light");
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Initialize on load
|
|
1345
|
+
initTheme();
|
|
1346
|
+
|
|
1347
|
+
// Expose globally for Blade onclick handlers
|
|
1348
|
+
window.toggleTheme = toggleTheme;
|
|
1349
|
+
`,
|
|
1350
|
+
"resources/views/layouts/app.blade.php": `<!DOCTYPE html>
|
|
1351
|
+
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
|
1352
|
+
<head>
|
|
1353
|
+
<meta charset="utf-8">
|
|
1354
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1355
|
+
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
1356
|
+
<title>{{ config('app.name', 'Unified UI') }}</title>
|
|
1357
|
+
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
1358
|
+
</head>
|
|
1359
|
+
<body class="min-h-svh bg-background text-foreground antialiased">
|
|
1360
|
+
@yield('content')
|
|
1361
|
+
</body>
|
|
1362
|
+
</html>
|
|
1363
|
+
`,
|
|
1364
|
+
"resources/views/welcome.blade.php": `@extends('layouts.app')
|
|
1365
|
+
|
|
1366
|
+
@section('content')
|
|
1367
|
+
<div class="flex min-h-svh flex-col items-center justify-center gap-8 p-8">
|
|
1368
|
+
<x-ui.card class="w-full max-w-lg">
|
|
1369
|
+
<x-ui.card-header>
|
|
1370
|
+
<x-ui.heading :level="2">Unified UI</x-ui.heading>
|
|
1371
|
+
<x-ui.text variant="bodySm" color="muted">
|
|
1372
|
+
Your Laravel project is ready with components. Start building!
|
|
1373
|
+
</x-ui.text>
|
|
1374
|
+
</x-ui.card-header>
|
|
1375
|
+
|
|
1376
|
+
<x-ui.card-body class="space-y-6">
|
|
1377
|
+
{{-- Buttons --}}
|
|
1378
|
+
<div class="space-y-2">
|
|
1379
|
+
<x-ui.text variant="label">Buttons</x-ui.text>
|
|
1380
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
1381
|
+
<x-ui.button variant="primary">Primary</x-ui.button>
|
|
1382
|
+
<x-ui.button variant="secondary">Secondary</x-ui.button>
|
|
1383
|
+
<x-ui.button variant="ghost">Ghost</x-ui.button>
|
|
1384
|
+
<x-ui.button variant="danger" size="sm">Danger</x-ui.button>
|
|
1385
|
+
<x-ui.button variant="primary" :loading="true" size="sm">Loading</x-ui.button>
|
|
1386
|
+
</div>
|
|
1387
|
+
</div>
|
|
1388
|
+
|
|
1389
|
+
{{-- Badges --}}
|
|
1390
|
+
<div class="space-y-2">
|
|
1391
|
+
<x-ui.text variant="label">Badges</x-ui.text>
|
|
1392
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
1393
|
+
<x-ui.badge variant="default">Default</x-ui.badge>
|
|
1394
|
+
<x-ui.badge variant="primary">Primary</x-ui.badge>
|
|
1395
|
+
<x-ui.badge variant="success">Success</x-ui.badge>
|
|
1396
|
+
<x-ui.badge variant="warning">Warning</x-ui.badge>
|
|
1397
|
+
<x-ui.badge variant="danger">Danger</x-ui.badge>
|
|
1398
|
+
<x-ui.badge variant="info">Info</x-ui.badge>
|
|
1399
|
+
<x-ui.badge variant="outline">Outline</x-ui.badge>
|
|
1400
|
+
</div>
|
|
1401
|
+
</div>
|
|
1402
|
+
|
|
1403
|
+
{{-- Input --}}
|
|
1404
|
+
<div class="space-y-2">
|
|
1405
|
+
<x-ui.text variant="label">Input</x-ui.text>
|
|
1406
|
+
<x-ui.input placeholder="you@example.com" />
|
|
1407
|
+
</div>
|
|
1408
|
+
|
|
1409
|
+
{{-- Alert --}}
|
|
1410
|
+
<x-ui.alert variant="info" title="All set!">
|
|
1411
|
+
Your design system components are working in Laravel.
|
|
1412
|
+
</x-ui.alert>
|
|
1413
|
+
</x-ui.card-body>
|
|
1414
|
+
|
|
1415
|
+
<x-ui.card-footer class="justify-between">
|
|
1416
|
+
<x-ui.button variant="primary">Get Started</x-ui.button>
|
|
1417
|
+
<x-ui.button variant="secondary" onclick="toggleTheme()">Toggle Theme</x-ui.button>
|
|
1418
|
+
</x-ui.card-footer>
|
|
1419
|
+
</x-ui.card>
|
|
1420
|
+
</div>
|
|
1421
|
+
@endsection
|
|
1422
|
+
`,
|
|
1423
|
+
"resources/views/components/ui/button.blade.php": `@props([
|
|
1424
|
+
'variant' => 'primary',
|
|
1425
|
+
'size' => 'md',
|
|
1426
|
+
'as' => 'button',
|
|
1427
|
+
'fullWidth' => false,
|
|
1428
|
+
'iconOnly' => false,
|
|
1429
|
+
'loading' => false,
|
|
1430
|
+
'disabled' => false,
|
|
1431
|
+
])
|
|
1432
|
+
|
|
1433
|
+
@php
|
|
1434
|
+
$variants = [
|
|
1435
|
+
'primary' => 'bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active',
|
|
1436
|
+
'secondary' => 'bg-secondary text-secondary-foreground border border-border hover:bg-secondary-hover active:bg-secondary-active',
|
|
1437
|
+
'ghost' => 'bg-transparent text-foreground hover:bg-muted hover:text-foreground active:bg-secondary-active',
|
|
1438
|
+
'danger' => 'bg-danger text-danger-foreground hover:bg-danger-hover active:bg-danger-active',
|
|
1439
|
+
];
|
|
1440
|
+
|
|
1441
|
+
$sizes = [
|
|
1442
|
+
'sm' => 'h-8 px-3 text-xs gap-1.5',
|
|
1443
|
+
'md' => 'h-[var(--ds-control-height,36px)] px-[var(--ds-padding-button-x,16px)] text-sm gap-2',
|
|
1444
|
+
'lg' => 'h-10 px-5 text-sm gap-2',
|
|
1445
|
+
];
|
|
1446
|
+
|
|
1447
|
+
$iconOnlySizes = [
|
|
1448
|
+
'sm' => 'w-8 !px-0',
|
|
1449
|
+
'md' => 'w-9 !px-0',
|
|
1450
|
+
'lg' => 'w-10 !px-0',
|
|
1451
|
+
];
|
|
1452
|
+
|
|
1453
|
+
$classes = implode(' ', array_filter([
|
|
1454
|
+
'inline-flex items-center justify-center gap-2 text-sm font-medium leading-5 rounded-md',
|
|
1455
|
+
'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))]',
|
|
1456
|
+
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
|
|
1457
|
+
'disabled:pointer-events-none disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed select-none',
|
|
1458
|
+
'active:scale-[0.98] disabled:active:scale-100',
|
|
1459
|
+
$variants[$variant] ?? $variants['primary'],
|
|
1460
|
+
$sizes[$size] ?? $sizes['md'],
|
|
1461
|
+
$iconOnly ? ($iconOnlySizes[$size] ?? '') : '',
|
|
1462
|
+
$fullWidth ? 'w-full' : '',
|
|
1463
|
+
$loading ? 'pointer-events-none opacity-70' : '',
|
|
1464
|
+
]));
|
|
1465
|
+
@endphp
|
|
1466
|
+
|
|
1467
|
+
<{{ $as }}
|
|
1468
|
+
{{ $attributes->merge(['class' => $classes, 'disabled' => $disabled || $loading]) }}
|
|
1469
|
+
data-ds
|
|
1470
|
+
data-ds-component="button"
|
|
1471
|
+
@if($loading) data-ds-loading @endif
|
|
1472
|
+
>
|
|
1473
|
+
@if($loading)
|
|
1474
|
+
<svg class="h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
1475
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
1476
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
|
1477
|
+
</svg>
|
|
1478
|
+
@endif
|
|
1479
|
+
{{ $slot }}
|
|
1480
|
+
</{{ $as }}>
|
|
1481
|
+
`,
|
|
1482
|
+
"resources/views/components/ui/badge.blade.php": `@props([
|
|
1483
|
+
'variant' => 'default',
|
|
1484
|
+
'size' => 'md',
|
|
1485
|
+
'dismissible' => false,
|
|
1486
|
+
])
|
|
1487
|
+
|
|
1488
|
+
@php
|
|
1489
|
+
$variants = [
|
|
1490
|
+
'default' => 'bg-muted text-foreground border border-transparent',
|
|
1491
|
+
'primary' => 'bg-primary-muted text-primary-muted-foreground border border-transparent',
|
|
1492
|
+
'secondary' => 'bg-secondary text-secondary-foreground border border-border',
|
|
1493
|
+
'success' => 'bg-success-muted text-success-muted-foreground border border-transparent',
|
|
1494
|
+
'warning' => 'bg-warning-muted text-warning-muted-foreground border border-transparent',
|
|
1495
|
+
'danger' => 'bg-danger-muted text-danger-muted-foreground border border-transparent',
|
|
1496
|
+
'info' => 'bg-info-muted text-info-muted-foreground border border-transparent',
|
|
1497
|
+
'outline' => 'bg-transparent text-foreground border border-border',
|
|
1498
|
+
];
|
|
1499
|
+
|
|
1500
|
+
$sizes = [
|
|
1501
|
+
'sm' => 'px-2 py-0.5 text-[11px] gap-1',
|
|
1502
|
+
'md' => 'px-2.5 py-1 text-xs gap-1.5',
|
|
1503
|
+
'lg' => 'px-3 py-1.5 text-sm gap-2',
|
|
1504
|
+
];
|
|
1505
|
+
|
|
1506
|
+
$classes = implode(' ', [
|
|
1507
|
+
'inline-flex items-center gap-1.5 rounded-full font-medium leading-none whitespace-nowrap',
|
|
1508
|
+
'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))]',
|
|
1509
|
+
'select-none shrink-0',
|
|
1510
|
+
$variants[$variant] ?? $variants['default'],
|
|
1511
|
+
$sizes[$size] ?? $sizes['md'],
|
|
1512
|
+
]);
|
|
1513
|
+
@endphp
|
|
1514
|
+
|
|
1515
|
+
<span {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="badge">
|
|
1516
|
+
{{ $slot }}
|
|
1517
|
+
@if($dismissible)
|
|
1518
|
+
<button
|
|
1519
|
+
class="ml-0.5 inline-flex items-center justify-center rounded-full hover:bg-black/10 dark:hover:bg-white/10 {{ $size === 'sm' ? 'h-3 w-3' : ($size === 'md' ? 'h-3.5 w-3.5' : 'h-4 w-4') }}"
|
|
1520
|
+
onclick="this.closest('[data-ds-component=badge]').remove()"
|
|
1521
|
+
aria-label="Dismiss"
|
|
1522
|
+
>
|
|
1523
|
+
<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-full w-full"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
1524
|
+
</button>
|
|
1525
|
+
@endif
|
|
1526
|
+
</span>
|
|
1527
|
+
`,
|
|
1528
|
+
"resources/views/components/ui/card.blade.php": `@props([
|
|
1529
|
+
'variant' => 'default',
|
|
1530
|
+
'padding' => 'compact',
|
|
1531
|
+
'fullWidth' => false,
|
|
1532
|
+
'as' => 'div',
|
|
1533
|
+
])
|
|
1534
|
+
|
|
1535
|
+
@php
|
|
1536
|
+
$variants = [
|
|
1537
|
+
'default' => 'bg-surface border border-border',
|
|
1538
|
+
'outlined' => 'bg-transparent border border-border-strong',
|
|
1539
|
+
'elevated' => 'bg-surface-raised border border-border-muted shadow-md',
|
|
1540
|
+
'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
|
+
];
|
|
1542
|
+
|
|
1543
|
+
$classes = implode(' ', array_filter([
|
|
1544
|
+
'flex flex-col rounded-md overflow-hidden text-sm text-foreground',
|
|
1545
|
+
$variants[$variant] ?? $variants['default'],
|
|
1546
|
+
$fullWidth ? 'w-full' : '',
|
|
1547
|
+
]));
|
|
1548
|
+
@endphp
|
|
1549
|
+
|
|
1550
|
+
<{{ $as }} {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card" data-ds-padding="{{ $padding }}">
|
|
1551
|
+
{{ $slot }}
|
|
1552
|
+
</{{ $as }}>
|
|
1553
|
+
`,
|
|
1554
|
+
"resources/views/components/ui/card-header.blade.php": `@aware(['padding' => 'compact'])
|
|
1555
|
+
|
|
1556
|
+
@php
|
|
1557
|
+
$classes = $padding === 'comfortable'
|
|
1558
|
+
? 'flex flex-col px-6 pt-6 gap-1.5'
|
|
1559
|
+
: 'flex flex-col px-[var(--ds-padding-card,16px)] pt-[var(--ds-padding-card,16px)] gap-1';
|
|
1560
|
+
@endphp
|
|
1561
|
+
|
|
1562
|
+
<div {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card-header">
|
|
1563
|
+
{{ $slot }}
|
|
1564
|
+
</div>
|
|
1565
|
+
`,
|
|
1566
|
+
"resources/views/components/ui/card-body.blade.php": `@aware(['padding' => 'compact'])
|
|
1567
|
+
|
|
1568
|
+
@php
|
|
1569
|
+
$classes = $padding === 'comfortable'
|
|
1570
|
+
? 'flex flex-col flex-1 px-6 py-4 gap-4'
|
|
1571
|
+
: 'flex flex-col flex-1 px-[var(--ds-padding-card,16px)] py-3 gap-[var(--ds-gap-default,0.75rem)]';
|
|
1572
|
+
@endphp
|
|
1573
|
+
|
|
1574
|
+
<div {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card-body">
|
|
1575
|
+
{{ $slot }}
|
|
1576
|
+
</div>
|
|
1577
|
+
`,
|
|
1578
|
+
"resources/views/components/ui/card-footer.blade.php": `@aware(['padding' => 'compact'])
|
|
1579
|
+
|
|
1580
|
+
@php
|
|
1581
|
+
$classes = $padding === 'comfortable'
|
|
1582
|
+
? 'flex items-center px-6 pb-6 gap-3'
|
|
1583
|
+
: 'flex items-center px-[var(--ds-padding-card,16px)] pb-[var(--ds-padding-card,16px)] gap-2';
|
|
1584
|
+
@endphp
|
|
1585
|
+
|
|
1586
|
+
<div {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="card-footer">
|
|
1587
|
+
{{ $slot }}
|
|
1588
|
+
</div>
|
|
1589
|
+
`,
|
|
1590
|
+
"resources/views/components/ui/input.blade.php": `@props([
|
|
1591
|
+
'variant' => 'default',
|
|
1592
|
+
'size' => 'md',
|
|
1593
|
+
'disabled' => false,
|
|
1594
|
+
])
|
|
1595
|
+
|
|
1596
|
+
@php
|
|
1597
|
+
$variants = [
|
|
1598
|
+
'default' => 'border-input hover:border-border-strong focus-visible:border-border-strong',
|
|
1599
|
+
'error' => 'border-danger text-foreground focus-visible:border-danger placeholder:text-input-placeholder',
|
|
1600
|
+
'success' => 'border-success text-foreground focus-visible:border-success placeholder:text-input-placeholder',
|
|
1601
|
+
];
|
|
1602
|
+
|
|
1603
|
+
$sizes = [
|
|
1604
|
+
'sm' => 'h-8 px-2.5 text-xs',
|
|
1605
|
+
'md' => 'h-[var(--ds-control-height,36px)] px-3 text-sm',
|
|
1606
|
+
'lg' => 'h-10 px-3.5 text-sm',
|
|
1607
|
+
];
|
|
1608
|
+
|
|
1609
|
+
$classes = implode(' ', [
|
|
1610
|
+
'flex w-full text-sm leading-5 rounded-md border bg-background text-input-foreground',
|
|
1611
|
+
'placeholder:text-input-placeholder',
|
|
1612
|
+
'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))]',
|
|
1613
|
+
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
|
|
1614
|
+
'disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-disabled disabled:text-disabled-foreground',
|
|
1615
|
+
'read-only:bg-muted read-only:cursor-default',
|
|
1616
|
+
$variants[$variant] ?? $variants['default'],
|
|
1617
|
+
$sizes[$size] ?? $sizes['md'],
|
|
1618
|
+
]);
|
|
1619
|
+
@endphp
|
|
1620
|
+
|
|
1621
|
+
<input {{ $attributes->merge(['class' => $classes, 'disabled' => $disabled, 'type' => 'text']) }} data-ds data-ds-component="input" />
|
|
1622
|
+
`,
|
|
1623
|
+
"resources/views/components/ui/alert.blade.php": `@props([
|
|
1624
|
+
'variant' => 'info',
|
|
1625
|
+
'title' => null,
|
|
1626
|
+
'dismissible' => false,
|
|
1627
|
+
])
|
|
1628
|
+
|
|
1629
|
+
@php
|
|
1630
|
+
$variants = [
|
|
1631
|
+
'info' => 'bg-info-muted text-info-muted-foreground border-info/20',
|
|
1632
|
+
'success' => 'bg-success-muted text-success-muted-foreground border-success/20',
|
|
1633
|
+
'warning' => 'bg-warning-muted text-warning-muted-foreground border-warning/20',
|
|
1634
|
+
'danger' => 'bg-danger-muted text-danger-muted-foreground border-danger/20',
|
|
1635
|
+
'default' => 'bg-muted text-muted-foreground border-border',
|
|
1636
|
+
];
|
|
1637
|
+
|
|
1638
|
+
$iconColors = [
|
|
1639
|
+
'info' => 'text-info',
|
|
1640
|
+
'success' => 'text-success',
|
|
1641
|
+
'warning' => 'text-warning',
|
|
1642
|
+
'danger' => 'text-danger',
|
|
1643
|
+
'default' => 'text-muted-foreground',
|
|
1644
|
+
];
|
|
1645
|
+
|
|
1646
|
+
$iconPaths = [
|
|
1647
|
+
'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
|
+
'success' => 'M9 12l2 2 4-4m6 2a10 10 0 11-20 0 10 10 0 0120 0z',
|
|
1649
|
+
'warning' => 'M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z',
|
|
1650
|
+
'danger' => 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a10 10 0 11-20 0 10 10 0 0120 0z',
|
|
1651
|
+
'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
|
+
];
|
|
1653
|
+
|
|
1654
|
+
$classes = implode(' ', [
|
|
1655
|
+
'relative flex gap-3 rounded-md p-4 text-sm leading-5 border',
|
|
1656
|
+
'transition-colors duration-[var(--duration-fast,150ms)]',
|
|
1657
|
+
$variants[$variant] ?? $variants['info'],
|
|
1658
|
+
]);
|
|
1659
|
+
@endphp
|
|
1660
|
+
|
|
1661
|
+
<div {{ $attributes->merge(['class' => $classes]) }} role="alert" data-ds data-ds-component="alert">
|
|
1662
|
+
<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
|
+
<path d="{{ $iconPaths[$variant] ?? $iconPaths['info'] }}"/>
|
|
1664
|
+
</svg>
|
|
1665
|
+
<div class="flex-1 space-y-1">
|
|
1666
|
+
@if($title)
|
|
1667
|
+
<p class="font-medium leading-5">{{ $title }}</p>
|
|
1668
|
+
@endif
|
|
1669
|
+
<div class="text-sm leading-5">{{ $slot }}</div>
|
|
1670
|
+
</div>
|
|
1671
|
+
@if($dismissible)
|
|
1672
|
+
<button
|
|
1673
|
+
class="absolute top-3 right-3 inline-flex items-center justify-center rounded-md h-6 w-6 hover:bg-black/10 dark:hover:bg-white/10"
|
|
1674
|
+
onclick="this.closest('[data-ds-component=alert]').remove()"
|
|
1675
|
+
aria-label="Dismiss"
|
|
1676
|
+
>
|
|
1677
|
+
<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"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
1678
|
+
</button>
|
|
1679
|
+
@endif
|
|
1680
|
+
</div>
|
|
1681
|
+
`,
|
|
1682
|
+
"resources/views/components/ui/heading.blade.php": `@props([
|
|
1683
|
+
'level' => 1,
|
|
1684
|
+
'color' => 'default',
|
|
1685
|
+
])
|
|
1686
|
+
|
|
1687
|
+
@php
|
|
1688
|
+
$levels = [
|
|
1689
|
+
1 => 'text-[30px] leading-[36px] font-bold tracking-tight',
|
|
1690
|
+
2 => 'text-[24px] leading-[32px] font-semibold tracking-tight',
|
|
1691
|
+
3 => 'text-[20px] leading-[28px] font-semibold tracking-normal',
|
|
1692
|
+
4 => 'text-[18px] leading-[28px] font-medium tracking-normal',
|
|
1693
|
+
];
|
|
1694
|
+
|
|
1695
|
+
$colors = [
|
|
1696
|
+
'default' => 'text-foreground',
|
|
1697
|
+
'foreground' => 'text-foreground',
|
|
1698
|
+
'muted' => 'text-muted-foreground',
|
|
1699
|
+
'primary' => 'text-primary',
|
|
1700
|
+
];
|
|
1701
|
+
|
|
1702
|
+
$classes = implode(' ', [
|
|
1703
|
+
$levels[$level] ?? $levels[1],
|
|
1704
|
+
$colors[$color] ?? $colors['default'],
|
|
1705
|
+
]);
|
|
1706
|
+
|
|
1707
|
+
$tag = 'h' . min(max((int)$level, 1), 6);
|
|
1708
|
+
@endphp
|
|
1709
|
+
|
|
1710
|
+
<{{ $tag }} {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="heading">
|
|
1711
|
+
{{ $slot }}
|
|
1712
|
+
</{{ $tag }}>
|
|
1713
|
+
`,
|
|
1714
|
+
"resources/views/components/ui/text.blade.php": `@props([
|
|
1715
|
+
'variant' => 'body',
|
|
1716
|
+
'color' => 'default',
|
|
1717
|
+
'as' => 'p',
|
|
1718
|
+
])
|
|
1719
|
+
|
|
1720
|
+
@php
|
|
1721
|
+
$variants = [
|
|
1722
|
+
'body' => 'text-[16px] leading-[24px] font-normal tracking-normal',
|
|
1723
|
+
'bodySm' => 'text-[14px] leading-[20px] font-normal tracking-normal',
|
|
1724
|
+
'caption' => 'text-[12px] leading-[16px] font-normal tracking-wide text-muted-foreground',
|
|
1725
|
+
'label' => 'text-[14px] leading-[20px] font-medium tracking-normal',
|
|
1726
|
+
'overline' => 'text-[12px] leading-[16px] font-semibold tracking-wider uppercase text-muted-foreground',
|
|
1727
|
+
'code' => 'text-[14px] leading-[20px] font-normal tracking-normal font-mono',
|
|
1728
|
+
];
|
|
1729
|
+
|
|
1730
|
+
$colors = [
|
|
1731
|
+
'default' => 'text-foreground',
|
|
1732
|
+
'foreground' => 'text-foreground',
|
|
1733
|
+
'muted' => 'text-muted-foreground',
|
|
1734
|
+
'primary' => 'text-primary',
|
|
1735
|
+
'success' => 'text-success',
|
|
1736
|
+
'warning' => 'text-warning',
|
|
1737
|
+
'danger' => 'text-danger',
|
|
1738
|
+
'info' => 'text-info',
|
|
1739
|
+
];
|
|
1740
|
+
|
|
1741
|
+
$classes = implode(' ', [
|
|
1742
|
+
$variants[$variant] ?? $variants['body'],
|
|
1743
|
+
$colors[$color] ?? $colors['default'],
|
|
1744
|
+
]);
|
|
1745
|
+
@endphp
|
|
1746
|
+
|
|
1747
|
+
<{{ $as }} {{ $attributes->merge(['class' => $classes]) }} data-ds data-ds-component="text">
|
|
1748
|
+
{{ $slot }}
|
|
1749
|
+
</{{ $as }}>
|
|
1750
|
+
`,
|
|
1751
|
+
},
|
|
1752
|
+
},
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1755
|
+
// ---------------------------------------------------------------------------
|
|
1756
|
+
// Starter kit scaffolding command
|
|
1757
|
+
// ---------------------------------------------------------------------------
|
|
1758
|
+
|
|
1759
|
+
async function cmdInitWithTemplate(positional, flags) {
|
|
1760
|
+
log();
|
|
1761
|
+
log(` ${c("bold", "Unified UI")} ${c("dim", "— Create a new project")}`);
|
|
1762
|
+
log();
|
|
1763
|
+
|
|
1764
|
+
// 1. Pick framework
|
|
1765
|
+
let framework;
|
|
1766
|
+
const templateFlag = typeof flags.template === "string" ? flags.template : null;
|
|
1767
|
+
|
|
1768
|
+
if (templateFlag) {
|
|
1769
|
+
framework = FRAMEWORKS.find((f) => f.name === templateFlag);
|
|
1770
|
+
if (!framework) {
|
|
1771
|
+
logError(
|
|
1772
|
+
`Unknown template: "${templateFlag}". Available: ${FRAMEWORKS.map((f) => f.name).join(", ")}`,
|
|
1773
|
+
);
|
|
1774
|
+
process.exit(1);
|
|
1775
|
+
}
|
|
1776
|
+
} else {
|
|
1777
|
+
framework = await promptSelect(
|
|
1778
|
+
"Which framework do you want to use?",
|
|
1779
|
+
FRAMEWORKS,
|
|
1780
|
+
);
|
|
1781
|
+
if (!framework) {
|
|
1782
|
+
logError(
|
|
1783
|
+
`Invalid selection. Available: ${FRAMEWORKS.map((f) => f.name).join(", ")}`,
|
|
1784
|
+
);
|
|
1785
|
+
process.exit(1);
|
|
1786
|
+
}
|
|
1787
|
+
log();
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
logStep("✓", `Framework: ${c("cyan", framework.label)}`);
|
|
1791
|
+
|
|
1792
|
+
// 2. Get project name
|
|
1793
|
+
let projectName = positional[0];
|
|
1794
|
+
if (!projectName) {
|
|
1795
|
+
projectName = await promptText("Project name:", "my-unified-app");
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
const targetDir = resolve(process.cwd(), projectName);
|
|
1799
|
+
logStep("✓", `Project: ${c("cyan", projectName)}`);
|
|
1800
|
+
log();
|
|
1801
|
+
|
|
1802
|
+
// 3. Run the official scaffolding command
|
|
1803
|
+
logStep("📦", `Scaffolding ${c("cyan", framework.label)} project...`);
|
|
1804
|
+
log();
|
|
1805
|
+
|
|
1806
|
+
const scaffoldCmd = framework.scaffoldCmd(projectName);
|
|
1807
|
+
logStep(" ", c("dim", `> ${scaffoldCmd}`));
|
|
1808
|
+
log();
|
|
1809
|
+
|
|
1810
|
+
const scaffoldOk = runCmd(scaffoldCmd, process.cwd());
|
|
1811
|
+
if (!scaffoldOk) {
|
|
1812
|
+
logError(
|
|
1813
|
+
`Scaffolding failed. Make sure the required tool is installed.\n` +
|
|
1814
|
+
(framework.name === "laravel-blade"
|
|
1815
|
+
? ` Requires: ${c("cyan", "composer")} (https://getcomposer.org)\n`
|
|
1816
|
+
: ` Requires: ${c("cyan", "node >= 20")} and ${c("cyan", "npm")}\n`),
|
|
1817
|
+
);
|
|
1818
|
+
process.exit(1);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
if (!existsSync(targetDir)) {
|
|
1822
|
+
logError(`Expected directory "${projectName}" was not created by the scaffolding tool.`);
|
|
1823
|
+
process.exit(1);
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
log();
|
|
1827
|
+
logStep("✓", c("green", `${framework.label} project scaffolded`));
|
|
1828
|
+
|
|
1829
|
+
// 4. Install Unified UI + extra deps
|
|
1830
|
+
logStep("📦", "Installing Unified UI design system...");
|
|
1831
|
+
|
|
1832
|
+
const pm = await detectPackageManager(targetDir);
|
|
1833
|
+
const allDeps = [...framework.deps];
|
|
1834
|
+
const allDevDeps = [...framework.devDeps];
|
|
1835
|
+
|
|
1836
|
+
if (allDeps.length > 0) {
|
|
1837
|
+
const depCmd = getInstallCommand(pm, allDeps);
|
|
1838
|
+
logStep(" ", c("dim", depCmd));
|
|
1839
|
+
runCmd(depCmd, targetDir, "pipe");
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
if (allDevDeps.length > 0) {
|
|
1843
|
+
const devDepCmd = getInstallCommand(pm, allDevDeps).replace(" add ", " add -D ").replace(" install ", " install -D ");
|
|
1844
|
+
logStep(" ", c("dim", devDepCmd));
|
|
1845
|
+
runCmd(devDepCmd, targetDir, "pipe");
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
logStep("✓", c("green", "Dependencies installed"));
|
|
1849
|
+
|
|
1850
|
+
// 5. Apply overlay files
|
|
1851
|
+
log();
|
|
1852
|
+
logStep("✏️ ", "Applying Unified UI starter files...");
|
|
1853
|
+
|
|
1854
|
+
const overlay = OVERLAYS[framework.name];
|
|
1855
|
+
if (overlay) {
|
|
1856
|
+
for (const [filePath, content] of Object.entries(overlay.files)) {
|
|
1857
|
+
const fullPath = join(targetDir, filePath);
|
|
1858
|
+
writeOverlay(fullPath, content);
|
|
1859
|
+
logStep("✓", c("green", filePath));
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// 6. Git commit (if git was initialized by the scaffold tool)
|
|
1864
|
+
const gitDir = join(targetDir, ".git");
|
|
1865
|
+
if (existsSync(gitDir)) {
|
|
1866
|
+
runCmd("git add -A", targetDir, "pipe");
|
|
1867
|
+
runCmd('git commit -m "chore: add Unified UI design system" --no-verify', targetDir, "pipe");
|
|
1868
|
+
logStep("✓", c("green", "Committed Unified UI changes"));
|
|
1869
|
+
} else {
|
|
1870
|
+
// Initialize git if it wasn't done by the scaffold tool
|
|
1871
|
+
if (runCmd("git init", targetDir, "pipe")) {
|
|
1872
|
+
runCmd("git add -A", targetDir, "pipe");
|
|
1873
|
+
runCmd('git commit -m "chore: initial commit with Unified UI" --no-verify', targetDir, "pipe");
|
|
1874
|
+
logStep("✓", c("green", "Initialized git repository"));
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// 7. Print success
|
|
1879
|
+
log();
|
|
1880
|
+
logStep("🎉", c("green", `Project "${projectName}" is ready!`));
|
|
1881
|
+
log();
|
|
1882
|
+
log(` ${c("dim", "Next steps:")}`);
|
|
1883
|
+
log();
|
|
1884
|
+
log(` ${c("cyan", `cd ${projectName}`)}`);
|
|
1885
|
+
|
|
1886
|
+
if (framework.name === "laravel-blade") {
|
|
1887
|
+
log(` ${c("cyan", "npm run dev")}`);
|
|
1888
|
+
log(` ${c("cyan", "php artisan serve")}`);
|
|
1889
|
+
} else {
|
|
1890
|
+
log(` ${c("cyan", "npm run dev")}`);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
log();
|
|
1894
|
+
|
|
1895
|
+
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.")}`);
|
|
1898
|
+
log(` ${c("dim", "See: https://www.unified-ui.space/docs/tokens")}`);
|
|
1899
|
+
log();
|
|
1900
|
+
} else {
|
|
1901
|
+
log(` ${c("dim", "Start adding components:")}`);
|
|
1902
|
+
log(` ${c("cyan", "npx @work-rjkashyap/unified-ui add button card badge")}`);
|
|
1903
|
+
log();
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
async function cmdInit(positional = [], flags = {}) {
|
|
1908
|
+
// If --template flag is present, run the full scaffolding flow
|
|
1909
|
+
if (flags.template) {
|
|
1910
|
+
return cmdInitWithTemplate(positional, flags);
|
|
1911
|
+
}
|
|
1912
|
+
|
|
310
1913
|
log();
|
|
311
1914
|
log(` ${c("bold", "Unified UI")} ${c("dim", "— Initialize project")}`);
|
|
312
1915
|
log();
|
|
@@ -617,20 +2220,35 @@ function cmdHelp() {
|
|
|
617
2220
|
log(` ${c("cyan", "npx @work-rjkashyap/unified-ui")} ${c("green", "<command>")} [options]`);
|
|
618
2221
|
log();
|
|
619
2222
|
log(" Commands:");
|
|
620
|
-
log(` ${c("green", "init")} Initialize project
|
|
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`);
|
|
621
2225
|
log(` ${c("green", "add")} <component...> Add component(s) with dependencies`);
|
|
622
2226
|
log(` ${c("green", "add")} --all Add all components`);
|
|
623
2227
|
log(` ${c("green", "list")} List all available components`);
|
|
624
2228
|
log(` ${c("green", "diff")} <component...> Compare local files with registry`);
|
|
625
2229
|
log(` ${c("green", "help")} Show this help message`);
|
|
626
2230
|
log();
|
|
2231
|
+
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`);
|
|
2236
|
+
log();
|
|
627
2237
|
log(" Options:");
|
|
2238
|
+
log(` ${c("yellow", "--template, -t")} Framework template (with 'init')`);
|
|
628
2239
|
log(` ${c("yellow", "--yes, -y")} Skip confirmation prompts`);
|
|
629
2240
|
log(` ${c("yellow", "--overwrite")} Overwrite existing files`);
|
|
630
2241
|
log(` ${c("yellow", "--all")} Add all components (with 'add')`);
|
|
631
2242
|
log();
|
|
632
2243
|
log(" Examples:");
|
|
633
|
-
log(` ${c("dim", "#
|
|
2244
|
+
log(` ${c("dim", "# Scaffold a new project (interactive)")} `);
|
|
2245
|
+
log(` npx @work-rjkashyap/unified-ui init -t`);
|
|
2246
|
+
log();
|
|
2247
|
+
log(` ${c("dim", "# Scaffold with specific framework")}`);
|
|
2248
|
+
log(` npx @work-rjkashyap/unified-ui init -t nextjs my-app`);
|
|
2249
|
+
log(` npx @work-rjkashyap/unified-ui init --template vite-react my-app`);
|
|
2250
|
+
log();
|
|
2251
|
+
log(` ${c("dim", "# Initialize existing project (copy-paste mode)")} `);
|
|
634
2252
|
log(` npx @work-rjkashyap/unified-ui init`);
|
|
635
2253
|
log();
|
|
636
2254
|
log(` ${c("dim", "# Add specific components")}`);
|
|
@@ -664,6 +2282,19 @@ function parseArgs(argv) {
|
|
|
664
2282
|
flags.overwrite = true;
|
|
665
2283
|
} else if (arg === "--all") {
|
|
666
2284
|
flags.all = true;
|
|
2285
|
+
} else if (arg === "--template" || arg === "-t") {
|
|
2286
|
+
// Next arg is the template name (or true if none given)
|
|
2287
|
+
const next = args[i + 1];
|
|
2288
|
+
if (next && !next.startsWith("-")) {
|
|
2289
|
+
flags.template = next;
|
|
2290
|
+
i++;
|
|
2291
|
+
} else {
|
|
2292
|
+
flags.template = true; // Will trigger interactive picker
|
|
2293
|
+
}
|
|
2294
|
+
} else if (arg.startsWith("--template=")) {
|
|
2295
|
+
flags.template = arg.split("=")[1];
|
|
2296
|
+
} else if (arg.startsWith("-t=")) {
|
|
2297
|
+
flags.template = arg.split("=")[1];
|
|
667
2298
|
} else if (arg.startsWith("--registry=")) {
|
|
668
2299
|
flags.registryUrl = arg.split("=")[1];
|
|
669
2300
|
} else if (!arg.startsWith("-")) {
|
|
@@ -691,7 +2322,7 @@ async function main() {
|
|
|
691
2322
|
|
|
692
2323
|
switch (command) {
|
|
693
2324
|
case "init":
|
|
694
|
-
await cmdInit();
|
|
2325
|
+
await cmdInit(positional, flags);
|
|
695
2326
|
break;
|
|
696
2327
|
case "add":
|
|
697
2328
|
await cmdAdd(positional, flags);
|