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 +63 -29
- package/bin/registry.js +221 -0
- package/dist/index.js +2013 -117
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +39 -33
package/bin/cli.js
CHANGED
|
@@ -2,48 +2,82 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Canx UI CLI
|
|
5
|
-
*
|
|
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.
|
|
17
|
-
|
|
18
|
-
if (!existsSync(
|
|
19
|
-
console.log(" Creating
|
|
20
|
-
|
|
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.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
|
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
|
}
|
package/bin/registry.js
ADDED
|
@@ -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
|
+
};
|