@alexourus/ui 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs ADDED
@@ -0,0 +1,1556 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import path2 from "path";
8
+ import fs2 from "fs-extra";
9
+ import prompts from "prompts";
10
+ import ora from "ora";
11
+ import { execa } from "execa";
12
+
13
+ // src/utils/logger.ts
14
+ import {
15
+ cyan,
16
+ green,
17
+ yellow,
18
+ red,
19
+ bold,
20
+ dim,
21
+ magenta,
22
+ blue
23
+ } from "kolorist";
24
+ var logger = {
25
+ banner() {
26
+ console.log();
27
+ console.log(
28
+ bold(
29
+ cyan(" \u25B2 alexourus-ui ") + dim("v0.3.0 \u2014 Modern React & Tailwind Design System")
30
+ )
31
+ );
32
+ console.log(
33
+ dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")
34
+ );
35
+ console.log();
36
+ },
37
+ info(...args) {
38
+ console.log(cyan("\u2139"), ...args);
39
+ },
40
+ success(...args) {
41
+ console.log(green("\u2714"), bold(green(String(args[0]))), ...args.slice(1));
42
+ },
43
+ warn(...args) {
44
+ console.log(yellow("\u25B2"), yellow(String(args[0])), ...args.slice(1));
45
+ },
46
+ error(...args) {
47
+ console.log(red("\u2716"), bold(red(String(args[0]))), ...args.slice(1));
48
+ },
49
+ step(step, desc) {
50
+ console.log(` ${magenta(step)} ${desc}`);
51
+ },
52
+ item(title, desc) {
53
+ console.log(` ${blue("\u2022")} ${bold(title)}${desc ? dim(` \u2014 ${desc}`) : ""}`);
54
+ },
55
+ break() {
56
+ console.log();
57
+ }
58
+ };
59
+
60
+ // src/utils/get-package-manager.ts
61
+ import fs from "fs-extra";
62
+ import path from "path";
63
+ async function getPackageManager(cwd = process.cwd()) {
64
+ const userAgent = process.env.npm_config_user_agent || "";
65
+ if (userAgent.startsWith("pnpm")) return "pnpm";
66
+ if (userAgent.startsWith("bun")) return "bun";
67
+ if (userAgent.startsWith("yarn")) return "yarn";
68
+ if (userAgent.startsWith("npm")) return "npm";
69
+ if (await fs.pathExists(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
70
+ if (await fs.pathExists(path.join(cwd, "bun.lockb")) || await fs.pathExists(path.join(cwd, "bun.lock"))) return "bun";
71
+ if (await fs.pathExists(path.join(cwd, "yarn.lock"))) return "yarn";
72
+ if (await fs.pathExists(path.join(cwd, "package-lock.json"))) return "npm";
73
+ return "npm";
74
+ }
75
+ function getInstallCommand(pm, isDev = false) {
76
+ switch (pm) {
77
+ case "pnpm":
78
+ return isDev ? "pnpm add -D" : "pnpm add";
79
+ case "bun":
80
+ return isDev ? "bun add -d" : "bun add";
81
+ case "yarn":
82
+ return isDev ? "yarn add -D" : "yarn add";
83
+ case "npm":
84
+ default:
85
+ return isDev ? "npm install --save-dev" : "npm install";
86
+ }
87
+ }
88
+
89
+ // src/utils/templates.ts
90
+ var UTILS_TEMPLATE = `import { type ClassValue, clsx } from "clsx";
91
+ import { twMerge } from "tailwind-merge";
92
+
93
+ export function cn(...inputs: ClassValue[]) {
94
+ return twMerge(clsx(inputs));
95
+ }
96
+ `;
97
+ var COLOR_THEMES = {
98
+ zinc: {
99
+ light: { primary: "222 47% 11%", primaryForeground: "210 40% 98%", ring: "222 47% 11%" },
100
+ dark: { primary: "0 0% 96%", primaryForeground: "0 0% 6%", ring: "0 0% 60%" }
101
+ },
102
+ emerald: {
103
+ light: { primary: "142.1 76.2% 36.3%", primaryForeground: "355.7 100% 97.3%", ring: "142.1 76.2% 36.3%" },
104
+ dark: { primary: "142.1 70.6% 45.3%", primaryForeground: "144.9 80.4% 10%", ring: "142.4 71.8% 29.2%" }
105
+ },
106
+ violet: {
107
+ light: { primary: "262.1 83.3% 57.8%", primaryForeground: "210 20% 98%", ring: "262.1 83.3% 57.8%" },
108
+ dark: { primary: "263.4 70% 50.4%", primaryForeground: "210 20% 98%", ring: "263.4 70% 50.4%" }
109
+ },
110
+ rose: {
111
+ light: { primary: "346.8 77.2% 49.8%", primaryForeground: "355.7 100% 97.3%", ring: "346.8 77.2% 49.8%" },
112
+ dark: { primary: "346.8 77.2% 49.8%", primaryForeground: "355.7 100% 97.3%", ring: "346.8 77.2% 49.8%" }
113
+ },
114
+ blue: {
115
+ light: { primary: "221.2 83.2% 53.3%", primaryForeground: "210 40% 98%", ring: "221.2 83.2% 53.3%" },
116
+ dark: { primary: "217.2 91.2% 59.8%", primaryForeground: "222.2 47.4% 11.2%", ring: "224.3 76.3% 48%" }
117
+ },
118
+ amber: {
119
+ light: { primary: "24.6 95% 53.1%", primaryForeground: "60 9.1% 97.8%", ring: "24.6 95% 53.1%" },
120
+ dark: { primary: "20.5 90.2% 48.2%", primaryForeground: "60 9.1% 97.8%", ring: "20.5 90.2% 48.2%" }
121
+ },
122
+ cyan: {
123
+ light: { primary: "188 86% 40%", primaryForeground: "210 40% 98%", ring: "188 86% 40%" },
124
+ dark: { primary: "188 86% 53%", primaryForeground: "222 47% 11%", ring: "188 86% 53%" }
125
+ }
126
+ };
127
+ function getCssVariables(theme = "zinc", radius = "0.5rem") {
128
+ const defaultTheme = COLOR_THEMES.zinc;
129
+ const selected = COLOR_THEMES[theme] ?? defaultTheme;
130
+ return `@tailwind base;
131
+ @tailwind components;
132
+ @tailwind utilities;
133
+
134
+ @layer base {
135
+ :root {
136
+ --background: 0 0% 100%;
137
+ --foreground: 222 47% 11%;
138
+ --card: 0 0% 100%;
139
+ --card-foreground: 222 47% 11%;
140
+ --popover: 0 0% 100%;
141
+ --popover-foreground: 222 47% 11%;
142
+ --primary: ${selected.light.primary};
143
+ --primary-foreground: ${selected.light.primaryForeground};
144
+ --secondary: 210 40% 96%;
145
+ --secondary-foreground: 222 47% 11%;
146
+ --muted: 210 40% 96%;
147
+ --muted-foreground: 215 16% 47%;
148
+ --accent: 210 40% 96%;
149
+ --accent-foreground: 222 47% 11%;
150
+ --destructive: 0 84% 60%;
151
+ --destructive-foreground: 210 40% 98%;
152
+ --success: 142 71% 45%;
153
+ --success-foreground: 0 0% 100%;
154
+ --warning: 38 92% 50%;
155
+ --warning-foreground: 222 47% 11%;
156
+ --info: 217 91% 60%;
157
+ --info-foreground: 0 0% 100%;
158
+ --border: 214 32% 91%;
159
+ --input: 214 32% 91%;
160
+ --ring: ${selected.light.ring};
161
+ --radius: ${radius};
162
+ }
163
+
164
+ .dark {
165
+ --background: 0 0% 4%;
166
+ --foreground: 0 0% 96%;
167
+ --card: 0 0% 6%;
168
+ --card-foreground: 0 0% 96%;
169
+ --popover: 0 0% 6%;
170
+ --popover-foreground: 0 0% 96%;
171
+ --primary: ${selected.dark.primary};
172
+ --primary-foreground: ${selected.dark.primaryForeground};
173
+ --secondary: 0 0% 10%;
174
+ --secondary-foreground: 0 0% 90%;
175
+ --muted: 0 0% 10%;
176
+ --muted-foreground: 0 0% 55%;
177
+ --accent: 0 0% 10%;
178
+ --accent-foreground: 0 0% 96%;
179
+ --destructive: 0 63% 31%;
180
+ --destructive-foreground: 0 0% 96%;
181
+ --success: 142 69% 35%;
182
+ --success-foreground: 0 0% 100%;
183
+ --warning: 38 92% 40%;
184
+ --warning-foreground: 0 0% 100%;
185
+ --info: 217 91% 50%;
186
+ --info-foreground: 0 0% 100%;
187
+ --border: 0 0% 12%;
188
+ --input: 0 0% 12%;
189
+ --ring: ${selected.dark.ring};
190
+ }
191
+ }
192
+
193
+ @layer base {
194
+ * {
195
+ @apply border-border;
196
+ }
197
+ body {
198
+ @apply bg-background text-foreground;
199
+ }
200
+ }
201
+ `;
202
+ }
203
+
204
+ // src/commands/init.ts
205
+ async function runInit(options) {
206
+ const cwd = options.cwd || process.cwd();
207
+ logger.banner();
208
+ const pm = await getPackageManager(cwd);
209
+ const hasSrc = await fs2.pathExists(path2.join(cwd, "src"));
210
+ const candidateCss = [
211
+ "src/app/globals.css",
212
+ "app/globals.css",
213
+ "src/styles/globals.css",
214
+ "styles/globals.css",
215
+ "src/index.css",
216
+ "src/main.css",
217
+ "src/App.css"
218
+ ];
219
+ let detectedCss = null;
220
+ for (const file of candidateCss) {
221
+ if (await fs2.pathExists(path2.join(cwd, file))) {
222
+ detectedCss = file;
223
+ break;
224
+ }
225
+ }
226
+ let theme = "zinc";
227
+ let radius = "0.5rem";
228
+ let cssFile = detectedCss || (hasSrc ? "src/app/globals.css" : "app/globals.css");
229
+ let componentsDir = hasSrc ? "src/components/ui" : "components/ui";
230
+ let utilsFile = hasSrc ? "src/lib/utils.ts" : "lib/utils.ts";
231
+ if (detectedCss) {
232
+ logger.info("Proje yap\u0131s\u0131 otomatik alg\u0131land\u0131:");
233
+ console.log(` \u2713 Global CSS: ${cssFile}`);
234
+ console.log(` \u2713 Bile\u015Fenler: ${componentsDir}`);
235
+ console.log(` \u2713 Yard\u0131mc\u0131lar: ${utilsFile}`);
236
+ console.log();
237
+ }
238
+ if (!options.yes) {
239
+ const questions = [
240
+ {
241
+ type: "select",
242
+ name: "theme",
243
+ message: "Hangi temel renk temas\u0131n\u0131 kullanmak istersiniz?",
244
+ choices: [
245
+ { title: "Zinc (Varsay\u0131lan Monochrome)", value: "zinc" },
246
+ { title: "Emerald (Ye\u015Fil)", value: "emerald" },
247
+ { title: "Violet (Mor)", value: "violet" },
248
+ { title: "Rose (G\xFCl)", value: "rose" },
249
+ { title: "Blue (Mavi)", value: "blue" },
250
+ { title: "Amber (Kehribar)", value: "amber" },
251
+ { title: "Cyan (Turkuaz)", value: "cyan" }
252
+ ],
253
+ initial: 0
254
+ },
255
+ {
256
+ type: "select",
257
+ name: "radius",
258
+ message: "K\xF6\u015Fe yuvarlakl\u0131\u011F\u0131 (Border Radius) tercihiniz:",
259
+ choices: [
260
+ { title: "0 (Keskin)", value: "0" },
261
+ { title: "0.3rem (Kompakt)", value: "0.3rem" },
262
+ { title: "0.5rem (Dengeli / Varsay\u0131lan)", value: "0.5rem" },
263
+ { title: "0.75rem (Yuvarlak)", value: "0.75rem" },
264
+ { title: "1.0rem (Geni\u015F)", value: "1.0rem" }
265
+ ],
266
+ initial: 2
267
+ }
268
+ ];
269
+ if (!detectedCss) {
270
+ questions.push({
271
+ type: "text",
272
+ name: "cssFile",
273
+ message: "Global CSS dosyan\u0131z bulunamad\u0131. L\xFCtfen dosya yolunu girin:",
274
+ initial: cssFile
275
+ });
276
+ }
277
+ const response = await prompts(questions);
278
+ if (!response.theme) {
279
+ logger.warn("Kurulum iptal edildi.");
280
+ process.exit(0);
281
+ }
282
+ theme = response.theme;
283
+ radius = response.radius;
284
+ if (response.cssFile) {
285
+ cssFile = response.cssFile;
286
+ }
287
+ }
288
+ const spinner = ora("Proje yap\u0131land\u0131r\u0131l\u0131yor...").start();
289
+ try {
290
+ const config = {
291
+ $schema: "https://alexourus-ui.dev/schema.json",
292
+ style: "default",
293
+ tailwind: {
294
+ config: "tailwind.config.js",
295
+ css: cssFile,
296
+ baseColor: theme,
297
+ cssVariables: true,
298
+ prefix: ""
299
+ },
300
+ aliases: {
301
+ components: componentsDir.replace("/ui", ""),
302
+ utils: utilsFile.replace(/\.ts$/, ""),
303
+ ui: componentsDir
304
+ }
305
+ };
306
+ await fs2.writeJSON(path2.join(cwd, "components.json"), config, { spaces: 2 });
307
+ spinner.succeed("components.json olu\u015Fturuldu");
308
+ const utilsPath = path2.join(cwd, utilsFile);
309
+ await fs2.ensureDir(path2.dirname(utilsPath));
310
+ await fs2.writeFile(utilsPath, UTILS_TEMPLATE, "utf-8");
311
+ logger.success(`${utilsFile} olu\u015Fturuldu.`);
312
+ const cssPath = path2.join(cwd, cssFile);
313
+ await fs2.ensureDir(path2.dirname(cssPath));
314
+ if (!await fs2.pathExists(cssPath)) {
315
+ await fs2.writeFile(cssPath, getCssVariables(theme, radius), "utf-8");
316
+ logger.success(`${cssFile} olu\u015Fturuldu.`);
317
+ }
318
+ const installSpinner = ora(`Temel ba\u011F\u0131ml\u0131l\u0131klar y\xFCkleniyor (${pm})...`).start();
319
+ const coreDeps = ["clsx", "tailwind-merge", "lucide-react", "class-variance-authority", "tailwindcss-animate"];
320
+ try {
321
+ const isDev = false;
322
+ const cmdParts = getInstallCommand(pm, isDev).split(" ");
323
+ const [cmd, ...args] = cmdParts;
324
+ if (cmd) {
325
+ await execa(cmd, [...args, ...coreDeps], { cwd });
326
+ }
327
+ installSpinner.succeed("Ba\u011F\u0131ml\u0131l\u0131klar ba\u015Far\u0131yla y\xFCklendi.");
328
+ } catch {
329
+ installSpinner.warn("Ba\u011F\u0131ml\u0131l\u0131klar otomatik y\xFCklenemedi, l\xFCtfen elle \xE7al\u0131\u015Ft\u0131r\u0131n:");
330
+ console.log(` ${getInstallCommand(pm)} ${coreDeps.join(" ")}`);
331
+ }
332
+ logger.break();
333
+ logger.success("alexourus-ui ba\u015Far\u0131yla kuruldu ve yap\u0131land\u0131r\u0131ld\u0131! \u{1F389}");
334
+ logger.break();
335
+ console.log(" Bile\u015Fen eklemek i\xE7in:");
336
+ console.log(` $ npx @alexourus/cli add button alert input-otp`);
337
+ console.log();
338
+ } catch (error) {
339
+ spinner.fail("Kurulum s\u0131ras\u0131nda bir hata olu\u015Ftu.");
340
+ console.error(error);
341
+ process.exit(1);
342
+ }
343
+ }
344
+
345
+ // src/commands/add.ts
346
+ import path3 from "path";
347
+ import fs3 from "fs-extra";
348
+ import prompts2 from "prompts";
349
+ import ora2 from "ora";
350
+ import { execa as execa2 } from "execa";
351
+
352
+ // src/registry/index.ts
353
+ var REGISTRY = {
354
+ button: {
355
+ name: "button",
356
+ type: "components:ui",
357
+ category: "temel",
358
+ description: "6 varyant, 4 boyut ve loading durumlu buton bile\u015Feni",
359
+ dependencies: ["@radix-ui/react-slot", "class-variance-authority", "lucide-react"],
360
+ devDependencies: [],
361
+ registryDependencies: [],
362
+ files: [
363
+ {
364
+ name: "button.tsx",
365
+ content: `"use client";
366
+
367
+ import * as React from "react";
368
+ import { Slot } from "@radix-ui/react-slot";
369
+ import { cva, type VariantProps } from "class-variance-authority";
370
+ import { Loader2 } from "lucide-react";
371
+ import { cn } from "@/lib/utils";
372
+
373
+ export const buttonVariants = cva(
374
+ "inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 select-none",
375
+ {
376
+ variants: {
377
+ variant: {
378
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90 active:scale-[0.98]",
379
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 active:scale-[0.98]",
380
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground active:scale-[0.98]",
381
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 active:scale-[0.98]",
382
+ ghost: "hover:bg-accent hover:text-accent-foreground active:scale-[0.98]",
383
+ link: "text-primary underline-offset-4 hover:underline",
384
+ },
385
+ size: {
386
+ default: "h-9 px-4 py-2",
387
+ sm: "h-8 rounded-md px-3 text-xs",
388
+ lg: "h-10 rounded-md px-8 text-base",
389
+ icon: "h-9 w-9",
390
+ },
391
+ },
392
+ defaultVariants: {
393
+ variant: "default",
394
+ size: "default",
395
+ },
396
+ }
397
+ );
398
+
399
+ export interface ButtonProps
400
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
401
+ VariantProps<typeof buttonVariants> {
402
+ asChild?: boolean;
403
+ loading?: boolean;
404
+ }
405
+
406
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
407
+ ({ className, variant, size, asChild = false, loading = false, disabled, children, ...props }, ref) => {
408
+ const Comp = asChild ? Slot : "button";
409
+ return (
410
+ <Comp
411
+ className={cn(buttonVariants({ variant, size, className }))}
412
+ ref={ref}
413
+ disabled={disabled || loading}
414
+ {...props}
415
+ >
416
+ {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
417
+ {children}
418
+ </Comp>
419
+ );
420
+ }
421
+ );
422
+ Button.displayName = "Button";
423
+ `
424
+ }
425
+ ]
426
+ },
427
+ badge: {
428
+ name: "badge",
429
+ type: "components:ui",
430
+ category: "temel",
431
+ description: "Durum ve etiket rozetleri (success, warning, destructive, info, dot)",
432
+ dependencies: ["class-variance-authority"],
433
+ devDependencies: [],
434
+ registryDependencies: [],
435
+ files: [
436
+ {
437
+ name: "badge.tsx",
438
+ content: `"use client";
439
+
440
+ import * as React from "react";
441
+ import { cva, type VariantProps } from "class-variance-authority";
442
+ import { cn } from "@/lib/utils";
443
+
444
+ export const badgeVariants = cva(
445
+ "inline-flex items-center gap-1.5 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",
446
+ {
447
+ variants: {
448
+ variant: {
449
+ default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
450
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
451
+ destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
452
+ outline: "text-foreground border-border",
453
+ success: "border-transparent bg-success text-success-foreground shadow hover:bg-success/80",
454
+ warning: "border-transparent bg-warning text-warning-foreground shadow hover:bg-warning/80",
455
+ info: "border-transparent bg-info text-info-foreground shadow hover:bg-info/80",
456
+ },
457
+ },
458
+ defaultVariants: {
459
+ variant: "default",
460
+ },
461
+ }
462
+ );
463
+
464
+ export interface BadgeProps
465
+ extends React.HTMLAttributes<HTMLDivElement>,
466
+ VariantProps<typeof badgeVariants> {
467
+ dot?: boolean;
468
+ }
469
+
470
+ export function Badge({ className, variant, dot = false, children, ...props }: BadgeProps) {
471
+ return (
472
+ <div className={cn(badgeVariants({ variant }), className)} {...props}>
473
+ {dot && <span className="h-1.5 w-1.5 rounded-full bg-current" />}
474
+ {children}
475
+ </div>
476
+ );
477
+ }
478
+ `
479
+ }
480
+ ]
481
+ },
482
+ card: {
483
+ name: "card",
484
+ type: "components:ui",
485
+ category: "temel",
486
+ description: "Header, Content ve Footer b\xF6l\xFCml\xFC kutu konteyner bile\u015Feni",
487
+ dependencies: [],
488
+ devDependencies: [],
489
+ registryDependencies: [],
490
+ files: [
491
+ {
492
+ name: "card.tsx",
493
+ content: `"use client";
494
+
495
+ import * as React from "react";
496
+ import { cn } from "@/lib/utils";
497
+
498
+ export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
499
+ ({ className, ...props }, ref) => (
500
+ <div ref={ref} className={cn("rounded-2xl border border-border bg-card text-card-foreground shadow-subtle", className)} {...props} />
501
+ )
502
+ );
503
+ Card.displayName = "Card";
504
+
505
+ export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
506
+ ({ className, ...props }, ref) => (
507
+ <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
508
+ )
509
+ );
510
+ CardHeader.displayName = "CardHeader";
511
+
512
+ export const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
513
+ ({ className, ...props }, ref) => (
514
+ <h3 ref={ref} className={cn("text-lg font-semibold leading-none tracking-tight", className)} {...props} />
515
+ )
516
+ );
517
+ CardTitle.displayName = "CardTitle";
518
+
519
+ export const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
520
+ ({ className, ...props }, ref) => (
521
+ <p ref={ref} className={cn("text-xs text-muted-foreground", className)} {...props} />
522
+ )
523
+ );
524
+ CardDescription.displayName = "CardDescription";
525
+
526
+ export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
527
+ ({ className, ...props }, ref) => (
528
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
529
+ )
530
+ );
531
+ CardContent.displayName = "CardContent";
532
+
533
+ export const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
534
+ ({ className, ...props }, ref) => (
535
+ <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
536
+ )
537
+ );
538
+ CardFooter.displayName = "CardFooter";
539
+ `
540
+ }
541
+ ]
542
+ },
543
+ dialog: {
544
+ name: "dialog",
545
+ type: "components:ui",
546
+ category: "overlay",
547
+ description: "Eri\u015Filebilir modal diyalog penceresi",
548
+ dependencies: ["@radix-ui/react-dialog", "lucide-react"],
549
+ devDependencies: [],
550
+ registryDependencies: [],
551
+ files: [
552
+ {
553
+ name: "dialog.tsx",
554
+ content: `"use client";
555
+
556
+ import * as React from "react";
557
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
558
+ import { X } from "lucide-react";
559
+ import { cn } from "@/lib/utils";
560
+
561
+ export const Dialog = DialogPrimitive.Root;
562
+ export const DialogTrigger = DialogPrimitive.Trigger;
563
+ export const DialogPortal = DialogPrimitive.Portal;
564
+ export const DialogClose = DialogPrimitive.Close;
565
+
566
+ export const DialogOverlay = React.forwardRef<
567
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
568
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
569
+ >(({ className, ...props }, ref) => (
570
+ <DialogPrimitive.Overlay
571
+ ref={ref}
572
+ className={cn(
573
+ "fixed inset-0 z-50 bg-black/60 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
574
+ className
575
+ )}
576
+ {...props}
577
+ />
578
+ ));
579
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
580
+
581
+ export const DialogContent = React.forwardRef<
582
+ React.ElementRef<typeof DialogPrimitive.Content>,
583
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
584
+ >(({ className, children, ...props }, ref) => (
585
+ <DialogPortal>
586
+ <DialogOverlay />
587
+ <DialogPrimitive.Content
588
+ ref={ref}
589
+ className={cn(
590
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-overlay duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-2xl",
591
+ className
592
+ )}
593
+ {...props}
594
+ >
595
+ {children}
596
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
597
+ <X className="h-4 w-4" />
598
+ <span className="sr-only">Kapat</span>
599
+ </DialogPrimitive.Close>
600
+ </DialogPrimitive.Content>
601
+ </DialogPortal>
602
+ ));
603
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
604
+
605
+ export const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
606
+ <div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
607
+ );
608
+ DialogHeader.displayName = "DialogHeader";
609
+
610
+ export const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
611
+ <div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
612
+ );
613
+ DialogFooter.displayName = "DialogFooter";
614
+
615
+ export const DialogTitle = React.forwardRef<
616
+ React.ElementRef<typeof DialogPrimitive.Title>,
617
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
618
+ >(({ className, ...props }, ref) => (
619
+ <DialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold leading-none tracking-tight", className)} {...props} />
620
+ ));
621
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
622
+
623
+ export const DialogDescription = React.forwardRef<
624
+ React.ElementRef<typeof DialogPrimitive.Description>,
625
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
626
+ >(({ className, ...props }, ref) => (
627
+ <DialogPrimitive.Description ref={ref} className={cn("text-xs text-muted-foreground", className)} {...props} />
628
+ ));
629
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
630
+ `
631
+ }
632
+ ]
633
+ },
634
+ toast: {
635
+ name: "toast",
636
+ type: "components:ui",
637
+ category: "feedback",
638
+ description: "Sonner tabanl\u0131 modern bildirim ve toaster sistemi",
639
+ dependencies: ["sonner"],
640
+ devDependencies: [],
641
+ registryDependencies: [],
642
+ files: [
643
+ {
644
+ name: "toast.tsx",
645
+ content: `"use client";
646
+
647
+ import { Toaster as Sonner, toast } from "sonner";
648
+
649
+ type ToasterProps = React.ComponentProps<typeof Sonner>;
650
+
651
+ export const Toaster = ({ ...props }: ToasterProps) => {
652
+ return (
653
+ <Sonner
654
+ className="toaster group"
655
+ toastOptions={{
656
+ classNames: {
657
+ toast:
658
+ "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg group-[.toaster]:rounded-xl",
659
+ description: "group-[.toast]:text-muted-foreground",
660
+ actionButton:
661
+ "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
662
+ cancelButton:
663
+ "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
664
+ },
665
+ }}
666
+ {...props}
667
+ />
668
+ );
669
+ };
670
+
671
+ export { toast };
672
+ `
673
+ }
674
+ ]
675
+ },
676
+ alert: {
677
+ name: "alert",
678
+ type: "components:ui",
679
+ category: "feedback",
680
+ description: "Farkl\u0131 \xF6nem derecelerine g\xF6re renk ve ikonlu bildirim kutusu",
681
+ dependencies: ["class-variance-authority"],
682
+ devDependencies: [],
683
+ registryDependencies: [],
684
+ files: [
685
+ {
686
+ name: "alert.tsx",
687
+ content: `"use client";
688
+
689
+ import * as React from "react";
690
+ import { cva, type VariantProps } from "class-variance-authority";
691
+ import { cn } from "@/lib/utils";
692
+
693
+ export const alertVariants = cva(
694
+ "relative w-full rounded-xl border p-4 text-sm [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
695
+ {
696
+ variants: {
697
+ variant: {
698
+ default: "bg-background text-foreground border-border",
699
+ info: "bg-info/10 text-info border-info/30 [&>svg]:text-info",
700
+ success: "bg-success/10 text-success border-success/30 [&>svg]:text-success",
701
+ warning: "bg-warning/10 text-warning border-warning/30 [&>svg]:text-warning",
702
+ destructive: "bg-destructive/10 text-destructive border-destructive/30 [&>svg]:text-destructive",
703
+ outline: "bg-transparent text-foreground border-border",
704
+ },
705
+ },
706
+ defaultVariants: {
707
+ variant: "default",
708
+ },
709
+ }
710
+ );
711
+
712
+ export interface AlertProps
713
+ extends React.HTMLAttributes<HTMLDivElement>,
714
+ VariantProps<typeof alertVariants> {}
715
+
716
+ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
717
+ ({ className, variant, ...props }, ref) => (
718
+ <div
719
+ ref={ref}
720
+ role="alert"
721
+ className={cn(alertVariants({ variant }), className)}
722
+ {...props}
723
+ />
724
+ )
725
+ );
726
+ Alert.displayName = "Alert";
727
+
728
+ export const AlertTitle = React.forwardRef<
729
+ HTMLParagraphElement,
730
+ React.HTMLAttributes<HTMLHeadingElement>
731
+ >(({ className, ...props }, ref) => (
732
+ <h5
733
+ ref={ref}
734
+ className={cn("mb-1 font-semibold leading-none tracking-tight", className)}
735
+ {...props}
736
+ />
737
+ ));
738
+ AlertTitle.displayName = "AlertTitle";
739
+
740
+ export const AlertDescription = React.forwardRef<
741
+ HTMLParagraphElement,
742
+ React.HTMLAttributes<HTMLParagraphElement>
743
+ >(({ className, ...props }, ref) => (
744
+ <div
745
+ ref={ref}
746
+ className={cn("text-xs leading-relaxed opacity-90", className)}
747
+ {...props}
748
+ />
749
+ ));
750
+ AlertDescription.displayName = "AlertDescription";
751
+ `
752
+ }
753
+ ]
754
+ },
755
+ "input-otp": {
756
+ name: "input-otp",
757
+ type: "components:ui",
758
+ category: "form",
759
+ description: "2FA ve SMS do\u011Frulama i\xE7in b\xF6l\xFCmlenmi\u015F PIN/OTP giri\u015F bile\u015Feni",
760
+ dependencies: ["input-otp"],
761
+ devDependencies: [],
762
+ registryDependencies: [],
763
+ files: [
764
+ {
765
+ name: "input-otp.tsx",
766
+ content: `"use client";
767
+
768
+ import * as React from "react";
769
+ import { OTPInput, OTPInputContext } from "input-otp";
770
+ import { Minus } from "lucide-react";
771
+ import { cn } from "@/lib/utils";
772
+
773
+ export type InputOTPProps = React.ComponentPropsWithoutRef<typeof OTPInput>;
774
+
775
+ export const InputOTP = React.forwardRef<
776
+ React.ElementRef<typeof OTPInput>,
777
+ InputOTPProps
778
+ >(({ className, containerClassName, ...props }, ref) => (
779
+ <OTPInput
780
+ ref={ref}
781
+ containerClassName={cn("flex items-center gap-2 has-disabled:opacity-50", containerClassName)}
782
+ className={cn("disabled:cursor-not-allowed", className)}
783
+ {...props}
784
+ />
785
+ ));
786
+ InputOTP.displayName = "InputOTP";
787
+
788
+ export const InputOTPGroup = React.forwardRef<
789
+ HTMLDivElement,
790
+ React.HTMLAttributes<HTMLDivElement>
791
+ >(({ className, ...props }, ref) => (
792
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
793
+ ));
794
+ InputOTPGroup.displayName = "InputOTPGroup";
795
+
796
+ export interface InputOTPSlotProps extends React.HTMLAttributes<HTMLDivElement> {
797
+ index: number;
798
+ }
799
+
800
+ export const InputOTPSlot = React.forwardRef<HTMLDivElement, InputOTPSlotProps>(
801
+ ({ index, className, ...props }, ref) => {
802
+ const inputOTPContext = React.useContext(OTPInputContext);
803
+ const { char, hasFakeCaret, isActive } = inputOTPContext?.slots?.[index] ?? {};
804
+
805
+ return (
806
+ <div
807
+ ref={ref}
808
+ className={cn(
809
+ "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm font-semibold transition-all first:rounded-l-lg first:border-l last:rounded-r-lg",
810
+ isActive && "z-10 ring-2 ring-ring ring-offset-background",
811
+ className
812
+ )}
813
+ {...props}
814
+ >
815
+ {char}
816
+ {hasFakeCaret && (
817
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
818
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
819
+ </div>
820
+ )}
821
+ </div>
822
+ );
823
+ }
824
+ );
825
+ InputOTPSlot.displayName = "InputOTPSlot";
826
+
827
+ export const InputOTPSeparator = React.forwardRef<
828
+ HTMLDivElement,
829
+ React.HTMLAttributes<HTMLDivElement>
830
+ >(({ className, ...props }, ref) => (
831
+ <div ref={ref} role="separator" aria-hidden="true" className={cn("text-muted-foreground", className)} {...props}>
832
+ <Minus className="h-4 w-4" />
833
+ </div>
834
+ ));
835
+ InputOTPSeparator.displayName = "InputOTPSeparator";
836
+ `
837
+ }
838
+ ]
839
+ },
840
+ "stat-card": {
841
+ name: "stat-card",
842
+ type: "components:ui",
843
+ category: "temel",
844
+ description: "SaaS dashboard ve KPI panelleri i\xE7in trend ve ikonlu metrik kart\u0131",
845
+ dependencies: ["class-variance-authority", "lucide-react"],
846
+ devDependencies: [],
847
+ registryDependencies: ["card"],
848
+ files: [
849
+ {
850
+ name: "stat-card.tsx",
851
+ content: `"use client";
852
+
853
+ import * as React from "react";
854
+ import { cva, type VariantProps } from "class-variance-authority";
855
+ import { TrendingUp, TrendingDown, Minus } from "lucide-react";
856
+ import { cn } from "@/lib/utils";
857
+
858
+ export const statCardVariants = cva(
859
+ "rounded-2xl border border-border bg-card p-5 text-card-foreground shadow-subtle transition-all duration-200 hover:border-border/80 hover:shadow-card",
860
+ {
861
+ variants: {
862
+ variant: {
863
+ default: "bg-card",
864
+ gradient: "bg-gradient-to-br from-card via-card/90 to-muted/30 border-border/70",
865
+ outline: "bg-transparent border-dashed",
866
+ },
867
+ },
868
+ defaultVariants: {
869
+ variant: "default",
870
+ },
871
+ }
872
+ );
873
+
874
+ export interface StatCardProps
875
+ extends React.HTMLAttributes<HTMLDivElement>,
876
+ VariantProps<typeof statCardVariants> {}
877
+
878
+ export const StatCard = React.forwardRef<HTMLDivElement, StatCardProps>(
879
+ ({ className, variant, ...props }, ref) => (
880
+ <div ref={ref} className={cn(statCardVariants({ variant }), className)} {...props} />
881
+ )
882
+ );
883
+ StatCard.displayName = "StatCard";
884
+
885
+ export const StatCardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
886
+ ({ className, ...props }, ref) => (
887
+ <div ref={ref} className={cn("flex items-center justify-between gap-2 pb-2", className)} {...props} />
888
+ )
889
+ );
890
+ StatCardHeader.displayName = "StatCardHeader";
891
+
892
+ export const StatCardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
893
+ ({ className, ...props }, ref) => (
894
+ <h3 ref={ref} className={cn("text-xs font-semibold text-muted-foreground uppercase tracking-wider", className)} {...props} />
895
+ )
896
+ );
897
+ StatCardTitle.displayName = "StatCardTitle";
898
+
899
+ export const StatCardIcon = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
900
+ ({ className, ...props }, ref) => (
901
+ <div ref={ref} className={cn("flex h-8 w-8 items-center justify-center rounded-xl bg-muted/60 text-foreground", className)} {...props} />
902
+ )
903
+ );
904
+ StatCardIcon.displayName = "StatCardIcon";
905
+
906
+ export const StatCardValue = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
907
+ ({ className, ...props }, ref) => (
908
+ <div ref={ref} className={cn("text-2xl font-bold tracking-tight text-foreground", className)} {...props} />
909
+ )
910
+ );
911
+ StatCardValue.displayName = "StatCardValue";
912
+
913
+ export interface StatCardTrendProps extends React.HTMLAttributes<HTMLSpanElement> {
914
+ trend: "up" | "down" | "neutral";
915
+ value: string;
916
+ }
917
+
918
+ export const StatCardTrend = React.forwardRef<HTMLSpanElement, StatCardTrendProps>(
919
+ ({ trend, value, className, ...props }, ref) => {
920
+ return (
921
+ <span
922
+ ref={ref}
923
+ className={cn(
924
+ "inline-flex items-center gap-1 text-xs font-semibold rounded-md px-1.5 py-0.5",
925
+ trend === "up" && "bg-success/10 text-success",
926
+ trend === "down" && "bg-destructive/10 text-destructive",
927
+ trend === "neutral" && "bg-muted text-muted-foreground",
928
+ className
929
+ )}
930
+ {...props}
931
+ >
932
+ {trend === "up" && <TrendingUp className="h-3 w-3" />}
933
+ {trend === "down" && <TrendingDown className="h-3 w-3" />}
934
+ {trend === "neutral" && <Minus className="h-3 w-3" />}
935
+ {value}
936
+ </span>
937
+ );
938
+ }
939
+ );
940
+ StatCardTrend.displayName = "StatCardTrend";
941
+
942
+ export const StatCardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
943
+ ({ className, ...props }, ref) => (
944
+ <p ref={ref} className={cn("flex items-center gap-2 pt-2 text-xs text-muted-foreground", className)} {...props} />
945
+ )
946
+ );
947
+ StatCardDescription.displayName = "StatCardDescription";
948
+ `
949
+ }
950
+ ]
951
+ },
952
+ rating: {
953
+ name: "rating",
954
+ type: "components:ui",
955
+ category: "form",
956
+ description: "Hover, klavye ve salt okunur mod destekli y\u0131ld\u0131zl\u0131 puanlama bile\u015Feni",
957
+ dependencies: ["class-variance-authority", "lucide-react"],
958
+ devDependencies: [],
959
+ registryDependencies: [],
960
+ files: [
961
+ {
962
+ name: "rating.tsx",
963
+ content: `"use client";
964
+
965
+ import * as React from "react";
966
+ import { Star } from "lucide-react";
967
+ import { cva, type VariantProps } from "class-variance-authority";
968
+ import { cn } from "@/lib/utils";
969
+
970
+ export const ratingVariants = cva("inline-flex items-center gap-1", {
971
+ variants: {
972
+ size: {
973
+ sm: "[&_svg]:h-3.5 [&_svg]:w-3.5",
974
+ md: "[&_svg]:h-4 [&_svg]:w-4",
975
+ lg: "[&_svg]:h-5 [&_svg]:w-5",
976
+ },
977
+ },
978
+ defaultVariants: {
979
+ size: "md",
980
+ },
981
+ });
982
+
983
+ export interface RatingProps
984
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange">,
985
+ VariantProps<typeof ratingVariants> {
986
+ value?: number;
987
+ defaultValue?: number;
988
+ max?: number;
989
+ readOnly?: boolean;
990
+ disabled?: boolean;
991
+ onChange?: (value: number) => void;
992
+ }
993
+
994
+ export const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
995
+ (
996
+ {
997
+ value: controlledValue,
998
+ defaultValue = 0,
999
+ max = 5,
1000
+ readOnly = false,
1001
+ disabled = false,
1002
+ size,
1003
+ className,
1004
+ onChange,
1005
+ ...props
1006
+ },
1007
+ ref
1008
+ ) => {
1009
+ const [internalValue, setInternalValue] = React.useState(defaultValue);
1010
+ const [hoverValue, setHoverValue] = React.useState<number | null>(null);
1011
+
1012
+ const isControlled = controlledValue !== undefined;
1013
+ const currentValue = isControlled ? controlledValue : internalValue;
1014
+
1015
+ const handleClick = (index: number) => {
1016
+ if (readOnly || disabled) return;
1017
+ if (!isControlled) setInternalValue(index);
1018
+ onChange?.(index);
1019
+ };
1020
+
1021
+ return (
1022
+ <div
1023
+ ref={ref}
1024
+ role="slider"
1025
+ aria-valuenow={currentValue}
1026
+ aria-valuemin={0}
1027
+ aria-valuemax={max}
1028
+ aria-label="De\u011Ferlendirme Puan\u0131"
1029
+ tabIndex={readOnly || disabled ? -1 : 0}
1030
+ className={cn(ratingVariants({ size }), disabled && "opacity-50 cursor-not-allowed", className)}
1031
+ {...props}
1032
+ >
1033
+ {Array.from({ length: max }).map((_, i) => {
1034
+ const ratingIndex = i + 1;
1035
+ const isFilled = (hoverValue ?? currentValue) >= ratingIndex;
1036
+ return (
1037
+ <span
1038
+ key={i}
1039
+ onClick={() => handleClick(ratingIndex)}
1040
+ onMouseEnter={() => !readOnly && !disabled && setHoverValue(ratingIndex)}
1041
+ onMouseLeave={() => !readOnly && !disabled && setHoverValue(null)}
1042
+ className={cn("transition-colors", !readOnly && !disabled && "cursor-pointer hover:scale-110")}
1043
+ >
1044
+ <Star
1045
+ className={cn(
1046
+ "transition-colors",
1047
+ isFilled ? "fill-amber-400 text-amber-400" : "fill-transparent text-muted-foreground/40"
1048
+ )}
1049
+ />
1050
+ </span>
1051
+ );
1052
+ })}
1053
+ </div>
1054
+ );
1055
+ }
1056
+ );
1057
+ Rating.displayName = "Rating";
1058
+ `
1059
+ }
1060
+ ]
1061
+ },
1062
+ "pricing-table": {
1063
+ name: "pricing-table",
1064
+ type: "components:block",
1065
+ category: "bloklar",
1066
+ description: "Ayl\u0131k/Y\u0131ll\u0131k indirim d\xF6ng\xFCl\xFC, pop\xFCler rozetli ve \xF6zellik listeli SaaS fiyat tablosu",
1067
+ dependencies: ["lucide-react", "@radix-ui/react-switch"],
1068
+ devDependencies: [],
1069
+ registryDependencies: ["button", "badge", "switch"],
1070
+ files: [
1071
+ {
1072
+ name: "pricing-table.tsx",
1073
+ content: `"use client";
1074
+
1075
+ import * as React from "react";
1076
+ import { Check, X, Sparkles } from "lucide-react";
1077
+ import { cn } from "@/lib/utils";
1078
+
1079
+ export interface PricingFeature {
1080
+ text: string;
1081
+ included: boolean;
1082
+ }
1083
+
1084
+ export interface PricingPlan {
1085
+ id: string;
1086
+ name: string;
1087
+ description: string;
1088
+ priceMonthly: number;
1089
+ priceYearly: number;
1090
+ currency?: string;
1091
+ isPopular?: boolean;
1092
+ popularBadge?: string;
1093
+ features: PricingFeature[];
1094
+ ctaText?: string;
1095
+ ctaVariant?: "default" | "secondary" | "outline";
1096
+ }
1097
+
1098
+ export interface PricingTableProps extends React.HTMLAttributes<HTMLDivElement> {
1099
+ plans: PricingPlan[];
1100
+ onPlanSelect?: (planId: string, billingCycle: "monthly" | "yearly") => void;
1101
+ yearlyDiscountLabel?: string;
1102
+ }
1103
+
1104
+ export function PricingTable({
1105
+ plans,
1106
+ onPlanSelect,
1107
+ yearlyDiscountLabel = "%20 \u0130ndirim",
1108
+ className,
1109
+ ...props
1110
+ }: PricingTableProps) {
1111
+ const [cycle, setCycle] = React.useState<"monthly" | "yearly">("monthly");
1112
+
1113
+ return (
1114
+ <div className={cn("w-full space-y-8", className)} {...props}>
1115
+ {/* Billing Switcher */}
1116
+ <div className="flex items-center justify-center gap-3">
1117
+ <span className={cn("text-xs font-semibold transition-colors", cycle === "monthly" ? "text-foreground" : "text-muted-foreground")}>
1118
+ Ayl\u0131k
1119
+ </span>
1120
+ <button
1121
+ type="button"
1122
+ role="switch"
1123
+ aria-checked={cycle === "yearly"}
1124
+ onClick={() => setCycle((c) => (c === "monthly" ? "yearly" : "monthly"))}
1125
+ className={cn(
1126
+ "relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1127
+ cycle === "yearly" ? "bg-primary" : "bg-muted"
1128
+ )}
1129
+ >
1130
+ <span
1131
+ className={cn(
1132
+ "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform",
1133
+ cycle === "yearly" ? "translate-x-5" : "translate-x-0"
1134
+ )}
1135
+ />
1136
+ </button>
1137
+ <span className={cn("text-xs font-semibold flex items-center gap-1.5 transition-colors", cycle === "yearly" ? "text-foreground" : "text-muted-foreground")}>
1138
+ Y\u0131ll\u0131k
1139
+ <span className="rounded-full bg-success/15 px-2 py-0.5 text-[10px] font-bold text-success">
1140
+ {yearlyDiscountLabel}
1141
+ </span>
1142
+ </span>
1143
+ </div>
1144
+
1145
+ {/* Plans Matrix */}
1146
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
1147
+ {plans.map((plan) => {
1148
+ const price = cycle === "monthly" ? plan.priceMonthly : plan.priceYearly;
1149
+ const currency = plan.currency || "\u20BA";
1150
+
1151
+ return (
1152
+ <div
1153
+ key={plan.id}
1154
+ className={cn(
1155
+ "relative flex flex-col rounded-2xl border p-6 transition-all duration-200",
1156
+ plan.isPopular
1157
+ ? "border-primary bg-card shadow-lg ring-2 ring-primary/20"
1158
+ : "border-border bg-card/60 hover:border-border/80"
1159
+ )}
1160
+ >
1161
+ {plan.isPopular && (
1162
+ <div className="absolute -top-3.5 left-1/2 -translate-x-1/2 flex items-center gap-1 rounded-full bg-primary px-3 py-1 text-[11px] font-bold text-primary-foreground shadow-sm">
1163
+ <Sparkles className="h-3 w-3" />
1164
+ {plan.popularBadge || "En \xC7ok Tercih Edilen"}
1165
+ </div>
1166
+ )}
1167
+
1168
+ <div className="space-y-2 mb-6">
1169
+ <h4 className="text-lg font-bold">{plan.name}</h4>
1170
+ <p className="text-xs text-muted-foreground min-h-[32px]">{plan.description}</p>
1171
+ </div>
1172
+
1173
+ <div className="mb-6 flex items-baseline gap-1">
1174
+ <span className="text-3xl font-extrabold">{currency}{price}</span>
1175
+ <span className="text-xs text-muted-foreground">/{cycle === "monthly" ? "ay" : "y\u0131l"}</span>
1176
+ </div>
1177
+
1178
+ <button
1179
+ type="button"
1180
+ onClick={() => onPlanSelect?.(plan.id, cycle)}
1181
+ className={cn(
1182
+ "w-full h-10 rounded-xl font-semibold text-xs transition-all shadow-sm mb-6",
1183
+ plan.ctaVariant === "outline"
1184
+ ? "border border-border bg-transparent hover:bg-muted text-foreground"
1185
+ : "bg-primary text-primary-foreground hover:bg-primary/90"
1186
+ )}
1187
+ >
1188
+ {plan.ctaText || "Ba\u015Flay\u0131n"}
1189
+ </button>
1190
+
1191
+ <div className="space-y-3 flex-1">
1192
+ <p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Dahil \xD6zellikler</p>
1193
+ {plan.features.map((feature, i) => (
1194
+ <div key={i} className="flex items-center gap-2.5 text-xs">
1195
+ {feature.included ? (
1196
+ <Check className="h-4 w-4 text-success shrink-0" />
1197
+ ) : (
1198
+ <X className="h-4 w-4 text-muted-foreground/50 shrink-0" />
1199
+ )}
1200
+ <span className={feature.included ? "text-foreground" : "text-muted-foreground line-through"}>
1201
+ {feature.text}
1202
+ </span>
1203
+ </div>
1204
+ ))}
1205
+ </div>
1206
+ </div>
1207
+ );
1208
+ })}
1209
+ </div>
1210
+ </div>
1211
+ );
1212
+ }
1213
+ `
1214
+ }
1215
+ ]
1216
+ },
1217
+ "auth-card": {
1218
+ name: "auth-card",
1219
+ type: "components:block",
1220
+ category: "bloklar",
1221
+ description: "Giri\u015F, Kay\u0131t, \u015Eifremi Unuttum ve Sosyal Giri\u015F destekli kimlik do\u011Frulama kart\u0131",
1222
+ dependencies: ["lucide-react"],
1223
+ devDependencies: [],
1224
+ registryDependencies: ["button", "input", "label", "checkbox"],
1225
+ files: [
1226
+ {
1227
+ name: "auth-card.tsx",
1228
+ content: `"use client";
1229
+
1230
+ import * as React from "react";
1231
+ import { Lock, Mail, User, Eye, EyeOff, Loader2 } from "lucide-react";
1232
+ import { cn } from "@/lib/utils";
1233
+
1234
+ export type AuthMode = "login" | "register" | "forgot-password";
1235
+
1236
+ export interface AuthCardProps {
1237
+ initialMode?: AuthMode;
1238
+ onLogin?: (data: { email: string; password: string; rememberMe?: boolean }) => void | Promise<void>;
1239
+ onRegister?: (data: { name: string; email: string; password: string }) => void | Promise<void>;
1240
+ onForgotPassword?: (email: string) => void | Promise<void>;
1241
+ onSocialLogin?: (provider: "github" | "google") => void;
1242
+ className?: string;
1243
+ }
1244
+
1245
+ export function AuthCard({
1246
+ initialMode = "login",
1247
+ onLogin,
1248
+ onRegister,
1249
+ onForgotPassword,
1250
+ onSocialLogin,
1251
+ className,
1252
+ }: AuthCardProps) {
1253
+ const [mode, setMode] = React.useState<AuthMode>(initialMode);
1254
+ const [name, setName] = React.useState("");
1255
+ const [email, setEmail] = React.useState("");
1256
+ const [password, setPassword] = React.useState("");
1257
+ const [rememberMe, setRememberMe] = React.useState(false);
1258
+ const [showPassword, setShowPassword] = React.useState(false);
1259
+ const [loading, setLoading] = React.useState(false);
1260
+
1261
+ const handleSubmit = async (e: React.FormEvent) => {
1262
+ e.preventDefault();
1263
+ setLoading(true);
1264
+ try {
1265
+ if (mode === "login") {
1266
+ await onLogin?.({ email, password, rememberMe });
1267
+ } else if (mode === "register") {
1268
+ await onRegister?.({ name, email, password });
1269
+ } else {
1270
+ await onForgotPassword?.(email);
1271
+ }
1272
+ } finally {
1273
+ setLoading(false);
1274
+ }
1275
+ };
1276
+
1277
+ return (
1278
+ <div className={cn("w-full max-w-md mx-auto rounded-2xl border border-border bg-card p-8 shadow-card", className)}>
1279
+ <div className="text-center space-y-2 mb-6">
1280
+ <h3 className="text-xl font-bold tracking-tight">
1281
+ {mode === "login" && "Tekrar Ho\u015F Geldiniz"}
1282
+ {mode === "register" && "Hesap Olu\u015Fturun"}
1283
+ {mode === "forgot-password" && "\u015Eifrenizi S\u0131f\u0131rlay\u0131n"}
1284
+ </h3>
1285
+ <p className="text-xs text-muted-foreground">
1286
+ {mode === "login" && "Devam etmek i\xE7in bilgilerinizi girin"}
1287
+ {mode === "register" && "Saniyeler i\xE7inde aram\u0131za kat\u0131l\u0131n"}
1288
+ {mode === "forgot-password" && "E-posta adresinize s\u0131f\u0131rlama linki g\xF6nderece\u011Fiz"}
1289
+ </p>
1290
+ </div>
1291
+
1292
+ <form onSubmit={handleSubmit} className="space-y-4">
1293
+ {mode === "register" && (
1294
+ <div className="space-y-1.5">
1295
+ <label className="text-xs font-semibold">Ad Soyad</label>
1296
+ <div className="relative">
1297
+ <User className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
1298
+ <input
1299
+ type="text"
1300
+ required
1301
+ value={name}
1302
+ onChange={(e) => setName(e.target.value)}
1303
+ placeholder="Ahmet Y\u0131lmaz"
1304
+ className="w-full pl-9 pr-3 py-2 text-xs rounded-xl border border-border bg-background focus:outline-none focus:ring-2 focus:ring-ring"
1305
+ />
1306
+ </div>
1307
+ </div>
1308
+ )}
1309
+
1310
+ <div className="space-y-1.5">
1311
+ <label className="text-xs font-semibold">E-posta</label>
1312
+ <div className="relative">
1313
+ <Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
1314
+ <input
1315
+ type="email"
1316
+ required
1317
+ value={email}
1318
+ onChange={(e) => setEmail(e.target.value)}
1319
+ placeholder="ornek@alanadi.com"
1320
+ className="w-full pl-9 pr-3 py-2 text-xs rounded-xl border border-border bg-background focus:outline-none focus:ring-2 focus:ring-ring"
1321
+ />
1322
+ </div>
1323
+ </div>
1324
+
1325
+ {mode !== "forgot-password" && (
1326
+ <div className="space-y-1.5">
1327
+ <div className="flex items-center justify-between">
1328
+ <label className="text-xs font-semibold">\u015Eifre</label>
1329
+ {mode === "login" && (
1330
+ <button
1331
+ type="button"
1332
+ onClick={() => setMode("forgot-password")}
1333
+ className="text-[11px] text-muted-foreground hover:text-foreground font-medium"
1334
+ >
1335
+ \u015Eifremi Unuttum?
1336
+ </button>
1337
+ )}
1338
+ </div>
1339
+ <div className="relative">
1340
+ <Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
1341
+ <input
1342
+ type={showPassword ? "text" : "password"}
1343
+ required
1344
+ value={password}
1345
+ onChange={(e) => setPassword(e.target.value)}
1346
+ placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
1347
+ className="w-full pl-9 pr-10 py-2 text-xs rounded-xl border border-border bg-background focus:outline-none focus:ring-2 focus:ring-ring"
1348
+ />
1349
+ <button
1350
+ type="button"
1351
+ onClick={() => setShowPassword(!showPassword)}
1352
+ className="absolute right-3 top-3 text-muted-foreground hover:text-foreground"
1353
+ >
1354
+ {showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
1355
+ </button>
1356
+ </div>
1357
+ </div>
1358
+ )}
1359
+
1360
+ <button
1361
+ type="submit"
1362
+ disabled={loading}
1363
+ className="w-full h-10 rounded-xl bg-primary text-primary-foreground font-semibold text-xs flex items-center justify-center gap-2 hover:bg-primary/90 transition-all shadow-sm"
1364
+ >
1365
+ {loading && <Loader2 className="h-4 w-4 animate-spin" />}
1366
+ {mode === "login" && "Giri\u015F Yap"}
1367
+ {mode === "register" && "Kay\u0131t Ol"}
1368
+ {mode === "forgot-password" && "S\u0131f\u0131rlama Ba\u011Flant\u0131s\u0131 G\xF6nder"}
1369
+ </button>
1370
+ </form>
1371
+
1372
+ <div className="mt-6 text-center text-xs text-muted-foreground">
1373
+ {mode === "login" ? (
1374
+ <p>
1375
+ Hesab\u0131n\u0131z yok mu?{" "}
1376
+ <button type="button" onClick={() => setMode("register")} className="font-semibold text-foreground hover:underline">
1377
+ Kay\u0131t Olun
1378
+ </button>
1379
+ </p>
1380
+ ) : (
1381
+ <p>
1382
+ Zaten hesab\u0131n\u0131z var m\u0131?{" "}
1383
+ <button type="button" onClick={() => setMode("login")} className="font-semibold text-foreground hover:underline">
1384
+ Giri\u015F Yap\u0131n
1385
+ </button>
1386
+ </p>
1387
+ )}
1388
+ </div>
1389
+ </div>
1390
+ );
1391
+ }
1392
+ `
1393
+ }
1394
+ ]
1395
+ }
1396
+ };
1397
+
1398
+ // src/commands/add.ts
1399
+ async function runAdd(components, options) {
1400
+ const cwd = options.cwd || process.cwd();
1401
+ logger.banner();
1402
+ let config = {
1403
+ style: "default",
1404
+ tailwind: {
1405
+ config: "tailwind.config.js",
1406
+ css: "app/globals.css",
1407
+ baseColor: "zinc",
1408
+ cssVariables: true,
1409
+ prefix: ""
1410
+ },
1411
+ aliases: {
1412
+ components: "@/components",
1413
+ utils: "@/lib/utils",
1414
+ ui: "@/components/ui"
1415
+ }
1416
+ };
1417
+ const configPath = path3.join(cwd, "components.json");
1418
+ if (await fs3.pathExists(configPath)) {
1419
+ try {
1420
+ config = await fs3.readJSON(configPath);
1421
+ } catch {
1422
+ }
1423
+ }
1424
+ let selectedComponents = [];
1425
+ if (options.all) {
1426
+ selectedComponents = Object.keys(REGISTRY);
1427
+ } else if (components.length > 0) {
1428
+ selectedComponents = components;
1429
+ } else {
1430
+ const choices = Object.entries(REGISTRY).map(([key, item]) => ({
1431
+ title: `${item.name} (${item.category}) \u2014 ${item.description}`,
1432
+ value: key
1433
+ }));
1434
+ const response = await prompts2({
1435
+ type: "multiselect",
1436
+ name: "components",
1437
+ message: "Eklemek istedi\u011Finiz bile\u015Fenleri se\xE7in (Bo\u015Fluk tu\u015Fu ile se\xE7in):",
1438
+ choices,
1439
+ min: 1,
1440
+ hint: "- Bo\u015Fluk tu\u015Fu ile se\xE7in, Enter ile onaylay\u0131n"
1441
+ });
1442
+ if (!response.components || response.components.length === 0) {
1443
+ logger.warn("Hi\xE7bir bile\u015Fen se\xE7ilmedi.");
1444
+ process.exit(0);
1445
+ }
1446
+ selectedComponents = response.components;
1447
+ }
1448
+ const spinner = ora2("Bile\u015Fenler y\xFCkleniyor...").start();
1449
+ const dependenciesToInstall = /* @__PURE__ */ new Set();
1450
+ const uiDir = path3.join(cwd, config.aliases.ui.replace(/^@\//, ""));
1451
+ await fs3.ensureDir(uiDir);
1452
+ for (const compName of selectedComponents) {
1453
+ const registryItem = REGISTRY[compName];
1454
+ if (!registryItem) {
1455
+ logger.warn(`'${compName}' ad\u0131nda bir bile\u015Fen registry'de bulunamad\u0131.`);
1456
+ continue;
1457
+ }
1458
+ for (const dep of registryItem.dependencies) {
1459
+ dependenciesToInstall.add(dep);
1460
+ }
1461
+ for (const file of registryItem.files) {
1462
+ const targetFilePath = path3.join(uiDir, file.name);
1463
+ if (await fs3.pathExists(targetFilePath) && !options.overwrite) {
1464
+ logger.item(file.name, "Mevcut dosya atland\u0131 (\xFCzerine yazmak i\xE7in --overwrite kullan\u0131n)");
1465
+ continue;
1466
+ }
1467
+ await fs3.writeFile(targetFilePath, file.content, "utf-8");
1468
+ logger.item(file.name, `-> ${path3.relative(cwd, targetFilePath)}`);
1469
+ }
1470
+ }
1471
+ spinner.succeed("Bile\u015Fen dosyalar\u0131 projeye ba\u015Far\u0131yla eklendi.");
1472
+ if (dependenciesToInstall.size > 0) {
1473
+ const pm = await getPackageManager(cwd);
1474
+ const installSpinner = ora2(`Ba\u011F\u0131ml\u0131l\u0131klar y\xFCkleniyor: ${Array.from(dependenciesToInstall).join(", ")}...`).start();
1475
+ try {
1476
+ const isDev = false;
1477
+ const cmdParts = getInstallCommand(pm, isDev).split(" ");
1478
+ const [cmd, ...args] = cmdParts;
1479
+ if (cmd) {
1480
+ await execa2(cmd, [...args, ...Array.from(dependenciesToInstall)], { cwd });
1481
+ }
1482
+ installSpinner.succeed("T\xFCm ba\u011F\u0131ml\u0131l\u0131klar ba\u015Far\u0131yla y\xFCklendi.");
1483
+ } catch {
1484
+ installSpinner.warn("Ba\u011F\u0131ml\u0131l\u0131klar otomatik y\xFCklenemedi. L\xFCtfen elle y\xFCkleyin:");
1485
+ console.log(` ${getInstallCommand(pm)} ${Array.from(dependenciesToInstall).join(" ")}`);
1486
+ }
1487
+ }
1488
+ logger.break();
1489
+ logger.success("Bile\u015Fenler kullan\u0131ma haz\u0131r! \u{1F680}");
1490
+ logger.break();
1491
+ }
1492
+
1493
+ // src/commands/list.ts
1494
+ import { bold as bold2, cyan as cyan2, dim as dim2, green as green2, magenta as magenta2, yellow as yellow2 } from "kolorist";
1495
+ async function runList() {
1496
+ logger.banner();
1497
+ console.log(bold2(" \u{1F4E6} Kullan\u0131labilir Bile\u015Fenler ve Bloklar:\n"));
1498
+ const categories = {
1499
+ temel: [],
1500
+ form: [],
1501
+ feedback: [],
1502
+ overlay: [],
1503
+ navigation: [],
1504
+ bloklar: []
1505
+ };
1506
+ for (const [key, item] of Object.entries(REGISTRY)) {
1507
+ const list = categories[item.category];
1508
+ if (list) {
1509
+ list.push(key);
1510
+ } else {
1511
+ categories.temel?.push(key);
1512
+ }
1513
+ }
1514
+ const categoryTitles = {
1515
+ temel: "Temel Bile\u015Fenler (Core / Base)",
1516
+ form: "Form & Girdi Bile\u015Fenleri (Forms & Inputs)",
1517
+ feedback: "Geri Bildirim & Durum (Feedback & Status)",
1518
+ overlay: "A\xE7\u0131l\u0131r Pencereler & Katmanlar (Overlays & Dialogs)",
1519
+ navigation: "Navigasyon & D\xFCzen (Navigation & Layout)",
1520
+ bloklar: "Haz\u0131r SaaS Bloklar\u0131 (Composite Blocks)"
1521
+ };
1522
+ for (const [cat, compKeys] of Object.entries(categories)) {
1523
+ if (compKeys.length === 0) continue;
1524
+ console.log(` ${magenta2("\u25CF")} ${bold2(categoryTitles[cat] || cat)}`);
1525
+ for (const key of compKeys) {
1526
+ const item = REGISTRY[key];
1527
+ console.log(
1528
+ ` ${cyan2("\u2022")} ${bold2(item.name.padEnd(16))} ${dim2(item.description)}`
1529
+ );
1530
+ if (item.dependencies.length > 0) {
1531
+ console.log(
1532
+ ` ${dim2("deps:")} ${green2(item.dependencies.join(", "))}`
1533
+ );
1534
+ }
1535
+ }
1536
+ console.log();
1537
+ }
1538
+ console.log(
1539
+ dim2(" Bir bile\u015Feni eklemek i\xE7in:") + bold2(yellow2(" npx @alexourus/cli add <bile\u015Fen-ad\u0131>"))
1540
+ );
1541
+ console.log();
1542
+ }
1543
+
1544
+ // src/index.ts
1545
+ var program = new Command();
1546
+ program.name("alexourus-ui").description("alexourus-ui \u2014 Modern React ve Tailwind bile\u015Fen k\xFCt\xFCphanesi CLI arac\u0131").version("0.3.2");
1547
+ program.command("init").description("Projenizde alexourus-ui yap\u0131land\u0131rmas\u0131n\u0131 ba\u015Flat\u0131n ve temel dosyalar\u0131 olu\u015Fturun").option("-y, --yes", "T\xFCm sorulara varsay\u0131lan yan\u0131tlar\u0131 vererek otomatik kur").option("-c, --cwd <cwd>", "\xC7al\u0131\u015Fma dizini", process.cwd()).action(async (opts) => {
1548
+ await runInit(opts);
1549
+ });
1550
+ program.command("add [components...]").description("Projenize bir veya daha fazla alexourus-ui bile\u015Feni ekleyin").option("-a, --all", "T\xFCm bile\u015Fenleri ve bloklar\u0131 projeye ekle").option("-o, --overwrite", "Mevcut bile\u015Fen dosyalar\u0131n\u0131n \xFCzerine yaz").option("-c, --cwd <cwd>", "\xC7al\u0131\u015Fma dizini", process.cwd()).option("-y, --yes", "Onay istemeden do\u011Frudan kur").action(async (components, opts) => {
1551
+ await runAdd(components, opts);
1552
+ });
1553
+ program.command("list").description("Kullan\u0131labilir t\xFCm alexourus-ui bile\u015Fenlerini ve bloklar\u0131n\u0131 listeler").action(async () => {
1554
+ await runList();
1555
+ });
1556
+ program.parse(process.argv);