@quarklab/rad-ui 0.1.5 → 0.2.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/dist/index.js CHANGED
@@ -1,14 +1,1141 @@
1
- "use strict";var fe=Object.create;var c=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var ie=Object.getPrototypeOf,ce=Object.prototype.hasOwnProperty;var ne=(e,a)=>{for(var t in a)c(e,t,{get:a[t],enumerable:!0})},b=(e,a,t,l)=>{if(a&&typeof a=="object"||typeof a=="function")for(let u of se(a))!ce.call(e,u)&&u!==t&&c(e,u,{get:()=>a[u],enumerable:!(l=re(a,u))||l.enumerable});return e};var d=(e,a,t)=>(t=e!=null?fe(ie(e)):{},b(a||!e||!e.__esModule?c(t,"default",{value:e,enumerable:!0}):t,e)),pe=e=>b(c({},"__esModule",{value:!0}),e);var Ie={};ne(Ie,{AspectRatio:()=>z,Avatar:()=>g,AvatarFallback:()=>S,AvatarImage:()=>h,Badge:()=>W,Button:()=>x,Kbd:()=>P,KbdGroup:()=>w,Label:()=>D,Separator:()=>C,Skeleton:()=>J,Spinner:()=>R,badgeVariants:()=>A,buttonVariants:()=>L,cn:()=>o,raduiPlugin:()=>xe,spinnerVariants:()=>M});module.exports=pe(Ie);var T=require("clsx"),v=require("tailwind-merge");function o(...e){return(0,v.twMerge)((0,T.clsx)(e))}var U=d(require("react")),q=require("class-variance-authority");var H=require("react/jsx-runtime"),L=(0,q.cva)("inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80",destructive:"bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80",outline:"border border-border bg-background hover:bg-muted hover:text-foreground active:bg-muted/80",ghost:"hover:bg-muted hover:text-foreground active:bg-muted/80",link:"text-primary underline-offset-4 hover:underline active:text-primary/80"},size:{sm:"h-9 px-3 text-xs rounded-md",md:"h-10 px-4 py-2",lg:"h-11 px-8 text-base rounded-lg"}},defaultVariants:{variant:"default",size:"md"}}),x=U.forwardRef(({className:e,variant:a,size:t,...l},u)=>(0,H.jsx)("button",{className:o(L({variant:a,size:t,className:e})),ref:u,...l}));x.displayName="Button";var O=d(require("react")),I=d(require("@radix-ui/react-separator"));var G=require("react/jsx-runtime"),C=O.forwardRef(({className:e,orientation:a="horizontal",decorative:t=!0,...l},u)=>(0,G.jsx)(I.Root,{ref:u,decorative:t,orientation:a,className:o("shrink-0 bg-border",a==="horizontal"?"h-[1px] w-full":"h-full w-[1px]",e),...l}));C.displayName=I.Root.displayName;var n=d(require("react")),f=d(require("@radix-ui/react-avatar"));var p=require("react/jsx-runtime"),g=n.forwardRef(({className:e,...a},t)=>(0,p.jsx)(f.Root,{ref:t,className:o("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",e),...a}));g.displayName=f.Root.displayName;var h=n.forwardRef(({className:e,...a},t)=>(0,p.jsx)(f.Image,{ref:t,className:o("aspect-square h-full w-full",e),...a}));h.displayName=f.Image.displayName;var S=n.forwardRef(({className:e,...a},t)=>(0,p.jsx)(f.Fallback,{ref:t,className:o("flex h-full w-full items-center justify-center rounded-full bg-muted",e),...a}));S.displayName=f.Fallback.displayName;var V=d(require("@radix-ui/react-aspect-ratio")),z=V.Root;var k=d(require("react"));var B=require("react/jsx-runtime"),P=k.forwardRef(({className:e,...a},t)=>(0,B.jsx)("kbd",{ref:t,className:o("pointer-events-none inline-flex h-6 select-none items-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground shadow-sm",e),...a}));P.displayName="Kbd";var w=k.forwardRef(({className:e,...a},t)=>(0,B.jsx)("div",{ref:t,className:o("inline-flex items-center gap-1",e),...a}));w.displayName="KbdGroup";var E=require("class-variance-authority");var N=require("react/jsx-runtime"),A=(0,E.cva)("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",secondary:"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",destructive:"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",outline:"text-foreground"}},defaultVariants:{variant:"default"}});function W({className:e,variant:a,...t}){return(0,N.jsx)("div",{className:o(A({variant:a}),e),...t})}var X=d(require("react")),F=d(require("@radix-ui/react-label")),K=require("class-variance-authority");var Z=require("react/jsx-runtime"),me=(0,K.cva)("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"),D=X.forwardRef(({className:e,...a},t)=>(0,Z.jsx)(F.Root,{ref:t,className:o(me(),e),...a}));D.displayName=F.Root.displayName;var Q=require("react/jsx-runtime");function J({className:e,...a}){return(0,Q.jsx)("div",{className:o("animate-pulse rounded-md bg-muted",e),...a})}var j=d(require("react"));var s=require("react");var Y={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};var Le=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().trim(),$=(e,a)=>{let t=(0,s.forwardRef)(({color:l="currentColor",size:u=24,strokeWidth:y=2,absoluteStrokeWidth:ae,className:te="",children:m,...oe},ue)=>(0,s.createElement)("svg",{ref:ue,...Y,width:u,height:u,stroke:l,strokeWidth:ae?Number(y)*24/Number(u):y,className:["lucide",`lucide-${Le(e)}`,te].join(" "),...oe},[...a.map(([de,le])=>(0,s.createElement)(de,le)),...Array.isArray(m)?m:[m]]));return t.displayName=`${e}`,t};var i=$("Loader",[["line",{x1:"12",x2:"12",y1:"2",y2:"6",key:"gza1u7"}],["line",{x1:"12",x2:"12",y1:"18",y2:"22",key:"1qhbu9"}],["line",{x1:"4.93",x2:"7.76",y1:"4.93",y2:"7.76",key:"xae44r"}],["line",{x1:"16.24",x2:"19.07",y1:"16.24",y2:"19.07",key:"bxnmvf"}],["line",{x1:"2",x2:"6",y1:"12",y2:"12",key:"89khin"}],["line",{x1:"18",x2:"22",y1:"12",y2:"12",key:"pb8tfm"}],["line",{x1:"4.93",x2:"7.76",y1:"19.07",y2:"16.24",key:"1uxjnu"}],["line",{x1:"16.24",x2:"19.07",y1:"7.76",y2:"4.93",key:"6duxfx"}]]);var _=require("class-variance-authority");var r=require("react/jsx-runtime"),M=(0,_.cva)("animate-spin",{variants:{size:{sm:"size-4",default:"size-6",lg:"size-8",xl:"size-12"}},defaultVariants:{size:"default"}}),R=j.forwardRef(({className:e,size:a,srText:t="Loading...",...l},u)=>(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(i,{ref:u,className:o(M({size:a}),e),role:"status","aria-label":t,...l}),(0,r.jsx)("span",{className:"sr-only",children:t})]}));R.displayName="Spinner";var ee=d(require("tailwindcss/plugin")),xe=(0,ee.default)(function({addBase:e}){e({":root":{"--background":"40 20% 98%","--foreground":"220 15% 15%","--primary":"175 100% 31%","--primary-foreground":"0 0% 100%","--secondary":"40 10% 92%","--secondary-foreground":"220 15% 15%","--destructive":"0 84% 60%","--destructive-foreground":"0 0% 100%","--card":"40 20% 96%","--card-foreground":"220 15% 15%","--border":"40 10% 88%","--muted":"40 10% 92%","--muted-foreground":"220 10% 40%","--ring":"175 100% 31%","--radius":"0.5rem"},".dark":{"--background":"222 47% 11%","--foreground":"210 40% 98%","--primary":"170 65% 50%","--primary-foreground":"222 47% 11%","--secondary":"217 33% 17%","--secondary-foreground":"210 40% 98%","--destructive":"0 63% 31%","--destructive-foreground":"210 40% 98%","--card":"222 47% 13%","--card-foreground":"210 40% 98%","--border":"217 33% 20%","--muted":"217 33% 17%","--muted-foreground":"210 20% 70%","--ring":"170 65% 50%"}})},{theme:{extend:{colors:{background:"hsl(var(--background))",foreground:"hsl(var(--foreground))",primary:{DEFAULT:"hsl(var(--primary))",foreground:"hsl(var(--primary-foreground))"},secondary:{DEFAULT:"hsl(var(--secondary))",foreground:"hsl(var(--secondary-foreground))"},destructive:{DEFAULT:"hsl(var(--destructive))",foreground:"hsl(var(--destructive-foreground))"},card:{DEFAULT:"hsl(var(--card))",foreground:"hsl(var(--card-foreground))"},border:"hsl(var(--border))",muted:{DEFAULT:"hsl(var(--muted))",foreground:"hsl(var(--muted-foreground))"},ring:"hsl(var(--ring))"},borderRadius:{lg:"var(--radius)",md:"calc(var(--radius) - 2px)",sm:"calc(var(--radius) - 4px)"}}}});0&&(module.exports={AspectRatio,Avatar,AvatarFallback,AvatarImage,Badge,Button,Kbd,KbdGroup,Label,Separator,Skeleton,Spinner,badgeVariants,buttonVariants,cn,raduiPlugin,spinnerVariants});
2
- /*! Bundled license information:
3
-
4
- lucide-react/dist/esm/defaultAttributes.js:
5
- lucide-react/dist/esm/createLucideIcon.js:
6
- lucide-react/dist/esm/icons/loader.js:
7
- lucide-react/dist/esm/lucide-react.js:
8
- (**
9
- * @license lucide-react v0.309.0 - ISC
10
- *
11
- * This source code is licensed under the ISC license.
12
- * See the LICENSE file in the root directory of this source tree.
13
- *)
14
- */
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import path3 from "path";
8
+ import fs3 from "fs-extra";
9
+ import * as p from "@clack/prompts";
10
+ import chalk3 from "chalk";
11
+
12
+ // src/utils/detect-project.ts
13
+ import path from "path";
14
+ import fs from "fs-extra";
15
+ async function detectProjectType(cwd) {
16
+ const pkgPath = path.resolve(cwd, "package.json");
17
+ if (!await fs.pathExists(pkgPath)) {
18
+ return "unknown";
19
+ }
20
+ const pkg = await fs.readJson(pkgPath);
21
+ const allDeps = {
22
+ ...pkg.dependencies,
23
+ ...pkg.devDependencies
24
+ };
25
+ if (allDeps["next"]) return "nextjs";
26
+ if (allDeps["expo"]) return "expo";
27
+ if (allDeps["vite"]) return "vite";
28
+ if (allDeps["react-scripts"]) return "cra";
29
+ return "unknown";
30
+ }
31
+ async function detectPackageManager(cwd) {
32
+ if (await fs.pathExists(path.resolve(cwd, "bun.lockb"))) return "bun";
33
+ if (await fs.pathExists(path.resolve(cwd, "pnpm-lock.yaml"))) return "pnpm";
34
+ if (await fs.pathExists(path.resolve(cwd, "yarn.lock"))) return "yarn";
35
+ return "npm";
36
+ }
37
+ function getDefaultCssPath(projectType) {
38
+ switch (projectType) {
39
+ case "nextjs":
40
+ return "src/app/globals.css";
41
+ case "vite":
42
+ return "src/index.css";
43
+ case "cra":
44
+ return "src/index.css";
45
+ case "expo":
46
+ return "global.css";
47
+ default:
48
+ return "src/globals.css";
49
+ }
50
+ }
51
+ function getDefaultTailwindConfig(projectType) {
52
+ switch (projectType) {
53
+ case "expo":
54
+ return "tailwind.config.js";
55
+ default:
56
+ return "tailwind.config.ts";
57
+ }
58
+ }
59
+
60
+ // src/utils/config.ts
61
+ import path2 from "path";
62
+ import fs2 from "fs-extra";
63
+ var CONFIG_FILE = "rad-ui.json";
64
+ function getConfigPath(cwd) {
65
+ return path2.resolve(cwd, CONFIG_FILE);
66
+ }
67
+ async function configExists(cwd) {
68
+ return fs2.pathExists(getConfigPath(cwd));
69
+ }
70
+ async function readConfig(cwd) {
71
+ const configPath = getConfigPath(cwd);
72
+ const exists = await fs2.pathExists(configPath);
73
+ if (!exists) {
74
+ throw new Error(
75
+ `Configuration file not found. Run ${chalk("rad-ui init")} first.`
76
+ );
77
+ }
78
+ return fs2.readJson(configPath);
79
+ }
80
+ async function writeConfig(cwd, config) {
81
+ const configPath = getConfigPath(cwd);
82
+ await fs2.writeJson(configPath, config, { spaces: 2 });
83
+ }
84
+ function chalk(text2) {
85
+ return `\`${text2}\``;
86
+ }
87
+ function resolveComponentsDir(cwd, config) {
88
+ const aliasPath = config.aliases.components.replace(/^@\//, "src/");
89
+ return path2.resolve(cwd, aliasPath);
90
+ }
91
+
92
+ // src/registry/themes.ts
93
+ var themes = [
94
+ {
95
+ name: "kahgel",
96
+ label: "Kahgel (\u06A9\u0627\u0647\u06AF\u0644) \u2014 Warm Clay",
97
+ cssVars: {
98
+ light: {
99
+ "--background": "40 20% 98%",
100
+ "--foreground": "220 15% 15%",
101
+ "--primary": "175 100% 31%",
102
+ "--primary-foreground": "0 0% 100%",
103
+ "--secondary": "40 10% 92%",
104
+ "--secondary-foreground": "220 15% 15%",
105
+ "--destructive": "0 84% 60%",
106
+ "--destructive-foreground": "0 0% 100%",
107
+ "--card": "40 20% 96%",
108
+ "--card-foreground": "220 15% 15%",
109
+ "--border": "40 10% 88%",
110
+ "--muted": "40 10% 92%",
111
+ "--muted-foreground": "220 10% 40%",
112
+ "--ring": "175 100% 31%",
113
+ "--radius": "0.5rem"
114
+ },
115
+ dark: {
116
+ "--background": "222 47% 11%",
117
+ "--foreground": "210 40% 98%",
118
+ "--primary": "170 65% 50%",
119
+ "--primary-foreground": "222 47% 11%",
120
+ "--secondary": "217 33% 17%",
121
+ "--secondary-foreground": "210 40% 98%",
122
+ "--destructive": "0 63% 31%",
123
+ "--destructive-foreground": "210 40% 98%",
124
+ "--card": "222 47% 13%",
125
+ "--card-foreground": "210 40% 98%",
126
+ "--border": "217 33% 20%",
127
+ "--muted": "217 33% 17%",
128
+ "--muted-foreground": "210 20% 70%",
129
+ "--ring": "170 65% 50%"
130
+ }
131
+ }
132
+ },
133
+ {
134
+ name: "firouzeh",
135
+ label: "Firouzeh (\u0641\u06CC\u0631\u0648\u0632\u0647) \u2014 Persian Turquoise",
136
+ cssVars: {
137
+ light: {
138
+ "--background": "180 20% 98%",
139
+ "--foreground": "200 25% 10%",
140
+ "--primary": "174 72% 40%",
141
+ "--primary-foreground": "0 0% 100%",
142
+ "--secondary": "180 15% 92%",
143
+ "--secondary-foreground": "200 25% 10%",
144
+ "--destructive": "0 84% 60%",
145
+ "--destructive-foreground": "0 0% 100%",
146
+ "--card": "180 15% 96%",
147
+ "--card-foreground": "200 25% 10%",
148
+ "--border": "180 12% 87%",
149
+ "--muted": "180 12% 92%",
150
+ "--muted-foreground": "200 10% 42%",
151
+ "--ring": "174 72% 40%",
152
+ "--radius": "0.5rem"
153
+ },
154
+ dark: {
155
+ "--background": "200 40% 8%",
156
+ "--foreground": "180 30% 96%",
157
+ "--primary": "174 65% 52%",
158
+ "--primary-foreground": "200 40% 8%",
159
+ "--secondary": "200 30% 15%",
160
+ "--secondary-foreground": "180 30% 96%",
161
+ "--destructive": "0 63% 31%",
162
+ "--destructive-foreground": "180 30% 96%",
163
+ "--card": "200 38% 10%",
164
+ "--card-foreground": "180 30% 96%",
165
+ "--border": "200 28% 18%",
166
+ "--muted": "200 28% 15%",
167
+ "--muted-foreground": "180 15% 65%",
168
+ "--ring": "174 65% 52%"
169
+ }
170
+ }
171
+ },
172
+ {
173
+ name: "lajvard",
174
+ label: "Lajvard (\u0644\u0627\u062C\u0648\u0631\u062F) \u2014 Lapis Lazuli",
175
+ cssVars: {
176
+ light: {
177
+ "--background": "225 25% 98%",
178
+ "--foreground": "230 25% 12%",
179
+ "--primary": "225 75% 45%",
180
+ "--primary-foreground": "0 0% 100%",
181
+ "--secondary": "225 15% 92%",
182
+ "--secondary-foreground": "230 25% 12%",
183
+ "--destructive": "0 84% 60%",
184
+ "--destructive-foreground": "0 0% 100%",
185
+ "--card": "225 20% 96%",
186
+ "--card-foreground": "230 25% 12%",
187
+ "--border": "225 15% 87%",
188
+ "--muted": "225 15% 92%",
189
+ "--muted-foreground": "225 10% 42%",
190
+ "--ring": "225 75% 45%",
191
+ "--radius": "0.5rem"
192
+ },
193
+ dark: {
194
+ "--background": "230 45% 8%",
195
+ "--foreground": "220 30% 96%",
196
+ "--primary": "220 70% 58%",
197
+ "--primary-foreground": "230 45% 8%",
198
+ "--secondary": "230 30% 15%",
199
+ "--secondary-foreground": "220 30% 96%",
200
+ "--destructive": "0 63% 31%",
201
+ "--destructive-foreground": "220 30% 96%",
202
+ "--card": "230 42% 10%",
203
+ "--card-foreground": "220 30% 96%",
204
+ "--border": "230 28% 18%",
205
+ "--muted": "230 28% 15%",
206
+ "--muted-foreground": "220 15% 65%",
207
+ "--ring": "220 70% 58%"
208
+ }
209
+ }
210
+ },
211
+ {
212
+ name: "puste",
213
+ label: "Puste (\u067E\u0633\u062A\u0647) \u2014 Pistachio",
214
+ cssVars: {
215
+ light: {
216
+ "--background": "80 20% 98%",
217
+ "--foreground": "100 15% 12%",
218
+ "--primary": "95 55% 38%",
219
+ "--primary-foreground": "0 0% 100%",
220
+ "--secondary": "85 12% 91%",
221
+ "--secondary-foreground": "100 15% 12%",
222
+ "--destructive": "0 84% 60%",
223
+ "--destructive-foreground": "0 0% 100%",
224
+ "--card": "80 18% 95%",
225
+ "--card-foreground": "100 15% 12%",
226
+ "--border": "85 12% 86%",
227
+ "--muted": "85 12% 91%",
228
+ "--muted-foreground": "95 8% 42%",
229
+ "--ring": "95 55% 38%",
230
+ "--radius": "0.5rem"
231
+ },
232
+ dark: {
233
+ "--background": "110 30% 8%",
234
+ "--foreground": "90 25% 95%",
235
+ "--primary": "95 50% 52%",
236
+ "--primary-foreground": "110 30% 8%",
237
+ "--secondary": "105 25% 14%",
238
+ "--secondary-foreground": "90 25% 95%",
239
+ "--destructive": "0 63% 31%",
240
+ "--destructive-foreground": "90 25% 95%",
241
+ "--card": "110 28% 10%",
242
+ "--card-foreground": "90 25% 95%",
243
+ "--border": "105 22% 18%",
244
+ "--muted": "105 22% 14%",
245
+ "--muted-foreground": "95 15% 62%",
246
+ "--ring": "95 50% 52%"
247
+ }
248
+ }
249
+ },
250
+ {
251
+ name: "anar",
252
+ label: "Anar (\u0627\u0646\u0627\u0631) \u2014 Pomegranate",
253
+ cssVars: {
254
+ light: {
255
+ "--background": "0 20% 98%",
256
+ "--foreground": "350 20% 12%",
257
+ "--primary": "348 75% 42%",
258
+ "--primary-foreground": "0 0% 100%",
259
+ "--secondary": "350 12% 92%",
260
+ "--secondary-foreground": "350 20% 12%",
261
+ "--destructive": "25 90% 55%",
262
+ "--destructive-foreground": "0 0% 100%",
263
+ "--card": "355 18% 96%",
264
+ "--card-foreground": "350 20% 12%",
265
+ "--border": "350 12% 87%",
266
+ "--muted": "350 12% 92%",
267
+ "--muted-foreground": "350 8% 42%",
268
+ "--ring": "348 75% 42%",
269
+ "--radius": "0.5rem"
270
+ },
271
+ dark: {
272
+ "--background": "350 40% 7%",
273
+ "--foreground": "355 25% 96%",
274
+ "--primary": "348 68% 55%",
275
+ "--primary-foreground": "350 40% 7%",
276
+ "--secondary": "350 28% 14%",
277
+ "--secondary-foreground": "355 25% 96%",
278
+ "--destructive": "25 80% 40%",
279
+ "--destructive-foreground": "355 25% 96%",
280
+ "--card": "350 38% 9%",
281
+ "--card-foreground": "355 25% 96%",
282
+ "--border": "350 25% 17%",
283
+ "--muted": "350 25% 14%",
284
+ "--muted-foreground": "350 15% 62%",
285
+ "--ring": "348 68% 55%"
286
+ }
287
+ }
288
+ }
289
+ ];
290
+ function generateThemeCSS(theme) {
291
+ const lightVars = Object.entries(theme.cssVars.light).map(([key, value]) => ` ${key}: ${value};`).join("\n");
292
+ const darkVars = Object.entries(theme.cssVars.dark).map(([key, value]) => ` ${key}: ${value};`).join("\n");
293
+ return `@tailwind base;
294
+ @tailwind components;
295
+ @tailwind utilities;
296
+
297
+ @layer base {
298
+ :root {
299
+ ${lightVars}
300
+ }
301
+
302
+ .dark {
303
+ ${darkVars}
304
+ }
305
+ }
306
+
307
+ @layer base {
308
+ * {
309
+ @apply border-border;
310
+ }
311
+ body {
312
+ @apply bg-background text-foreground;
313
+ }
314
+ }
315
+ `;
316
+ }
317
+
318
+ // src/utils/logger.ts
319
+ import chalk2 from "chalk";
320
+ var logger = {
321
+ info: (msg) => console.log(chalk2.cyan("\u2139"), msg),
322
+ success: (msg) => console.log(chalk2.green("\u2713"), msg),
323
+ warn: (msg) => console.log(chalk2.yellow("\u26A0"), msg),
324
+ error: (msg) => console.log(chalk2.red("\u2717"), msg),
325
+ break: () => console.log("")
326
+ };
327
+
328
+ // src/commands/init.ts
329
+ async function initCommand(opts) {
330
+ const cwd = process.cwd();
331
+ p.intro(chalk3.bold("Welcome to Rad UI"));
332
+ if (await configExists(cwd)) {
333
+ const shouldContinue = await p.confirm({
334
+ message: "Rad UI is already initialized in this project. Reinitialize?",
335
+ initialValue: false
336
+ });
337
+ if (p.isCancel(shouldContinue) || !shouldContinue) {
338
+ p.cancel("Cancelled.");
339
+ process.exit(0);
340
+ }
341
+ }
342
+ const projectType = await detectProjectType(cwd);
343
+ const packageManager = await detectPackageManager(cwd);
344
+ if (projectType !== "unknown") {
345
+ p.log.info(`Detected project: ${chalk3.cyan(projectType)}`);
346
+ }
347
+ p.log.info(`Package manager: ${chalk3.cyan(packageManager)}`);
348
+ const responses = await p.group(
349
+ {
350
+ platform: () => p.select({
351
+ message: "Which platform are you building for?",
352
+ options: [
353
+ { value: "web", label: "Web", hint: "React + Tailwind CSS" },
354
+ {
355
+ value: "mobile",
356
+ label: "Mobile",
357
+ hint: "React Native + NativeWind (coming soon)"
358
+ },
359
+ { value: "both", label: "Both", hint: "Web + Mobile" }
360
+ ],
361
+ initialValue: "web"
362
+ }),
363
+ theme: () => p.select({
364
+ message: "Choose a theme:",
365
+ options: themes.map((t) => ({
366
+ value: t.name,
367
+ label: t.label
368
+ })),
369
+ initialValue: "kahgel"
370
+ }),
371
+ componentsPath: () => p.text({
372
+ message: "Where would you like to store components?",
373
+ placeholder: "@/components/ui",
374
+ defaultValue: "@/components/ui"
375
+ }),
376
+ utilsPath: () => p.text({
377
+ message: "Where is your utils file?",
378
+ placeholder: "@/lib/utils",
379
+ defaultValue: "@/lib/utils"
380
+ }),
381
+ cssPath: () => p.text({
382
+ message: "Where is your global CSS file?",
383
+ placeholder: getDefaultCssPath(projectType),
384
+ defaultValue: getDefaultCssPath(projectType)
385
+ }),
386
+ tailwindConfig: () => p.text({
387
+ message: "Where is your Tailwind config file?",
388
+ placeholder: getDefaultTailwindConfig(projectType),
389
+ defaultValue: getDefaultTailwindConfig(projectType)
390
+ })
391
+ },
392
+ {
393
+ onCancel: () => {
394
+ p.cancel("Cancelled.");
395
+ process.exit(0);
396
+ }
397
+ }
398
+ );
399
+ const s = p.spinner();
400
+ s.start("Creating configuration...");
401
+ const config = {
402
+ $schema: "https://www.quarklab.dev/schema.json",
403
+ registry: "https://www.quarklab.dev/r",
404
+ platform: responses.platform,
405
+ theme: responses.theme,
406
+ tailwind: {
407
+ config: responses.tailwindConfig,
408
+ css: responses.cssPath
409
+ },
410
+ aliases: {
411
+ components: responses.componentsPath,
412
+ utils: responses.utilsPath
413
+ }
414
+ };
415
+ await writeConfig(cwd, config);
416
+ s.stop("Configuration created.");
417
+ s.start("Setting up theme...");
418
+ const selectedTheme = themes.find((t) => t.name === config.theme);
419
+ if (selectedTheme) {
420
+ const cssPath = path3.resolve(cwd, config.tailwind.css);
421
+ const cssDir = path3.dirname(cssPath);
422
+ await fs3.ensureDir(cssDir);
423
+ const cssContent = generateThemeCSS(selectedTheme);
424
+ await fs3.writeFile(cssPath, cssContent, "utf-8");
425
+ }
426
+ s.stop("Theme configured.");
427
+ s.start("Setting up utilities...");
428
+ const utilsAlias = config.aliases.utils;
429
+ const utilsRelPath = utilsAlias.replace(/^@\//, "src/") + ".ts";
430
+ const utilsDestPath = path3.resolve(cwd, utilsRelPath);
431
+ const utilsDir = path3.dirname(utilsDestPath);
432
+ await fs3.ensureDir(utilsDir);
433
+ const utilsContent = `import { type ClassValue, clsx } from "clsx";
434
+ import { twMerge } from "tailwind-merge";
435
+
436
+ export function cn(...inputs: ClassValue[]) {
437
+ return twMerge(clsx(inputs));
438
+ }
439
+ `;
440
+ await fs3.writeFile(utilsDestPath, utilsContent, "utf-8");
441
+ s.stop("Utilities ready.");
442
+ s.start("Configuring Tailwind CSS...");
443
+ const tailwindConfigPath = path3.resolve(cwd, config.tailwind.config);
444
+ const componentsRelPath = config.aliases.components.replace(/^@\//, "src/");
445
+ if (await fs3.pathExists(tailwindConfigPath)) {
446
+ let tailwindContent = await fs3.readFile(tailwindConfigPath, "utf-8");
447
+ const contentPath = `./${componentsRelPath}/**/*.{ts,tsx}`;
448
+ if (!tailwindContent.includes(contentPath)) {
449
+ tailwindContent = tailwindContent.replace(
450
+ /(content\s*:\s*\[)/,
451
+ `$1
452
+ "${contentPath}",`
453
+ );
454
+ await fs3.writeFile(tailwindConfigPath, tailwindContent, "utf-8");
455
+ p.log.info(`Added component path to ${config.tailwind.config}`);
456
+ }
457
+ } else {
458
+ const isTS = config.tailwind.config.endsWith(".ts");
459
+ const newConfig = isTS ? `import type { Config } from "tailwindcss";
460
+
461
+ const config: Config = {
462
+ darkMode: ["class"],
463
+ content: [
464
+ "./src/**/*.{js,ts,jsx,tsx,mdx}",
465
+ "./${componentsRelPath}/**/*.{ts,tsx}",
466
+ ],
467
+ theme: {
468
+ extend: {
469
+ colors: {
470
+ background: "hsl(var(--background))",
471
+ foreground: "hsl(var(--foreground))",
472
+ primary: {
473
+ DEFAULT: "hsl(var(--primary))",
474
+ foreground: "hsl(var(--primary-foreground))",
475
+ },
476
+ secondary: {
477
+ DEFAULT: "hsl(var(--secondary))",
478
+ foreground: "hsl(var(--secondary-foreground))",
479
+ },
480
+ destructive: {
481
+ DEFAULT: "hsl(var(--destructive))",
482
+ foreground: "hsl(var(--destructive-foreground))",
483
+ },
484
+ card: {
485
+ DEFAULT: "hsl(var(--card))",
486
+ foreground: "hsl(var(--card-foreground))",
487
+ },
488
+ border: "hsl(var(--border))",
489
+ muted: {
490
+ DEFAULT: "hsl(var(--muted))",
491
+ foreground: "hsl(var(--muted-foreground))",
492
+ },
493
+ ring: "hsl(var(--ring))",
494
+ },
495
+ borderRadius: {
496
+ lg: "var(--radius)",
497
+ md: "calc(var(--radius) - 2px)",
498
+ sm: "calc(var(--radius) - 4px)",
499
+ },
500
+ keyframes: {
501
+ "caret-blink": {
502
+ "0%,70%,100%": { opacity: "1" },
503
+ "20%,50%": { opacity: "0" },
504
+ },
505
+ },
506
+ animation: {
507
+ "caret-blink": "caret-blink 1.25s ease-out infinite",
508
+ },
509
+ },
510
+ },
511
+ plugins: [],
512
+ };
513
+
514
+ export default config;
515
+ ` : `/** @type {import('tailwindcss').Config} */
516
+ module.exports = {
517
+ darkMode: ["class"],
518
+ content: [
519
+ "./src/**/*.{js,ts,jsx,tsx,mdx}",
520
+ "./${componentsRelPath}/**/*.{ts,tsx}",
521
+ ],
522
+ theme: {
523
+ extend: {
524
+ colors: {
525
+ background: "hsl(var(--background))",
526
+ foreground: "hsl(var(--foreground))",
527
+ primary: {
528
+ DEFAULT: "hsl(var(--primary))",
529
+ foreground: "hsl(var(--primary-foreground))",
530
+ },
531
+ secondary: {
532
+ DEFAULT: "hsl(var(--secondary))",
533
+ foreground: "hsl(var(--secondary-foreground))",
534
+ },
535
+ destructive: {
536
+ DEFAULT: "hsl(var(--destructive))",
537
+ foreground: "hsl(var(--destructive-foreground))",
538
+ },
539
+ card: {
540
+ DEFAULT: "hsl(var(--card))",
541
+ foreground: "hsl(var(--card-foreground))",
542
+ },
543
+ border: "hsl(var(--border))",
544
+ muted: {
545
+ DEFAULT: "hsl(var(--muted))",
546
+ foreground: "hsl(var(--muted-foreground))",
547
+ },
548
+ ring: "hsl(var(--ring))",
549
+ },
550
+ borderRadius: {
551
+ lg: "var(--radius)",
552
+ md: "calc(var(--radius) - 2px)",
553
+ sm: "calc(var(--radius) - 4px)",
554
+ },
555
+ keyframes: {
556
+ "caret-blink": {
557
+ "0%,70%,100%": { opacity: "1" },
558
+ "20%,50%": { opacity: "0" },
559
+ },
560
+ },
561
+ animation: {
562
+ "caret-blink": "caret-blink 1.25s ease-out infinite",
563
+ },
564
+ },
565
+ },
566
+ plugins: [],
567
+ };
568
+ `;
569
+ await fs3.writeFile(tailwindConfigPath, newConfig, "utf-8");
570
+ }
571
+ s.stop("Tailwind CSS configured.");
572
+ s.start("Installing base dependencies...");
573
+ const installCmd = getInstallCommand(packageManager, [
574
+ "tailwindcss",
575
+ "clsx",
576
+ "tailwind-merge",
577
+ "class-variance-authority"
578
+ ]);
579
+ try {
580
+ const { execa } = await import("execa");
581
+ await execa(installCmd.command, installCmd.args, {
582
+ cwd,
583
+ stdio: "pipe"
584
+ });
585
+ s.stop("Dependencies installed.");
586
+ } catch {
587
+ s.stop("Could not auto-install dependencies.");
588
+ p.log.warn(
589
+ `Please manually install: ${chalk3.cyan("tailwindcss clsx tailwind-merge class-variance-authority")}`
590
+ );
591
+ }
592
+ logger.break();
593
+ p.note(
594
+ [
595
+ `${chalk3.green("Rad UI has been initialized!")}`,
596
+ "",
597
+ `Add components with: ${chalk3.cyan("npx @quarklab/rad-ui add <component>")}`,
598
+ `Add all components: ${chalk3.cyan("npx @quarklab/rad-ui add --all")}`,
599
+ "",
600
+ `Theme: ${chalk3.cyan(selectedTheme?.label || config.theme)}`,
601
+ `Components: ${chalk3.cyan(config.aliases.components)}`,
602
+ `Utils: ${chalk3.cyan(config.aliases.utils)}`
603
+ ].join("\n"),
604
+ "Next steps"
605
+ );
606
+ p.outro("Happy building!");
607
+ }
608
+ function getInstallCommand(pm, packages) {
609
+ switch (pm) {
610
+ case "pnpm":
611
+ return { command: "pnpm", args: ["add", ...packages] };
612
+ case "yarn":
613
+ return { command: "yarn", args: ["add", ...packages] };
614
+ case "bun":
615
+ return { command: "bun", args: ["add", ...packages] };
616
+ default:
617
+ return { command: "npm", args: ["install", ...packages] };
618
+ }
619
+ }
620
+
621
+ // src/commands/add.ts
622
+ import path4 from "path";
623
+ import { fileURLToPath } from "url";
624
+ import fs4 from "fs-extra";
625
+ import * as p2 from "@clack/prompts";
626
+ import chalk4 from "chalk";
627
+
628
+ // src/registry/index.ts
629
+ var components = [
630
+ {
631
+ name: "aspect-ratio",
632
+ description: "Displays content within a desired ratio",
633
+ platform: "web",
634
+ files: ["aspect-ratio.tsx"],
635
+ npmDependencies: {
636
+ "@radix-ui/react-aspect-ratio": "^1.1.8"
637
+ },
638
+ internalDependencies: []
639
+ },
640
+ {
641
+ name: "avatar",
642
+ description: "An image element with a fallback for representing the user",
643
+ platform: "web",
644
+ files: ["avatar.tsx"],
645
+ npmDependencies: {
646
+ "@radix-ui/react-avatar": "^1.1.11"
647
+ },
648
+ internalDependencies: []
649
+ },
650
+ {
651
+ name: "badge",
652
+ description: "Displays a badge or a component that looks like a badge",
653
+ platform: "web",
654
+ files: ["badge.tsx"],
655
+ npmDependencies: {
656
+ "class-variance-authority": "^0.7.1"
657
+ },
658
+ internalDependencies: []
659
+ },
660
+ {
661
+ name: "button",
662
+ description: "Displays a button or a component that looks like a button",
663
+ platform: "web",
664
+ files: ["button.tsx"],
665
+ npmDependencies: {
666
+ "class-variance-authority": "^0.7.1"
667
+ },
668
+ internalDependencies: []
669
+ },
670
+ {
671
+ name: "checkbox",
672
+ description: "A control that allows the user to toggle between checked and not checked",
673
+ platform: "web",
674
+ files: ["checkbox.tsx"],
675
+ npmDependencies: {
676
+ "@radix-ui/react-checkbox": "^1.1.0",
677
+ "lucide-react": "^0.309.0"
678
+ },
679
+ internalDependencies: []
680
+ },
681
+ {
682
+ name: "field",
683
+ description: "A complete form field with label, description, error message, and more",
684
+ platform: "web",
685
+ files: ["field.tsx"],
686
+ npmDependencies: {
687
+ "class-variance-authority": "^0.7.1"
688
+ },
689
+ internalDependencies: ["label", "separator"]
690
+ },
691
+ {
692
+ name: "input",
693
+ description: "A text input field with multiple variants",
694
+ platform: "web",
695
+ files: ["input.tsx"],
696
+ npmDependencies: {
697
+ "class-variance-authority": "^0.7.1",
698
+ "lucide-react": "^0.309.0"
699
+ },
700
+ internalDependencies: []
701
+ },
702
+ {
703
+ name: "input-group",
704
+ description: "Group inputs with addons, buttons, and text",
705
+ platform: "web",
706
+ files: ["input-group.tsx"],
707
+ npmDependencies: {
708
+ "class-variance-authority": "^0.7.1"
709
+ },
710
+ internalDependencies: ["button", "input", "textarea"]
711
+ },
712
+ {
713
+ name: "input-otp",
714
+ description: "One-time password input component",
715
+ platform: "web",
716
+ files: ["input-otp.tsx"],
717
+ npmDependencies: {
718
+ "input-otp": "^1.4.2",
719
+ "lucide-react": "^0.309.0"
720
+ },
721
+ internalDependencies: []
722
+ },
723
+ {
724
+ name: "kbd",
725
+ description: "Display keyboard shortcuts and key combinations",
726
+ platform: "web",
727
+ files: ["kbd.tsx"],
728
+ npmDependencies: {},
729
+ internalDependencies: []
730
+ },
731
+ {
732
+ name: "label",
733
+ description: "Renders an accessible label associated with controls",
734
+ platform: "web",
735
+ files: ["label.tsx"],
736
+ npmDependencies: {
737
+ "@radix-ui/react-label": "^2.1.8",
738
+ "class-variance-authority": "^0.7.1"
739
+ },
740
+ internalDependencies: []
741
+ },
742
+ {
743
+ name: "native-select",
744
+ description: "A native HTML select element with custom styling",
745
+ platform: "web",
746
+ files: ["native-select.tsx"],
747
+ npmDependencies: {
748
+ "class-variance-authority": "^0.7.1",
749
+ "lucide-react": "^0.309.0"
750
+ },
751
+ internalDependencies: []
752
+ },
753
+ {
754
+ name: "radio-group",
755
+ description: "A set of checkable buttons where only one can be checked at a time",
756
+ platform: "web",
757
+ files: ["radio-group.tsx"],
758
+ npmDependencies: {
759
+ "@radix-ui/react-radio-group": "^1.2.0",
760
+ "lucide-react": "^0.309.0"
761
+ },
762
+ internalDependencies: []
763
+ },
764
+ {
765
+ name: "separator",
766
+ description: "Visually or semantically separates content",
767
+ platform: "web",
768
+ files: ["separator.tsx"],
769
+ npmDependencies: {
770
+ "@radix-ui/react-separator": "^1.1.8"
771
+ },
772
+ internalDependencies: []
773
+ },
774
+ {
775
+ name: "skeleton",
776
+ description: "A placeholder to show while content is loading",
777
+ platform: "web",
778
+ files: ["skeleton.tsx"],
779
+ npmDependencies: {},
780
+ internalDependencies: []
781
+ },
782
+ {
783
+ name: "slider",
784
+ description: "An input where the user selects a value from within a given range",
785
+ platform: "web",
786
+ files: ["slider.tsx"],
787
+ npmDependencies: {
788
+ "@radix-ui/react-slider": "^1.2.0"
789
+ },
790
+ internalDependencies: []
791
+ },
792
+ {
793
+ name: "spinner",
794
+ description: "A loading spinner indicator with multiple variants",
795
+ platform: "web",
796
+ files: ["spinner.tsx"],
797
+ npmDependencies: {
798
+ "class-variance-authority": "^0.7.1",
799
+ "lucide-react": "^0.309.0"
800
+ },
801
+ internalDependencies: []
802
+ },
803
+ {
804
+ name: "switch",
805
+ description: "A control that allows the user to toggle between on and off",
806
+ platform: "web",
807
+ files: ["switch.tsx"],
808
+ npmDependencies: {
809
+ "@radix-ui/react-switch": "^1.1.0"
810
+ },
811
+ internalDependencies: []
812
+ },
813
+ {
814
+ name: "textarea",
815
+ description: "A multi-line text input",
816
+ platform: "web",
817
+ files: ["textarea.tsx"],
818
+ npmDependencies: {},
819
+ internalDependencies: []
820
+ },
821
+ {
822
+ name: "toggle",
823
+ description: "A two-state button that can be either on or off",
824
+ platform: "web",
825
+ files: ["toggle.tsx"],
826
+ npmDependencies: {
827
+ "@radix-ui/react-toggle": "^1.1.0",
828
+ "class-variance-authority": "^0.7.1"
829
+ },
830
+ internalDependencies: []
831
+ },
832
+ {
833
+ name: "toggle-group",
834
+ description: "A set of two-state buttons that can be toggled on or off",
835
+ platform: "web",
836
+ files: ["toggle-group.tsx"],
837
+ npmDependencies: {
838
+ "@radix-ui/react-toggle-group": "^1.1.0",
839
+ "class-variance-authority": "^0.7.1"
840
+ },
841
+ internalDependencies: ["toggle"]
842
+ }
843
+ ];
844
+ function getComponent(name) {
845
+ return components.find((c) => c.name === name);
846
+ }
847
+ function getComponentsByPlatform(platform) {
848
+ if (platform === "both") return components;
849
+ return components.filter(
850
+ (c) => c.platform === platform || c.platform === "shared"
851
+ );
852
+ }
853
+ function resolveDependencies(names) {
854
+ const resolved = /* @__PURE__ */ new Set();
855
+ function resolve(name) {
856
+ if (resolved.has(name)) return;
857
+ resolved.add(name);
858
+ const comp = getComponent(name);
859
+ if (!comp) return;
860
+ for (const dep of comp.internalDependencies) {
861
+ resolve(dep);
862
+ }
863
+ }
864
+ for (const name of names) {
865
+ resolve(name);
866
+ }
867
+ return Array.from(resolved);
868
+ }
869
+ function collectNpmDependencies(names) {
870
+ const deps = {
871
+ // Base dependencies needed by all components (via lib/utils.ts)
872
+ clsx: "^2.1.0",
873
+ "tailwind-merge": "^2.2.1"
874
+ };
875
+ for (const name of names) {
876
+ const comp = getComponent(name);
877
+ if (!comp) continue;
878
+ Object.assign(deps, comp.npmDependencies);
879
+ }
880
+ return deps;
881
+ }
882
+
883
+ // src/utils/transformers.ts
884
+ function transformImports(source, config) {
885
+ const utilsAlias = config.aliases.utils;
886
+ let result = source.replace(
887
+ /from\s+["']\.\.\/lib\/utils["']/g,
888
+ `from "${utilsAlias}"`
889
+ );
890
+ result = result.replace(
891
+ /from\s+["']\.\/lib\/utils["']/g,
892
+ `from "${utilsAlias}"`
893
+ );
894
+ return result;
895
+ }
896
+
897
+ // src/utils/fetch-registry.ts
898
+ async function fetchComponent(registryUrl, name) {
899
+ const url = `${registryUrl}/${name}.json`;
900
+ const res = await fetch(url);
901
+ if (!res.ok) {
902
+ throw new Error(`Failed to fetch component "${name}": HTTP ${res.status}`);
903
+ }
904
+ return res.json();
905
+ }
906
+ async function fetchRegistryIndex(registryUrl) {
907
+ const url = `${registryUrl}/index.json`;
908
+ const res = await fetch(url);
909
+ if (!res.ok) {
910
+ throw new Error(`Failed to fetch registry index: HTTP ${res.status}`);
911
+ }
912
+ return res.json();
913
+ }
914
+
915
+ // src/commands/add.ts
916
+ var __filename = fileURLToPath(import.meta.url);
917
+ var __dirname = path4.dirname(__filename);
918
+ function getTemplatesDir() {
919
+ return path4.resolve(__dirname, "..", "templates");
920
+ }
921
+ async function readBundledTemplate(name, platform) {
922
+ const comp = getComponent(name);
923
+ if (!comp) return null;
924
+ const platformDir = platform === "mobile" ? "mobile" : "web";
925
+ const templatesDir = getTemplatesDir();
926
+ const sourceDir = path4.resolve(templatesDir, platformDir);
927
+ const files = [];
928
+ for (const file of comp.files) {
929
+ const filePath = path4.resolve(sourceDir, file);
930
+ if (await fs4.pathExists(filePath)) {
931
+ const content = await fs4.readFile(filePath, "utf-8");
932
+ files.push({ path: `ui/${file}`, type: "registry:ui", content });
933
+ }
934
+ }
935
+ const utilsPath = path4.resolve(sourceDir, "lib", "utils.ts");
936
+ const utilsContent = await fs4.pathExists(utilsPath) ? await fs4.readFile(utilsPath, "utf-8") : void 0;
937
+ return {
938
+ name: comp.name,
939
+ type: "registry:ui",
940
+ description: comp.description,
941
+ platform: comp.platform,
942
+ dependencies: Object.keys(comp.npmDependencies),
943
+ registryDependencies: comp.internalDependencies,
944
+ files,
945
+ utils: utilsContent ? { path: "lib/utils.ts", content: utilsContent } : void 0
946
+ };
947
+ }
948
+ async function getComponentContent(name, config) {
949
+ if (config.registry) {
950
+ try {
951
+ return await fetchComponent(config.registry, name);
952
+ } catch {
953
+ }
954
+ }
955
+ return readBundledTemplate(name, config.platform);
956
+ }
957
+ async function addCommand(componentNames, opts) {
958
+ const cwd = process.cwd();
959
+ p2.intro(chalk4.bold("Add Rad UI Components"));
960
+ if (!await configExists(cwd)) {
961
+ p2.log.error(
962
+ `Configuration not found. Run ${chalk4.cyan("npx @quarklab/rad-ui init")} first.`
963
+ );
964
+ process.exit(1);
965
+ }
966
+ const config = await readConfig(cwd);
967
+ let availableNames;
968
+ let platformComponents = getComponentsByPlatform(config.platform);
969
+ try {
970
+ if (config.registry) {
971
+ const remoteIndex = await fetchRegistryIndex(config.registry);
972
+ const filtered = config.platform === "both" ? remoteIndex : remoteIndex.filter(
973
+ (c) => c.platform === config.platform || c.platform === "shared"
974
+ );
975
+ availableNames = filtered.map((c) => c.name);
976
+ } else {
977
+ availableNames = platformComponents.map((c) => c.name);
978
+ }
979
+ } catch {
980
+ availableNames = platformComponents.map((c) => c.name);
981
+ }
982
+ let selectedNames;
983
+ if (opts.all) {
984
+ selectedNames = availableNames;
985
+ p2.log.info(`Adding all ${selectedNames.length} components...`);
986
+ } else if (componentNames.length > 0) {
987
+ const invalid = componentNames.filter((n) => !availableNames.includes(n));
988
+ if (invalid.length > 0) {
989
+ p2.log.error(
990
+ `Unknown component(s): ${invalid.map((n) => chalk4.red(n)).join(", ")}`
991
+ );
992
+ p2.log.info(
993
+ `Available: ${availableNames.map((n) => chalk4.cyan(n)).join(", ")}`
994
+ );
995
+ process.exit(1);
996
+ }
997
+ selectedNames = componentNames;
998
+ } else {
999
+ const selected = await p2.multiselect({
1000
+ message: "Which components would you like to add?",
1001
+ options: platformComponents.map((c) => ({
1002
+ value: c.name,
1003
+ label: c.name,
1004
+ hint: c.description
1005
+ })),
1006
+ required: true
1007
+ });
1008
+ if (p2.isCancel(selected)) {
1009
+ p2.cancel("Cancelled.");
1010
+ process.exit(0);
1011
+ }
1012
+ selectedNames = selected;
1013
+ }
1014
+ const allNames = resolveDependencies(selectedNames);
1015
+ const extraDeps = allNames.filter((n) => !selectedNames.includes(n));
1016
+ if (extraDeps.length > 0) {
1017
+ p2.log.info(
1018
+ `Also adding dependencies: ${extraDeps.map((n) => chalk4.cyan(n)).join(", ")}`
1019
+ );
1020
+ }
1021
+ const componentsDir = opts.path ? path4.resolve(cwd, opts.path) : resolveComponentsDir(cwd, config);
1022
+ if (!opts.overwrite) {
1023
+ const existing = [];
1024
+ for (const name of allNames) {
1025
+ const comp = getComponent(name);
1026
+ if (!comp) continue;
1027
+ for (const file of comp.files) {
1028
+ const destPath = path4.resolve(componentsDir, file);
1029
+ if (await fs4.pathExists(destPath)) {
1030
+ existing.push(name);
1031
+ break;
1032
+ }
1033
+ }
1034
+ }
1035
+ if (existing.length > 0) {
1036
+ const shouldOverwrite = await p2.confirm({
1037
+ message: `The following components already exist: ${existing.map((n) => chalk4.yellow(n)).join(", ")}. Overwrite?`,
1038
+ initialValue: false
1039
+ });
1040
+ if (p2.isCancel(shouldOverwrite) || !shouldOverwrite) {
1041
+ const filteredNames = allNames.filter((n) => !existing.includes(n));
1042
+ if (filteredNames.length === 0) {
1043
+ p2.cancel("No components to add.");
1044
+ process.exit(0);
1045
+ }
1046
+ allNames.length = 0;
1047
+ allNames.push(...filteredNames);
1048
+ }
1049
+ }
1050
+ }
1051
+ const s = p2.spinner();
1052
+ s.start("Adding components...");
1053
+ await fs4.ensureDir(componentsDir);
1054
+ let copiedCount = 0;
1055
+ const allDeps = [];
1056
+ for (const name of allNames) {
1057
+ const item = await getComponentContent(name, config);
1058
+ if (!item || item.files.length === 0) {
1059
+ p2.log.warn(`Component "${name}" not found.`);
1060
+ continue;
1061
+ }
1062
+ if (item.dependencies) {
1063
+ allDeps.push(...item.dependencies);
1064
+ }
1065
+ for (const file of item.files) {
1066
+ let content = file.content;
1067
+ content = transformImports(content, config);
1068
+ const fileName = path4.basename(file.path);
1069
+ const destPath = path4.resolve(componentsDir, fileName);
1070
+ await fs4.ensureDir(path4.dirname(destPath));
1071
+ await fs4.writeFile(destPath, content, "utf-8");
1072
+ copiedCount++;
1073
+ }
1074
+ }
1075
+ s.stop(`Added ${copiedCount} component file(s).`);
1076
+ const npmDeps = collectNpmDependencies(allNames);
1077
+ const depsFromRegistry = [...new Set(allDeps)];
1078
+ const depsToInstall = Object.entries(npmDeps).map(
1079
+ ([name, version]) => `${name}@${version}`
1080
+ );
1081
+ for (const dep of depsFromRegistry) {
1082
+ if (!npmDeps[dep]) {
1083
+ depsToInstall.push(dep);
1084
+ }
1085
+ }
1086
+ if (depsToInstall.length > 0) {
1087
+ s.start("Installing dependencies...");
1088
+ const pm = await detectPackageManager(cwd);
1089
+ const installCmd = getInstallCommand2(pm, depsToInstall);
1090
+ try {
1091
+ const { execa } = await import("execa");
1092
+ await execa(installCmd.command, installCmd.args, {
1093
+ cwd,
1094
+ stdio: "pipe"
1095
+ });
1096
+ s.stop("Dependencies installed.");
1097
+ } catch {
1098
+ s.stop("Could not auto-install dependencies.");
1099
+ p2.log.warn(
1100
+ `Please manually install:
1101
+ ${chalk4.cyan(depsToInstall.join(" "))}`
1102
+ );
1103
+ }
1104
+ }
1105
+ logger.break();
1106
+ p2.note(
1107
+ [
1108
+ `Components added to ${chalk4.cyan(componentsDir.replace(cwd + "/", ""))}:`,
1109
+ "",
1110
+ ...allNames.map((n) => ` ${chalk4.green("+")} ${n}`),
1111
+ "",
1112
+ "Import example:",
1113
+ chalk4.gray(
1114
+ ` import { Button } from "${config.aliases.components}/button";`
1115
+ )
1116
+ ].join("\n"),
1117
+ "Done"
1118
+ );
1119
+ p2.outro("Components are ready to use!");
1120
+ }
1121
+ function getInstallCommand2(pm, packages) {
1122
+ switch (pm) {
1123
+ case "pnpm":
1124
+ return { command: "pnpm", args: ["add", ...packages] };
1125
+ case "yarn":
1126
+ return { command: "yarn", args: ["add", ...packages] };
1127
+ case "bun":
1128
+ return { command: "bun", args: ["add", ...packages] };
1129
+ default:
1130
+ return { command: "npm", args: ["install", ...packages] };
1131
+ }
1132
+ }
1133
+
1134
+ // src/index.ts
1135
+ var program = new Command();
1136
+ program.name("rad-ui").description(
1137
+ "CLI for adding Rad UI components to your project.\nBeautiful Persian-themed React components built on Radix UI and Tailwind CSS."
1138
+ ).version("0.1.0");
1139
+ program.command("init").description("Initialize Rad UI in your project").option("-y, --yes", "Skip confirmation prompts and use defaults").action(initCommand);
1140
+ program.command("add").description("Add components to your project").argument("[components...]", "Components to add (space-separated)").option("-a, --all", "Add all available components").option("-o, --overwrite", "Overwrite existing components").option("-p, --path <path>", "Custom path for components").action(addCommand);
1141
+ program.parse();