canx-ui 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -2,48 +2,82 @@
2
2
 
3
3
  /**
4
4
  * Canx UI CLI
5
- * Simulates 'npx canx-ui init'
5
+ * Supports:
6
+ * - npx canx-ui init (Setup utils and basic css)
7
+ * - npx canx-ui add [component] (Add specific component)
6
8
  */
7
- import { existsSync, writeFileSync, mkdirSync } from "fs";
9
+ import { existsSync, writeFileSync, mkdirSync, readFileSync } from "fs";
8
10
  import { join } from "path";
11
+ import { registry } from "./registry.js";
9
12
 
10
13
  const args = process.argv.slice(2);
11
14
  const command = args[0];
15
+ const componentName = args[1];
16
+
17
+ const COMPONENTS_DIR = join(process.cwd(), "src", "components", "ui");
18
+ const LIB_DIR = join(process.cwd(), "src", "lib");
19
+ const UTILS_PATH = join(LIB_DIR, "utils.ts");
20
+
21
+ // Helper to ensure directory exists
22
+ function ensureDir(dir) {
23
+ if (!existsSync(dir)) {
24
+ mkdirSync(dir, { recursive: true });
25
+ // console.log(` Created directory: ${dir}`);
26
+ }
27
+ }
12
28
 
13
29
  if (command === "init") {
14
30
  console.log("šŸŽØ Initializing Canx UI...");
15
31
 
16
- // 1. Check/Create components directory
17
- const componentsDir = join(process.cwd(), "src", "components", "ui");
18
- if (!existsSync(componentsDir)) {
19
- console.log(" Creating directory: src/components/ui");
20
- mkdirSync(componentsDir, { recursive: true });
32
+ // 1. Create lib/utils.ts
33
+ ensureDir(LIB_DIR);
34
+ if (!existsSync(UTILS_PATH)) {
35
+ console.log(" Creating src/lib/utils.ts...");
36
+ writeFileSync(UTILS_PATH, registry.utils);
37
+ } else {
38
+ console.log(" src/lib/utils.ts already exists.");
21
39
  }
22
40
 
23
- // 2. Create a theme config or necessary utils
24
- const utilsPath = join(process.cwd(), "src", "lib", "utils.ts");
25
- const libDir = join(process.cwd(), "src", "lib");
26
-
27
- if (!existsSync(utilsPath)) {
28
- if (!existsSync(libDir)) mkdirSync(libDir, { recursive: true });
29
-
30
- console.log(" Creating utility: src/lib/utils.ts");
31
- const utilsContent = `import { type ClassValue, clsx } from "clsx";
32
- import { twMerge } from "tailwind-merge";
41
+ // 2. Hint about CSS
42
+ console.log("\nāœ… Initialization complete!");
43
+ console.log(" \nāš ļø Requirement: Make sure you have the following in your global CSS (v4 compatible variables):");
44
+ console.log(" (We didn't overwrite your globals.css to be safe, but here is what you need)");
45
+ console.log(registry.cssVars);
46
+ console.log("\n šŸ“¦ Dependencies needed:");
47
+ console.log(" npm install clsx tailwind-merge class-variance-authority lucide-react @radix-ui/react-slot");
33
48
 
34
- export function cn(...inputs: ClassValue[]) {
35
- return twMerge(clsx(inputs));
36
- }
37
- `;
38
- writeFileSync(utilsPath, utilsContent);
39
- // Note: We assume the user needs to install clsx and tailwind-merge
40
- console.log(" āš ļø Please install dependencies: npm install clsx tailwind-merge");
41
- }
49
+ } else if (command === "add") {
50
+ if (!componentName) {
51
+ console.error("āŒ Please specify a component to add. Example: npx canx-ui add button");
52
+ process.exit(1);
53
+ }
54
+
55
+ const template = registry[componentName.toLowerCase()];
56
+ if (!template) {
57
+ console.error(`āŒ Component '${componentName}' not found in registry.`);
58
+ console.log(" Available components: button, input, modal");
59
+ process.exit(1);
60
+ }
42
61
 
43
- console.log("āœ… Canx UI initialized successfully!");
44
- console.log(" Run 'npm install canx-ui' to use the library components.");
45
- console.log(" Or import them directly if you copied the source.");
62
+ console.log(`šŸ“¦ Adding ${componentName}...`);
63
+
64
+ // Check if initialized
65
+ if (!existsSync(UTILS_PATH)) {
66
+ console.warn("āš ļø src/lib/utils.ts not found. It is recommended to run 'npx canx-ui init' first.");
67
+ }
68
+
69
+ ensureDir(COMPONENTS_DIR);
70
+ const componentPath = join(COMPONENTS_DIR, `${componentName.toLowerCase()}.tsx`);
71
+
72
+ if (existsSync(componentPath)) {
73
+ console.log(` āš ļø ${componentName} already exists at src/components/ui/${componentName.toLowerCase()}.tsx. Skipping.`);
74
+ } else {
75
+ writeFileSync(componentPath, template);
76
+ console.log(` āœ… Created src/components/ui/${componentName.toLowerCase()}.tsx`);
77
+ }
46
78
 
47
79
  } else {
48
- console.log("Unknown command. Try 'init'.");
80
+ console.log("Unknown command. Available commands:");
81
+ console.log(" npx canx-ui init Initialize dependencies and utils");
82
+ console.log(" npx canx-ui add [name] Add a component to your project");
49
83
  }
@@ -0,0 +1,221 @@
1
+
2
+ export const registry = {
3
+ utils: `import { type ClassValue, clsx } from "clsx";
4
+ import { twMerge } from "tailwind-merge";
5
+
6
+ export function cn(...inputs: ClassValue[]) {
7
+ return twMerge(clsx(inputs));
8
+ }
9
+ `,
10
+ button: `"use client";
11
+
12
+ import * as React from "react"
13
+ import { Slot } from "@radix-ui/react-slot"
14
+ import { cva, type VariantProps } from "class-variance-authority"
15
+
16
+ import { cn } from "@/lib/utils"
17
+
18
+ const buttonVariants = cva(
19
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
20
+ {
21
+ variants: {
22
+ variant: {
23
+ default:
24
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
25
+ destructive:
26
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
27
+ outline:
28
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
29
+ secondary:
30
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
31
+ ghost: "hover:bg-accent hover:text-accent-foreground",
32
+ link: "text-primary underline-offset-4 hover:underline",
33
+ },
34
+ size: {
35
+ default: "h-9 px-4 py-2",
36
+ sm: "h-8 rounded-md px-3 text-xs",
37
+ lg: "h-10 rounded-md px-8",
38
+ icon: "h-9 w-9",
39
+ },
40
+ },
41
+ defaultVariants: {
42
+ variant: "default",
43
+ size: "default",
44
+ },
45
+ }
46
+ )
47
+
48
+ export interface ButtonProps
49
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
50
+ VariantProps<typeof buttonVariants> {
51
+ asChild?: boolean
52
+ }
53
+
54
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
55
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
56
+ const Comp = asChild ? Slot : "button"
57
+ return (
58
+ <Comp
59
+ className={cn(buttonVariants({ variant, size, className }))}
60
+ ref={ref}
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+ )
66
+ Button.displayName = "Button"
67
+
68
+ export { Button, buttonVariants }
69
+ `,
70
+ input: `import * as React from "react"
71
+
72
+ import { cn } from "@/lib/utils"
73
+
74
+ export interface InputProps
75
+ extends React.InputHTMLAttributes<HTMLInputElement> {
76
+ label?: string;
77
+ error?: string;
78
+ }
79
+
80
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
81
+ ({ className, type, label, error, ...props }, ref) => {
82
+ return (
83
+ <div className="w-full space-y-2">
84
+ {label && <label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">{label}</label>}
85
+ <input
86
+ type={type}
87
+ className={cn(
88
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
89
+ error && "border-red-500 focus-visible:ring-red-500",
90
+ className
91
+ )}
92
+ ref={ref}
93
+ {...props}
94
+ />
95
+ {error && <p className="text-xs text-red-500">{error}</p>}
96
+ </div>
97
+ )
98
+ }
99
+ )
100
+ Input.displayName = "Input"
101
+
102
+ export { Input }
103
+ `,
104
+ modal: `"use client";
105
+
106
+ import * as React from "react";
107
+ import { cn } from "@/lib/utils";
108
+ import { X } from "lucide-react";
109
+
110
+ interface ModalProps {
111
+ id: string;
112
+ triggerLabel?: string;
113
+ title?: string;
114
+ children: React.ReactNode;
115
+ className?: string;
116
+ }
117
+
118
+ export function Modal({ id, triggerLabel, title, children, className }: ModalProps) {
119
+ const dialogRef = React.useRef<HTMLDialogElement>(null);
120
+
121
+ const openModal = () => {
122
+ dialogRef.current?.showModal();
123
+ };
124
+
125
+ const closeModal = () => {
126
+ dialogRef.current?.close();
127
+ };
128
+
129
+ // Close when clicking outside
130
+ const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
131
+ if (e.target === dialogRef.current) {
132
+ closeModal();
133
+ }
134
+ }
135
+
136
+ return (
137
+ <>
138
+ {triggerLabel && (
139
+ <button
140
+ onClick={openModal}
141
+ className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors text-sm font-medium"
142
+ >
143
+ {triggerLabel}
144
+ </button>
145
+ )}
146
+
147
+ <dialog
148
+ id={id}
149
+ ref={dialogRef}
150
+ onClick={handleBackdropClick}
151
+ className={cn(
152
+ "backdrop:bg-black/50 backdrop:backdrop-blur-sm bg-background text-foreground p-6 rounded-lg shadow-xl w-full max-w-md border border-border m-auto open:animate-in open:fade-in-0 open:zoom-in-95 backdrop:animate-in backdrop:fade-in-0",
153
+ className
154
+ )}
155
+ >
156
+ <div className="flex items-center justify-between mb-4">
157
+ {title && <h3 className="text-lg font-semibold">{title}</h3>}
158
+ <button
159
+ onClick={closeModal}
160
+ className="text-muted-foreground hover:text-foreground transition-colors"
161
+ >
162
+ <X className="w-4 h-4" />
163
+ </button>
164
+ </div>
165
+
166
+ <div className="mt-2">
167
+ {children}
168
+ </div>
169
+ </dialog>
170
+ </>
171
+ );
172
+ }
173
+ `,
174
+ cssVars: `@layer base {
175
+ :root {
176
+ --background: 0 0% 100%;
177
+ --foreground: 240 10% 3.9%;
178
+ --card: 0 0% 100%;
179
+ --card-foreground: 240 10% 3.9%;
180
+ --popover: 0 0% 100%;
181
+ --popover-foreground: 240 10% 3.9%;
182
+ --primary: 240 5.9% 10%;
183
+ --primary-foreground: 0 0% 98%;
184
+ --secondary: 240 4.8% 95.9%;
185
+ --secondary-foreground: 240 5.9% 10%;
186
+ --muted: 240 4.8% 95.9%;
187
+ --muted-foreground: 240 3.8% 46.1%;
188
+ --accent: 240 4.8% 95.9%;
189
+ --accent-foreground: 240 5.9% 10%;
190
+ --destructive: 0 84.2% 60.2%;
191
+ --destructive-foreground: 0 0% 98%;
192
+ --border: 240 5.9% 90%;
193
+ --input: 240 5.9% 90%;
194
+ --ring: 240 5.9% 10%;
195
+ --radius: 0.5rem;
196
+ }
197
+
198
+ .dark {
199
+ --background: 240 10% 3.9%;
200
+ --foreground: 0 0% 98%;
201
+ --card: 240 10% 3.9%;
202
+ --card-foreground: 0 0% 98%;
203
+ --popover: 240 10% 3.9%;
204
+ --popover-foreground: 0 0% 98%;
205
+ --primary: 0 0% 98%;
206
+ --primary-foreground: 240 5.9% 10%;
207
+ --secondary: 240 3.7% 15.9%;
208
+ --secondary-foreground: 0 0% 98%;
209
+ --muted: 240 3.7% 15.9%;
210
+ --muted-foreground: 240 5% 64.9%;
211
+ --accent: 240 3.7% 15.9%;
212
+ --accent-foreground: 0 0% 98%;
213
+ --destructive: 0 62.8% 30.6%;
214
+ --destructive-foreground: 0 0% 98%;
215
+ --border: 240 3.7% 15.9%;
216
+ --input: 240 3.7% 15.9%;
217
+ --ring: 240 4.9% 83.9%;
218
+ }
219
+ }
220
+ `
221
+ };