nexoreui-cli 0.1.0 → 0.1.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/README.md +65 -0
- package/dist/index.js +2035 -200
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -26,6 +26,101 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
// src/commands/add.ts
|
|
27
27
|
var fs3 = __toESM(require("fs"));
|
|
28
28
|
var path3 = __toESM(require("path"));
|
|
29
|
+
var readline = __toESM(require("readline"));
|
|
30
|
+
var import_child_process = require("child_process");
|
|
31
|
+
|
|
32
|
+
// src/utils/detect.ts
|
|
33
|
+
var fs = __toESM(require("fs"));
|
|
34
|
+
var path = __toESM(require("path"));
|
|
35
|
+
function detectProject(cwd = process.cwd()) {
|
|
36
|
+
let packageManager = "npm";
|
|
37
|
+
let projectType = "unknown";
|
|
38
|
+
let hasSrcDir = false;
|
|
39
|
+
let currentDir = cwd;
|
|
40
|
+
let baseDir = cwd;
|
|
41
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
42
|
+
if (fs.existsSync(path.join(currentDir, "package.json"))) {
|
|
43
|
+
baseDir = currentDir;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
currentDir = path.dirname(currentDir);
|
|
47
|
+
}
|
|
48
|
+
if (fs.existsSync(path.join(baseDir, "pnpm-lock.yaml"))) {
|
|
49
|
+
packageManager = "pnpm";
|
|
50
|
+
} else if (fs.existsSync(path.join(baseDir, "yarn.lock"))) {
|
|
51
|
+
packageManager = "yarn";
|
|
52
|
+
} else if (fs.existsSync(path.join(baseDir, "bun.lockb")) || fs.existsSync(path.join(baseDir, "bun.lock"))) {
|
|
53
|
+
packageManager = "bun";
|
|
54
|
+
}
|
|
55
|
+
if (fs.existsSync(path.join(baseDir, "src"))) {
|
|
56
|
+
hasSrcDir = true;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const packageJsonPath = path.join(baseDir, "package.json");
|
|
60
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
61
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
62
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
63
|
+
if (deps["next"]) {
|
|
64
|
+
projectType = "next";
|
|
65
|
+
} else if (deps["vite"] || deps["@tailwindcss/vite"]) {
|
|
66
|
+
projectType = "vite";
|
|
67
|
+
} else if (deps["react-scripts"]) {
|
|
68
|
+
projectType = "cra";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
packageManager,
|
|
75
|
+
projectType,
|
|
76
|
+
hasSrcDir,
|
|
77
|
+
baseDir
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/utils/copy.ts
|
|
82
|
+
var fs2 = __toESM(require("fs"));
|
|
83
|
+
var path2 = __toESM(require("path"));
|
|
84
|
+
var CN_TEMPLATE = `import { type ClassValue, clsx } from "clsx"
|
|
85
|
+
import { twMerge } from "tailwind-merge"
|
|
86
|
+
|
|
87
|
+
export function cn(...inputs: ClassValue[]) {
|
|
88
|
+
return twMerge(clsx(inputs))
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
function ensureDir(dirPath) {
|
|
92
|
+
if (!fs2.existsSync(dirPath)) {
|
|
93
|
+
fs2.mkdirSync(dirPath, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function getRelativeImportPath(fromDir, toFile) {
|
|
97
|
+
let relativePath = path2.relative(fromDir, toFile);
|
|
98
|
+
relativePath = relativePath.replace(/\\/g, "/");
|
|
99
|
+
relativePath = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
100
|
+
if (!relativePath.startsWith(".")) {
|
|
101
|
+
relativePath = "./" + relativePath;
|
|
102
|
+
}
|
|
103
|
+
return relativePath;
|
|
104
|
+
}
|
|
105
|
+
function ensureCnUtil(utilsPath) {
|
|
106
|
+
const dir = path2.dirname(utilsPath);
|
|
107
|
+
ensureDir(dir);
|
|
108
|
+
if (!fs2.existsSync(utilsPath)) {
|
|
109
|
+
fs2.writeFileSync(utilsPath, CN_TEMPLATE, "utf8");
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function copyComponentFile(content, targetFilePath, utilsFilePath) {
|
|
115
|
+
const targetDir = path2.dirname(targetFilePath);
|
|
116
|
+
ensureDir(targetDir);
|
|
117
|
+
const relativeImport = getRelativeImportPath(targetDir, utilsFilePath);
|
|
118
|
+
const rewrittenContent = content.replace(
|
|
119
|
+
/['"]\.\.\/utils\/cn['"]/g,
|
|
120
|
+
`"${relativeImport}"`
|
|
121
|
+
);
|
|
122
|
+
fs2.writeFileSync(targetFilePath, rewrittenContent, "utf8");
|
|
123
|
+
}
|
|
29
124
|
|
|
30
125
|
// src/registry/button.ts
|
|
31
126
|
var button = {
|
|
@@ -34,7 +129,8 @@ var button = {
|
|
|
34
129
|
"class-variance-authority",
|
|
35
130
|
"clsx",
|
|
36
131
|
"tailwind-merge",
|
|
37
|
-
"framer-motion"
|
|
132
|
+
"framer-motion",
|
|
133
|
+
"lucide-react"
|
|
38
134
|
],
|
|
39
135
|
fileName: "button.tsx",
|
|
40
136
|
content: `'use client';
|
|
@@ -42,28 +138,34 @@ var button = {
|
|
|
42
138
|
import * as React from 'react';
|
|
43
139
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
44
140
|
import { cn } from '../utils/cn';
|
|
45
|
-
import { motion, HTMLMotionProps
|
|
141
|
+
import { motion, HTMLMotionProps } from 'framer-motion';
|
|
142
|
+
import { Loader2 } from 'lucide-react';
|
|
46
143
|
|
|
47
144
|
const buttonVariants = cva(
|
|
48
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-
|
|
145
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 active:scale-95",
|
|
49
146
|
{
|
|
50
147
|
variants: {
|
|
51
148
|
variant: {
|
|
52
|
-
default: "bg-gradient-to-br from-primary to-primary/80 text-primary-foreground shadow-lg shadow-primary/
|
|
149
|
+
default: "bg-gradient-to-br from-primary to-primary/80 text-primary-foreground shadow-lg shadow-primary/10 hover:shadow-xl hover:shadow-primary/20",
|
|
53
150
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 border border-border/50",
|
|
54
|
-
destructive: "bg-gradient-to-br from-destructive to-destructive/80 text-destructive-foreground shadow-lg shadow-destructive/
|
|
151
|
+
destructive: "bg-gradient-to-br from-destructive to-destructive/80 text-destructive-foreground shadow-lg shadow-destructive/10 hover:shadow-xl hover:shadow-destructive/20",
|
|
55
152
|
outline: "border-2 border-input bg-background hover:bg-accent hover:text-accent-foreground hover:border-accent",
|
|
56
153
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
57
154
|
link: "text-primary underline-offset-4 hover:underline",
|
|
58
|
-
//
|
|
155
|
+
// Premium variants
|
|
59
156
|
premium: "bg-gradient-to-r from-violet-600 via-pink-600 to-orange-500 text-white shadow-lg shadow-purple-500/20 hover:shadow-xl hover:shadow-purple-500/30",
|
|
60
|
-
neon: "bg-background border-2 border-primary text-foreground shadow-[0_0_15px_rgba(var(--primary-rgb),0.
|
|
157
|
+
neon: "bg-background border-2 border-primary text-foreground shadow-[0_0_15px_rgba(var(--primary-rgb),0.3)] hover:shadow-[0_0_25px_rgba(var(--primary-rgb),0.5)]",
|
|
61
158
|
glass: "backdrop-blur-md bg-white/10 dark:bg-black/20 border border-white/20 dark:border-white/10 text-foreground hover:bg-white/20 dark:hover:bg-black/30 shadow-lg",
|
|
62
159
|
shimmer: "relative overflow-hidden bg-slate-900 text-white dark:bg-white dark:text-black",
|
|
160
|
+
// New requested variants
|
|
161
|
+
gradient: "bg-gradient-to-r from-indigo-500 via-purple-500 to-violet-600 text-white shadow-lg shadow-indigo-500/20 hover:shadow-xl hover:shadow-indigo-500/30 hover:opacity-95",
|
|
162
|
+
glow: "bg-primary text-primary-foreground shadow-[0_0_12px_rgba(var(--primary-rgb),0.3)] hover:shadow-[0_0_24px_rgba(var(--primary-rgb),0.6)] border border-primary/20",
|
|
163
|
+
magnetic: "bg-gradient-to-br from-violet-600 to-indigo-600 text-white shadow-md hover:shadow-lg",
|
|
164
|
+
loading: "bg-primary/80 text-primary-foreground/80 pointer-events-none cursor-wait",
|
|
63
165
|
},
|
|
64
166
|
size: {
|
|
65
167
|
default: "h-10 px-5 py-2",
|
|
66
|
-
sm: "h-9 rounded-
|
|
168
|
+
sm: "h-9 rounded-lg px-3 text-xs",
|
|
67
169
|
lg: "h-11 rounded-xl px-8 text-base",
|
|
68
170
|
icon: "h-10 w-10 rounded-full",
|
|
69
171
|
},
|
|
@@ -75,23 +177,70 @@ const buttonVariants = cva(
|
|
|
75
177
|
}
|
|
76
178
|
);
|
|
77
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Props for the Button component
|
|
182
|
+
*/
|
|
78
183
|
export interface ButtonProps
|
|
79
184
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
80
185
|
VariantProps<typeof buttonVariants> {
|
|
81
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* Enable hover/tap spring motion animation
|
|
188
|
+
* @default true
|
|
189
|
+
*/
|
|
82
190
|
animate?: boolean;
|
|
83
|
-
/**
|
|
191
|
+
/**
|
|
192
|
+
* Enable shimmer light animation effect
|
|
193
|
+
* @default false
|
|
194
|
+
*/
|
|
84
195
|
shimmer?: boolean;
|
|
85
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Enable neon glow effect
|
|
198
|
+
* @default false
|
|
199
|
+
*/
|
|
86
200
|
glow?: boolean;
|
|
87
|
-
|
|
88
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Display loading spinner icon and disable actions
|
|
203
|
+
* @default false
|
|
204
|
+
*/
|
|
205
|
+
isLoading?: boolean;
|
|
89
206
|
}
|
|
90
207
|
|
|
91
208
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
92
|
-
(
|
|
93
|
-
|
|
209
|
+
(
|
|
210
|
+
{
|
|
211
|
+
className,
|
|
212
|
+
variant,
|
|
213
|
+
size,
|
|
214
|
+
animate = true,
|
|
215
|
+
shimmer = false,
|
|
216
|
+
glow = false,
|
|
217
|
+
isLoading = false,
|
|
218
|
+
children,
|
|
219
|
+
...props
|
|
220
|
+
},
|
|
221
|
+
ref
|
|
222
|
+
) => {
|
|
94
223
|
const isShimmer = variant === 'shimmer' || shimmer;
|
|
224
|
+
const isMagnetic = variant === 'magnetic';
|
|
225
|
+
const isGlow = variant === 'glow' || glow;
|
|
226
|
+
|
|
227
|
+
// Track mouse coords for magnetic hover movement
|
|
228
|
+
const [magneticPos, setMagneticPos] = React.useState({ x: 0, y: 0 });
|
|
229
|
+
|
|
230
|
+
const handleMouseMove = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
231
|
+
if (!isMagnetic) return;
|
|
232
|
+
const { clientX, clientY, currentTarget } = e;
|
|
233
|
+
const { left, top, width, height } = currentTarget.getBoundingClientRect();
|
|
234
|
+
const x = clientX - (left + width / 2);
|
|
235
|
+
const y = clientY - (top + height / 2);
|
|
236
|
+
// spring weight multiplier
|
|
237
|
+
setMagneticPos({ x: x * 0.35, y: y * 0.35 });
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const handleMouseLeave = () => {
|
|
241
|
+
if (!isMagnetic) return;
|
|
242
|
+
setMagneticPos({ x: 0, y: 0 });
|
|
243
|
+
};
|
|
95
244
|
|
|
96
245
|
const buttonContent = (
|
|
97
246
|
<>
|
|
@@ -109,45 +258,240 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
109
258
|
style={{ transform: 'skewX(-20deg)' }}
|
|
110
259
|
/>
|
|
111
260
|
)}
|
|
112
|
-
<span className="relative z-10 flex items-center gap-2">
|
|
261
|
+
<span className="relative z-10 flex items-center justify-center gap-2">
|
|
262
|
+
{isLoading && <Loader2 className="animate-spin h-4 w-4 shrink-0" />}
|
|
263
|
+
{children}
|
|
264
|
+
</span>
|
|
113
265
|
</>
|
|
114
266
|
);
|
|
115
267
|
|
|
268
|
+
const activeVariant = isLoading ? "loading" : variant;
|
|
269
|
+
|
|
270
|
+
// Disable button if loading
|
|
271
|
+
const disabledState = props.disabled || isLoading;
|
|
272
|
+
|
|
273
|
+
// Destructure custom props to avoid passing invalid props down to HTML element
|
|
274
|
+
const { ...htmlProps } = props;
|
|
275
|
+
|
|
276
|
+
// Setup base styles
|
|
277
|
+
const resolvedClassName = cn(
|
|
278
|
+
buttonVariants({ variant: activeVariant, size, className }),
|
|
279
|
+
isShimmer && "relative overflow-hidden",
|
|
280
|
+
isGlow && "shadow-[0_0_15px_rgba(var(--primary-rgb),0.4)]"
|
|
281
|
+
);
|
|
282
|
+
|
|
116
283
|
if (!animate) {
|
|
117
284
|
return (
|
|
118
285
|
<button
|
|
119
|
-
className={cn(buttonVariants({ variant, size, className }), isShimmer && "relative overflow-hidden")}
|
|
120
286
|
ref={ref}
|
|
121
|
-
{
|
|
287
|
+
disabled={disabledState}
|
|
288
|
+
className={resolvedClassName}
|
|
289
|
+
{...(htmlProps as React.ButtonHTMLAttributes<HTMLButtonElement>)}
|
|
122
290
|
>
|
|
123
291
|
{buttonContent}
|
|
124
292
|
</button>
|
|
125
293
|
);
|
|
126
294
|
}
|
|
127
295
|
|
|
128
|
-
// Destructure HTML-only event handlers that shouldn't go to motion.button
|
|
129
|
-
const { onDrag, onDragStart, onDragEnd, onAnimationStart, ...motionSafeProps } = props;
|
|
130
|
-
|
|
131
296
|
return (
|
|
132
297
|
<motion.button
|
|
133
|
-
className={cn(buttonVariants({ variant, size, className }))}
|
|
134
298
|
ref={ref}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
299
|
+
disabled={disabledState}
|
|
300
|
+
className={resolvedClassName}
|
|
301
|
+
onMouseMove={handleMouseMove}
|
|
302
|
+
onMouseLeave={handleMouseLeave}
|
|
303
|
+
animate={isMagnetic ? { x: magneticPos.x, y: magneticPos.y } : undefined}
|
|
304
|
+
whileHover={{
|
|
305
|
+
scale: isMagnetic ? 1.02 : 1.03,
|
|
306
|
+
y: isMagnetic ? 0 : -1.5,
|
|
307
|
+
shadow: isGlow ? "0 0 25px rgba(var(--primary-rgb), 0.7)" : undefined,
|
|
138
308
|
}}
|
|
139
309
|
whileTap={{ scale: 0.97 }}
|
|
140
|
-
transition={{
|
|
141
|
-
|
|
310
|
+
transition={{
|
|
311
|
+
type: "spring",
|
|
312
|
+
stiffness: 350,
|
|
313
|
+
damping: 20,
|
|
314
|
+
}}
|
|
315
|
+
{...(htmlProps as any)}
|
|
142
316
|
>
|
|
143
317
|
{buttonContent}
|
|
144
318
|
</motion.button>
|
|
145
319
|
);
|
|
146
320
|
}
|
|
147
321
|
);
|
|
322
|
+
|
|
148
323
|
Button.displayName = "Button";
|
|
149
324
|
|
|
150
325
|
export { Button, buttonVariants };
|
|
326
|
+
|
|
327
|
+
// ----------------------------------------------------
|
|
328
|
+
// Merged button components for backward compatibility
|
|
329
|
+
// ----------------------------------------------------
|
|
330
|
+
|
|
331
|
+
export const NeonButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
332
|
+
({ children, ...props }, ref) => (
|
|
333
|
+
<Button ref={ref} variant="neon" glow={true} {...props}>
|
|
334
|
+
{children}
|
|
335
|
+
</Button>
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
NeonButton.displayName = "NeonButton";
|
|
339
|
+
|
|
340
|
+
export const ThreeDButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
341
|
+
({ children, className, ...props }, ref) => (
|
|
342
|
+
<Button
|
|
343
|
+
ref={ref}
|
|
344
|
+
className={cn(
|
|
345
|
+
"shadow-[0_5px_0_hsl(var(--primary-dark,240_5.9%_30%))] hover:shadow-[0_2px_0_hsl(var(--primary-dark,240_5.9%_30%))] active:translate-y-[3px] active:shadow-[0_0px_0_transparent] transition-all",
|
|
346
|
+
className
|
|
347
|
+
)}
|
|
348
|
+
{...props}
|
|
349
|
+
>
|
|
350
|
+
{children}
|
|
351
|
+
</Button>
|
|
352
|
+
)
|
|
353
|
+
);
|
|
354
|
+
ThreeDButton.displayName = "ThreeDButton";
|
|
355
|
+
|
|
356
|
+
export const RippleButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
357
|
+
({ children, className, ...props }, ref) => (
|
|
358
|
+
<Button
|
|
359
|
+
ref={ref}
|
|
360
|
+
className={cn(
|
|
361
|
+
"relative overflow-hidden group active:scale-95 transition-transform",
|
|
362
|
+
className
|
|
363
|
+
)}
|
|
364
|
+
{...props}
|
|
365
|
+
>
|
|
366
|
+
<span className="absolute inset-0 bg-white/20 scale-0 rounded-full group-active:scale-[2] transition-transform duration-500 origin-center"></span>
|
|
367
|
+
<span className="relative z-10">{children}</span>
|
|
368
|
+
</Button>
|
|
369
|
+
)
|
|
370
|
+
);
|
|
371
|
+
RippleButton.displayName = "RippleButton";
|
|
372
|
+
|
|
373
|
+
export const CyberpunkButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
374
|
+
({ children, className, ...props }, ref) => (
|
|
375
|
+
<Button
|
|
376
|
+
ref={ref}
|
|
377
|
+
className={cn(
|
|
378
|
+
"bg-yellow-400 text-black font-extrabold uppercase tracking-widest border-2 border-black hover:bg-black hover:text-yellow-400 hover:border-yellow-400 transition-colors shadow-[4px_4px_0_0_#000] rounded-none hover:shadow-[4px_4px_0_0_#fff]",
|
|
379
|
+
className
|
|
380
|
+
)}
|
|
381
|
+
{...props}
|
|
382
|
+
>
|
|
383
|
+
{children}
|
|
384
|
+
</Button>
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
CyberpunkButton.displayName = "CyberpunkButton";
|
|
388
|
+
|
|
389
|
+
export const MagneticButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
390
|
+
({ children, ...props }, ref) => (
|
|
391
|
+
<Button ref={ref} variant="magnetic" {...props}>
|
|
392
|
+
{children}
|
|
393
|
+
</Button>
|
|
394
|
+
)
|
|
395
|
+
);
|
|
396
|
+
MagneticButton.displayName = "MagneticButton";
|
|
397
|
+
|
|
398
|
+
export const ShimmerButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
399
|
+
({ children, ...props }, ref) => (
|
|
400
|
+
<Button ref={ref} variant="shimmer" shimmer={true} {...props}>
|
|
401
|
+
{children}
|
|
402
|
+
</Button>
|
|
403
|
+
)
|
|
404
|
+
);
|
|
405
|
+
ShimmerButton.displayName = "ShimmerButton";
|
|
406
|
+
|
|
407
|
+
export const BorderBeamButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
408
|
+
({ children, className, ...props }, ref) => (
|
|
409
|
+
<Button
|
|
410
|
+
ref={ref}
|
|
411
|
+
variant="outline"
|
|
412
|
+
className={cn(
|
|
413
|
+
"relative overflow-hidden border border-border group",
|
|
414
|
+
className
|
|
415
|
+
)}
|
|
416
|
+
{...props}
|
|
417
|
+
>
|
|
418
|
+
<div className="absolute inset-0 bg-gradient-to-r from-primary to-transparent opacity-0 group-hover:opacity-20 transition-opacity"></div>
|
|
419
|
+
<div className="absolute top-0 left-0 w-full h-[2px] bg-primary scale-x-0 group-hover:scale-x-100 transition-transform origin-left"></div>
|
|
420
|
+
<span className="relative z-10">{children}</span>
|
|
421
|
+
</Button>
|
|
422
|
+
)
|
|
423
|
+
);
|
|
424
|
+
BorderBeamButton.displayName = "BorderBeamButton";
|
|
425
|
+
|
|
426
|
+
export const LoadingButton = React.forwardRef<HTMLButtonElement, ButtonProps & { isLoading?: boolean }>(
|
|
427
|
+
({ children, isLoading = true, ...props }, ref) => (
|
|
428
|
+
<Button ref={ref} isLoading={isLoading} {...props}>
|
|
429
|
+
{children}
|
|
430
|
+
</Button>
|
|
431
|
+
)
|
|
432
|
+
);
|
|
433
|
+
LoadingButton.displayName = "LoadingButton";
|
|
434
|
+
|
|
435
|
+
export const DestructiveGlowButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
436
|
+
({ children, ...props }, ref) => (
|
|
437
|
+
<Button ref={ref} variant="destructive" glow={true} {...props}>
|
|
438
|
+
{children}
|
|
439
|
+
</Button>
|
|
440
|
+
)
|
|
441
|
+
);
|
|
442
|
+
DestructiveGlowButton.displayName = "DestructiveGlowButton";
|
|
443
|
+
|
|
444
|
+
export const GhostOutlineButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
445
|
+
({ children, ...props }, ref) => (
|
|
446
|
+
<Button ref={ref} variant="outline" {...props}>
|
|
447
|
+
{children}
|
|
448
|
+
</Button>
|
|
449
|
+
)
|
|
450
|
+
);
|
|
451
|
+
GhostOutlineButton.displayName = "GhostOutlineButton";
|
|
452
|
+
|
|
453
|
+
export const GlowButton = React.forwardRef<HTMLButtonElement, ButtonProps & { glowColor?: string }>(
|
|
454
|
+
({ children, glowColor = "rgba(139, 92, 246, 0.15)", className, ...props }, ref) => (
|
|
455
|
+
<div className="relative group inline-block">
|
|
456
|
+
<div
|
|
457
|
+
className="absolute -inset-0.5 bg-gradient-to-r from-primary to-purple-600 rounded-lg blur opacity-75 group-hover:opacity-100 transition duration-1000 group-hover:duration-200"
|
|
458
|
+
style={{ backgroundColor: glowColor }}
|
|
459
|
+
/>
|
|
460
|
+
<Button ref={ref} className={cn("relative bg-background", className)} {...props}>
|
|
461
|
+
{children}
|
|
462
|
+
</Button>
|
|
463
|
+
</div>
|
|
464
|
+
)
|
|
465
|
+
);
|
|
466
|
+
GlowButton.displayName = "GlowButton";
|
|
467
|
+
|
|
468
|
+
export const ShinyButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
469
|
+
({ children, ...props }, ref) => (
|
|
470
|
+
<Button ref={ref} shimmer={true} {...props}>
|
|
471
|
+
{children}
|
|
472
|
+
</Button>
|
|
473
|
+
)
|
|
474
|
+
);
|
|
475
|
+
ShinyButton.displayName = "ShinyButton";
|
|
476
|
+
|
|
477
|
+
export const GradientButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
478
|
+
({ children, ...props }, ref) => (
|
|
479
|
+
<Button ref={ref} variant="gradient" {...props}>
|
|
480
|
+
{children}
|
|
481
|
+
</Button>
|
|
482
|
+
)
|
|
483
|
+
);
|
|
484
|
+
GradientButton.displayName = "GradientButton";
|
|
485
|
+
|
|
486
|
+
export const GlassButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
487
|
+
({ children, ...props }, ref) => (
|
|
488
|
+
<Button ref={ref} variant="glass" {...props}>
|
|
489
|
+
{children}
|
|
490
|
+
</Button>
|
|
491
|
+
)
|
|
492
|
+
);
|
|
493
|
+
GlassButton.displayName = "GlassButton";
|
|
494
|
+
|
|
151
495
|
`
|
|
152
496
|
};
|
|
153
497
|
|
|
@@ -162,13 +506,15 @@ var modal = {
|
|
|
162
506
|
"lucide-react",
|
|
163
507
|
"framer-motion"
|
|
164
508
|
],
|
|
165
|
-
componentsDependencies: [
|
|
509
|
+
componentsDependencies: [
|
|
510
|
+
"button"
|
|
511
|
+
],
|
|
166
512
|
fileName: "modal.tsx",
|
|
167
513
|
content: `"use client"
|
|
168
514
|
|
|
169
515
|
import * as React from "react"
|
|
170
516
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
|
171
|
-
import { X, AlertTriangle, CheckCircle } from "lucide-react"
|
|
517
|
+
import { X, AlertTriangle, CheckCircle, Star } from "lucide-react"
|
|
172
518
|
import { cn } from "../utils/cn"
|
|
173
519
|
import { Button } from "./button"
|
|
174
520
|
|
|
@@ -198,7 +544,7 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
|
198
544
|
const DialogContent = React.forwardRef<
|
|
199
545
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
200
546
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
201
|
-
>((
|
|
547
|
+
>(({ className, children, ...props }, ref) => (
|
|
202
548
|
<DialogPortal>
|
|
203
549
|
<DialogOverlay />
|
|
204
550
|
<DialogPrimitive.Content
|
|
@@ -416,6 +762,307 @@ export function Modal({
|
|
|
416
762
|
</Dialog>
|
|
417
763
|
)
|
|
418
764
|
}
|
|
765
|
+
|
|
766
|
+
// ============================================
|
|
767
|
+
// Backward compatibility wrappers & variants
|
|
768
|
+
// ============================================
|
|
769
|
+
|
|
770
|
+
export interface BasicModalProps {
|
|
771
|
+
isOpen?: boolean;
|
|
772
|
+
onOpenChange?: (open: boolean) => void;
|
|
773
|
+
trigger?: React.ReactNode;
|
|
774
|
+
title?: string;
|
|
775
|
+
description?: string;
|
|
776
|
+
children?: React.ReactNode;
|
|
777
|
+
confirmText?: string;
|
|
778
|
+
cancelText?: string;
|
|
779
|
+
onConfirm?: () => void;
|
|
780
|
+
onCancel?: () => void;
|
|
781
|
+
className?: string;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
export const BasicModal = ({
|
|
785
|
+
isOpen,
|
|
786
|
+
onOpenChange,
|
|
787
|
+
trigger,
|
|
788
|
+
title = "Basic Modal",
|
|
789
|
+
description = "This is a simple modal dialog that can be used for various purposes.",
|
|
790
|
+
children,
|
|
791
|
+
confirmText = "Confirm",
|
|
792
|
+
cancelText = "Cancel",
|
|
793
|
+
onConfirm,
|
|
794
|
+
onCancel,
|
|
795
|
+
className = ""
|
|
796
|
+
}: BasicModalProps) => {
|
|
797
|
+
return (
|
|
798
|
+
<Modal
|
|
799
|
+
isOpen={isOpen}
|
|
800
|
+
onOpenChange={onOpenChange}
|
|
801
|
+
trigger={trigger}
|
|
802
|
+
title={title}
|
|
803
|
+
description={description}
|
|
804
|
+
confirmText={confirmText}
|
|
805
|
+
cancelText={cancelText}
|
|
806
|
+
onConfirm={onConfirm}
|
|
807
|
+
onCancel={onCancel}
|
|
808
|
+
className={className}
|
|
809
|
+
variant="default"
|
|
810
|
+
>
|
|
811
|
+
{children}
|
|
812
|
+
</Modal>
|
|
813
|
+
)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
export interface InteractiveGlassModalProps {
|
|
817
|
+
isOpen?: boolean;
|
|
818
|
+
onOpenChange?: (open: boolean) => void;
|
|
819
|
+
trigger?: React.ReactNode;
|
|
820
|
+
icon?: React.ReactNode;
|
|
821
|
+
title?: string;
|
|
822
|
+
description?: string;
|
|
823
|
+
children?: React.ReactNode;
|
|
824
|
+
confirmText?: string;
|
|
825
|
+
cancelText?: string;
|
|
826
|
+
onConfirm?: () => void;
|
|
827
|
+
onCancel?: () => void;
|
|
828
|
+
className?: string;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
export const InteractiveGlassModal = ({
|
|
832
|
+
isOpen,
|
|
833
|
+
onOpenChange,
|
|
834
|
+
trigger,
|
|
835
|
+
icon = <Star className="w-6 h-6 text-yellow-300" />,
|
|
836
|
+
title = "Premium Glass Effect",
|
|
837
|
+
description = "This modal uses full glassmorphism for a stunning visual effect.",
|
|
838
|
+
children,
|
|
839
|
+
confirmText = "Upgrade Now",
|
|
840
|
+
cancelText = "Maybe Later",
|
|
841
|
+
onConfirm,
|
|
842
|
+
onCancel,
|
|
843
|
+
className = ""
|
|
844
|
+
}: InteractiveGlassModalProps) => {
|
|
845
|
+
return (
|
|
846
|
+
<Modal
|
|
847
|
+
isOpen={isOpen}
|
|
848
|
+
onOpenChange={onOpenChange}
|
|
849
|
+
trigger={trigger}
|
|
850
|
+
title={<span className="flex items-center gap-2">{icon} {title}</span>}
|
|
851
|
+
description={description}
|
|
852
|
+
confirmText={confirmText}
|
|
853
|
+
cancelText={cancelText}
|
|
854
|
+
onConfirm={onConfirm}
|
|
855
|
+
onCancel={onCancel}
|
|
856
|
+
className={className}
|
|
857
|
+
variant="glass"
|
|
858
|
+
>
|
|
859
|
+
{children}
|
|
860
|
+
</Modal>
|
|
861
|
+
)
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
export interface DangerModalProps {
|
|
865
|
+
isOpen?: boolean;
|
|
866
|
+
onOpenChange?: (open: boolean) => void;
|
|
867
|
+
trigger?: React.ReactNode;
|
|
868
|
+
icon?: React.ReactNode;
|
|
869
|
+
title?: string;
|
|
870
|
+
description?: string;
|
|
871
|
+
children?: React.ReactNode;
|
|
872
|
+
confirmText?: string;
|
|
873
|
+
cancelText?: string;
|
|
874
|
+
onConfirm?: () => void;
|
|
875
|
+
onCancel?: () => void;
|
|
876
|
+
className?: string;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
export const DangerModal = ({
|
|
880
|
+
isOpen,
|
|
881
|
+
onOpenChange,
|
|
882
|
+
trigger,
|
|
883
|
+
icon = <X className="w-8 h-8" />,
|
|
884
|
+
title = "Are you absolutely sure?",
|
|
885
|
+
description = "This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
|
886
|
+
children,
|
|
887
|
+
confirmText = "Delete",
|
|
888
|
+
cancelText = "Cancel",
|
|
889
|
+
onConfirm,
|
|
890
|
+
onCancel,
|
|
891
|
+
className = ""
|
|
892
|
+
}: DangerModalProps) => {
|
|
893
|
+
return (
|
|
894
|
+
<Modal
|
|
895
|
+
isOpen={isOpen}
|
|
896
|
+
onOpenChange={onOpenChange}
|
|
897
|
+
trigger={trigger}
|
|
898
|
+
title={title}
|
|
899
|
+
description={description}
|
|
900
|
+
confirmText={confirmText}
|
|
901
|
+
cancelText={cancelText}
|
|
902
|
+
onConfirm={onConfirm}
|
|
903
|
+
onCancel={onCancel}
|
|
904
|
+
className={className}
|
|
905
|
+
variant="destructive"
|
|
906
|
+
>
|
|
907
|
+
{children}
|
|
908
|
+
</Modal>
|
|
909
|
+
)
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
export interface GlassModalProps {
|
|
913
|
+
trigger?: React.ReactNode
|
|
914
|
+
title?: React.ReactNode
|
|
915
|
+
description?: React.ReactNode
|
|
916
|
+
children?: React.ReactNode
|
|
917
|
+
open?: boolean
|
|
918
|
+
onOpenChange?: (open: boolean) => void
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
export function GlassModal({ trigger, title = "Glass Modal", description, children, open, onOpenChange }: GlassModalProps) {
|
|
922
|
+
return (
|
|
923
|
+
<Modal
|
|
924
|
+
isOpen={open}
|
|
925
|
+
onOpenChange={onOpenChange}
|
|
926
|
+
trigger={trigger}
|
|
927
|
+
title={title}
|
|
928
|
+
description={description}
|
|
929
|
+
variant="glass"
|
|
930
|
+
>
|
|
931
|
+
{children}
|
|
932
|
+
</Modal>
|
|
933
|
+
)
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export interface AlertModalProps {
|
|
937
|
+
trigger?: React.ReactNode
|
|
938
|
+
title?: string
|
|
939
|
+
description?: string
|
|
940
|
+
onConfirm?: () => void
|
|
941
|
+
onCancel?: () => void
|
|
942
|
+
confirmText?: string
|
|
943
|
+
cancelText?: string
|
|
944
|
+
children?: React.ReactNode
|
|
945
|
+
open?: boolean
|
|
946
|
+
onOpenChange?: (open: boolean) => void
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
export function AlertModal({
|
|
950
|
+
trigger,
|
|
951
|
+
title = "Are you absolutely sure?",
|
|
952
|
+
description,
|
|
953
|
+
onConfirm,
|
|
954
|
+
onCancel,
|
|
955
|
+
confirmText = "Confirm",
|
|
956
|
+
cancelText = "Cancel",
|
|
957
|
+
children,
|
|
958
|
+
open,
|
|
959
|
+
onOpenChange
|
|
960
|
+
}: AlertModalProps) {
|
|
961
|
+
return (
|
|
962
|
+
<Modal
|
|
963
|
+
isOpen={open}
|
|
964
|
+
onOpenChange={onOpenChange}
|
|
965
|
+
trigger={trigger}
|
|
966
|
+
title={title}
|
|
967
|
+
description={description}
|
|
968
|
+
confirmText={confirmText}
|
|
969
|
+
cancelText={cancelText}
|
|
970
|
+
onConfirm={onConfirm}
|
|
971
|
+
onCancel={onCancel}
|
|
972
|
+
variant="destructive"
|
|
973
|
+
>
|
|
974
|
+
{children}
|
|
975
|
+
</Modal>
|
|
976
|
+
)
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
export interface SuccessModalProps {
|
|
980
|
+
trigger?: React.ReactNode
|
|
981
|
+
title?: string
|
|
982
|
+
description?: string
|
|
983
|
+
children?: React.ReactNode
|
|
984
|
+
open?: boolean
|
|
985
|
+
onOpenChange?: (open: boolean) => void
|
|
986
|
+
confirmText?: string
|
|
987
|
+
onConfirm?: () => void
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
export function SuccessModal({
|
|
991
|
+
trigger,
|
|
992
|
+
title = "Success!",
|
|
993
|
+
description,
|
|
994
|
+
children,
|
|
995
|
+
open,
|
|
996
|
+
onOpenChange,
|
|
997
|
+
confirmText = "Awesome",
|
|
998
|
+
onConfirm
|
|
999
|
+
}: SuccessModalProps) {
|
|
1000
|
+
return (
|
|
1001
|
+
<Modal
|
|
1002
|
+
isOpen={open}
|
|
1003
|
+
onOpenChange={onOpenChange}
|
|
1004
|
+
trigger={trigger}
|
|
1005
|
+
title={title}
|
|
1006
|
+
description={description}
|
|
1007
|
+
confirmText={confirmText}
|
|
1008
|
+
onConfirm={onConfirm}
|
|
1009
|
+
variant="success"
|
|
1010
|
+
>
|
|
1011
|
+
{children}
|
|
1012
|
+
</Modal>
|
|
1013
|
+
)
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
export interface CommandPaletteModalProps {
|
|
1017
|
+
trigger: React.ReactNode
|
|
1018
|
+
open?: boolean
|
|
1019
|
+
onOpenChange?: (open: boolean) => void
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
export function CommandPaletteModal({ trigger, open, onOpenChange }: CommandPaletteModalProps) {
|
|
1023
|
+
return (
|
|
1024
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
1025
|
+
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
|
1026
|
+
<DialogContent className="p-0 overflow-hidden sm:max-w-[600px] gap-0">
|
|
1027
|
+
<div className="flex items-center border-b px-3">
|
|
1028
|
+
<svg className="mr-2 h-4 w-4 shrink-0 opacity-50" 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"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
|
1029
|
+
<input className="flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50" placeholder="Type a command or search..." />
|
|
1030
|
+
</div>
|
|
1031
|
+
<div className="max-h-[300px] overflow-y-auto p-2">
|
|
1032
|
+
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">Suggestions</div>
|
|
1033
|
+
<div className="flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground hover:bg-accent/50">
|
|
1034
|
+
Calendar
|
|
1035
|
+
</div>
|
|
1036
|
+
<div className="flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground hover:bg-accent/50">
|
|
1037
|
+
Search Emoji
|
|
1038
|
+
</div>
|
|
1039
|
+
<div className="flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground hover:bg-accent/50">
|
|
1040
|
+
Calculator
|
|
1041
|
+
</div>
|
|
1042
|
+
</div>
|
|
1043
|
+
</DialogContent>
|
|
1044
|
+
</Dialog>
|
|
1045
|
+
)
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
export interface BottomSheetSimulatedProps {
|
|
1049
|
+
trigger: React.ReactNode
|
|
1050
|
+
children?: React.ReactNode
|
|
1051
|
+
open?: boolean
|
|
1052
|
+
onOpenChange?: (open: boolean) => void
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
export function BottomSheetSimulated({ trigger, children, open, onOpenChange }: BottomSheetSimulatedProps) {
|
|
1056
|
+
return (
|
|
1057
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
1058
|
+
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
|
1059
|
+
<DialogContent className="sm:max-w-full sm:h-[50vh] sm:rounded-b-none sm:rounded-t-[10px] fixed bottom-0 top-auto translate-y-0 data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom">
|
|
1060
|
+
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
|
1061
|
+
{children}
|
|
1062
|
+
</DialogContent>
|
|
1063
|
+
</Dialog>
|
|
1064
|
+
)
|
|
1065
|
+
}
|
|
419
1066
|
`
|
|
420
1067
|
};
|
|
421
1068
|
|
|
@@ -426,18 +1073,20 @@ var card = {
|
|
|
426
1073
|
"class-variance-authority",
|
|
427
1074
|
"clsx",
|
|
428
1075
|
"tailwind-merge",
|
|
429
|
-
"framer-motion"
|
|
1076
|
+
"framer-motion",
|
|
1077
|
+
"lucide-react"
|
|
430
1078
|
],
|
|
431
1079
|
fileName: "card.tsx",
|
|
432
1080
|
content: `'use client';
|
|
433
1081
|
|
|
434
1082
|
import * as React from "react"
|
|
435
1083
|
import { cn } from "../utils/cn"
|
|
436
|
-
import { motion, HTMLMotionProps } from "framer-motion"
|
|
1084
|
+
import { motion, useMotionValue, useSpring, useTransform, HTMLMotionProps } from "framer-motion"
|
|
437
1085
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
1086
|
+
import { Heart, Share2, MapPin, Star } from "lucide-react"
|
|
438
1087
|
|
|
439
1088
|
const cardVariants = cva(
|
|
440
|
-
"rounded-2xl text-card-foreground transition-all duration-300",
|
|
1089
|
+
"rounded-2xl text-card-foreground transition-all duration-300 overflow-hidden",
|
|
441
1090
|
{
|
|
442
1091
|
variants: {
|
|
443
1092
|
variant: {
|
|
@@ -445,11 +1094,16 @@ const cardVariants = cva(
|
|
|
445
1094
|
glass: "backdrop-blur-md bg-white/10 dark:bg-black/20 border border-white/20 dark:border-white/10 shadow-lg",
|
|
446
1095
|
gradient: "bg-gradient-to-br from-violet-500/10 via-pink-500/10 to-orange-500/10 border border-purple-500/20 shadow-lg shadow-purple-500/5",
|
|
447
1096
|
glow: "bg-card border-2 border-primary/20 shadow-[0_0_15px_rgba(var(--primary-rgb),0.1)] hover:shadow-[0_0_25px_rgba(var(--primary-rgb),0.2)]",
|
|
1097
|
+
// New variants
|
|
1098
|
+
bento: "border border-border/60 bg-gradient-to-br from-card to-muted/20 text-card-foreground shadow-md hover:shadow-lg hover:border-primary/30 relative",
|
|
1099
|
+
spotlight: "border bg-card text-card-foreground relative hover:border-primary/20",
|
|
1100
|
+
flip: "bg-transparent border-0 shadow-none overflow-visible relative",
|
|
1101
|
+
tilt: "border bg-card text-card-foreground shadow-md",
|
|
448
1102
|
},
|
|
449
1103
|
hover: {
|
|
450
1104
|
none: "",
|
|
451
|
-
lift: "hover:-translate-y-1 hover:shadow-lg",
|
|
452
|
-
glow: "hover:border-primary/50 hover:shadow-[
|
|
1105
|
+
lift: "hover:-translate-y-1.5 hover:shadow-lg",
|
|
1106
|
+
glow: "hover:border-primary/50 hover:shadow-[0_0_25px_rgba(var(--primary-rgb),0.25)]",
|
|
453
1107
|
}
|
|
454
1108
|
},
|
|
455
1109
|
defaultVariants: {
|
|
@@ -459,43 +1113,112 @@ const cardVariants = cva(
|
|
|
459
1113
|
}
|
|
460
1114
|
)
|
|
461
1115
|
|
|
1116
|
+
/**
|
|
1117
|
+
* Props for the Card component
|
|
1118
|
+
*/
|
|
462
1119
|
export interface CardProps
|
|
463
|
-
extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
1120
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>,
|
|
464
1121
|
VariantProps<typeof cardVariants> {
|
|
465
1122
|
/**
|
|
466
|
-
* Whether to enable hover animations
|
|
1123
|
+
* Whether to enable hover spring animations
|
|
467
1124
|
* @default true
|
|
468
1125
|
*/
|
|
469
1126
|
animate?: boolean;
|
|
470
1127
|
/**
|
|
471
|
-
* The title of the card
|
|
1128
|
+
* The main title of the card
|
|
472
1129
|
*/
|
|
473
1130
|
title?: React.ReactNode;
|
|
474
1131
|
/**
|
|
475
|
-
* The description of the card
|
|
1132
|
+
* The subtitle or description of the card
|
|
476
1133
|
*/
|
|
477
1134
|
description?: React.ReactNode;
|
|
478
1135
|
/**
|
|
479
|
-
*
|
|
1136
|
+
* Content to render in the card footer area
|
|
480
1137
|
*/
|
|
481
1138
|
footer?: React.ReactNode;
|
|
482
1139
|
/**
|
|
483
|
-
* An image URL to display at the top of the card
|
|
1140
|
+
* An optional image URL to display at the top of the card
|
|
484
1141
|
*/
|
|
485
1142
|
image?: string;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
1143
|
+
/**
|
|
1144
|
+
* HTML alternative text for the image
|
|
1145
|
+
*/
|
|
1146
|
+
imageAlt?: string;
|
|
1147
|
+
/**
|
|
1148
|
+
* Back content displayed when using the \`flip\` variant on hover
|
|
1149
|
+
*/
|
|
1150
|
+
backContent?: React.ReactNode;
|
|
1151
|
+
/**
|
|
1152
|
+
* Custom radial spotlight background color (e.g., rgba(168, 85, 247, 0.15))
|
|
1153
|
+
* @default "rgba(139, 92, 246, 0.15)"
|
|
1154
|
+
*/
|
|
1155
|
+
spotlightColor?: string;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
1159
|
+
(
|
|
1160
|
+
{
|
|
1161
|
+
className,
|
|
1162
|
+
variant,
|
|
1163
|
+
hover,
|
|
1164
|
+
animate = true,
|
|
1165
|
+
title,
|
|
1166
|
+
description,
|
|
1167
|
+
footer,
|
|
1168
|
+
image,
|
|
1169
|
+
imageAlt,
|
|
1170
|
+
backContent,
|
|
1171
|
+
spotlightColor = "rgba(139, 92, 246, 0.15)",
|
|
1172
|
+
children,
|
|
1173
|
+
...props
|
|
1174
|
+
},
|
|
1175
|
+
ref
|
|
1176
|
+
) => {
|
|
490
1177
|
const isCompound = !title && !description && !footer && !image;
|
|
491
1178
|
|
|
492
|
-
|
|
1179
|
+
// Feature toggles based on variants
|
|
1180
|
+
const isSpotlight = variant === "spotlight";
|
|
1181
|
+
const isFlip = variant === "flip";
|
|
1182
|
+
const isTilt = variant === "tilt";
|
|
1183
|
+
|
|
1184
|
+
// Spotlight mouse tracking state
|
|
1185
|
+
const [mousePos, setMousePos] = React.useState({ x: 0, y: 0 });
|
|
1186
|
+
const handleMouseMoveSpotlight = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
1187
|
+
if (!isSpotlight) return;
|
|
1188
|
+
const { currentTarget, clientX, clientY } = e;
|
|
1189
|
+
const { left, top } = currentTarget.getBoundingClientRect();
|
|
1190
|
+
setMousePos({ x: clientX - left, y: clientY - top });
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
// Tilt mouse tracking state
|
|
1194
|
+
const [tiltPos, setTiltPos] = React.useState({ rotateX: 0, rotateY: 0 });
|
|
1195
|
+
const handleMouseMoveTilt = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
1196
|
+
if (!isTilt) return;
|
|
1197
|
+
const { currentTarget, clientX, clientY } = e;
|
|
1198
|
+
const { left, top, width, height } = currentTarget.getBoundingClientRect();
|
|
1199
|
+
const x = clientX - left;
|
|
1200
|
+
const y = clientY - top;
|
|
1201
|
+
const maxTilt = 12; // degrees max rotation
|
|
1202
|
+
const rotateX = ((y - height / 2) / (height / 2)) * -maxTilt;
|
|
1203
|
+
const rotateY = ((x - width / 2) / (width / 2)) * maxTilt;
|
|
1204
|
+
setTiltPos({ rotateX, rotateY });
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
const handleMouseLeaveTilt = () => {
|
|
1208
|
+
if (!isTilt) return;
|
|
1209
|
+
setTiltPos({ rotateX: 0, rotateY: 0 });
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
// Flip card hover state
|
|
1213
|
+
const [isFlipped, setIsFlipped] = React.useState(false);
|
|
1214
|
+
|
|
1215
|
+
const baseContent = isCompound ? (
|
|
493
1216
|
children
|
|
494
1217
|
) : (
|
|
495
1218
|
<>
|
|
496
1219
|
{image && (
|
|
497
1220
|
<div className="relative w-full h-48 overflow-hidden rounded-t-2xl">
|
|
498
|
-
<img src={image} alt={typeof title === 'string' ? title : 'Card image'} className="object-cover w-full h-full" />
|
|
1221
|
+
<img src={image} alt={imageAlt || (typeof title === 'string' ? title : 'Card image')} className="object-cover w-full h-full transition-transform duration-300 hover:scale-105" />
|
|
499
1222
|
</div>
|
|
500
1223
|
)}
|
|
501
1224
|
{(title || description) && (
|
|
@@ -509,16 +1232,78 @@ const Card = React.forwardRef<HTMLDivElement, CardProps & HTMLMotionProps<"div">
|
|
|
509
1232
|
</>
|
|
510
1233
|
);
|
|
511
1234
|
|
|
512
|
-
|
|
1235
|
+
// Destructure custom props to avoid DOM validation warnings
|
|
1236
|
+
const { ...htmlProps } = props;
|
|
1237
|
+
|
|
1238
|
+
// Flip Variant Render
|
|
1239
|
+
if (isFlip) {
|
|
1240
|
+
return (
|
|
1241
|
+
<div
|
|
1242
|
+
ref={ref}
|
|
1243
|
+
className={cn(cardVariants({ variant, hover, className }), "perspective-1000 w-full h-full")}
|
|
1244
|
+
onMouseEnter={() => setIsFlipped(true)}
|
|
1245
|
+
onMouseLeave={() => setIsFlipped(false)}
|
|
1246
|
+
{...(htmlProps as React.HTMLAttributes<HTMLDivElement>)}
|
|
1247
|
+
>
|
|
1248
|
+
<motion.div
|
|
1249
|
+
className="relative w-full h-full transition-all duration-500 preserve-3d"
|
|
1250
|
+
animate={{ rotateY: isFlipped ? 180 : 0 }}
|
|
1251
|
+
transition={{ type: "spring", stiffness: 300, damping: 22 }}
|
|
1252
|
+
>
|
|
1253
|
+
{/* Front Face */}
|
|
1254
|
+
<div className="absolute inset-0 backface-hidden border bg-card text-card-foreground rounded-2xl shadow-sm flex flex-col justify-between overflow-hidden">
|
|
1255
|
+
{baseContent}
|
|
1256
|
+
</div>
|
|
1257
|
+
|
|
1258
|
+
{/* Back Face */}
|
|
1259
|
+
<div className="absolute inset-0 backface-hidden rotate-y-180 border bg-gradient-to-br from-primary/10 to-primary/5 text-card-foreground rounded-2xl shadow-sm flex flex-col p-6 items-center justify-center text-center overflow-hidden">
|
|
1260
|
+
{backContent || (
|
|
1261
|
+
<div className="text-sm font-medium text-muted-foreground">
|
|
1262
|
+
Flip side content placeholder
|
|
1263
|
+
</div>
|
|
1264
|
+
)}
|
|
1265
|
+
</div>
|
|
1266
|
+
</motion.div>
|
|
1267
|
+
</div>
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Spotlight Variant Render Extra Element
|
|
1272
|
+
const spotlightEffect = isSpotlight && (
|
|
1273
|
+
<div
|
|
1274
|
+
className="pointer-events-none absolute -inset-px rounded-2xl opacity-0 hover:opacity-100 group-hover:opacity-100 transition-opacity duration-300"
|
|
1275
|
+
style={{
|
|
1276
|
+
background: \`radial-gradient(400px circle at \${mousePos.x}px \${mousePos.y}px, \${spotlightColor}, transparent 80%)\`,
|
|
1277
|
+
}}
|
|
1278
|
+
/>
|
|
1279
|
+
);
|
|
1280
|
+
|
|
1281
|
+
// Build the resolved element attributes
|
|
1282
|
+
const cardClass = cn(cardVariants({ variant, hover: isFlip || isTilt ? "none" : hover, className }), isSpotlight && "group");
|
|
1283
|
+
|
|
1284
|
+
if (animate || isTilt) {
|
|
513
1285
|
return (
|
|
514
1286
|
<motion.div
|
|
515
1287
|
ref={ref}
|
|
516
|
-
className={
|
|
517
|
-
|
|
1288
|
+
className={cardClass}
|
|
1289
|
+
onMouseMove={(e) => {
|
|
1290
|
+
if (isSpotlight) handleMouseMoveSpotlight(e);
|
|
1291
|
+
if (isTilt) handleMouseMoveTilt(e);
|
|
1292
|
+
}}
|
|
1293
|
+
onMouseLeave={() => {
|
|
1294
|
+
if (isTilt) handleMouseLeaveTilt();
|
|
1295
|
+
}}
|
|
1296
|
+
animate={
|
|
1297
|
+
isTilt
|
|
1298
|
+
? { rotateX: tiltPos.rotateX, rotateY: tiltPos.rotateY }
|
|
1299
|
+
: undefined
|
|
1300
|
+
}
|
|
1301
|
+
whileHover={isTilt ? undefined : { scale: 1.015 }}
|
|
518
1302
|
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
|
519
|
-
{...
|
|
1303
|
+
{...(htmlProps as any)}
|
|
520
1304
|
>
|
|
521
|
-
{
|
|
1305
|
+
{spotlightEffect}
|
|
1306
|
+
{baseContent}
|
|
522
1307
|
</motion.div>
|
|
523
1308
|
);
|
|
524
1309
|
}
|
|
@@ -526,10 +1311,10 @@ const Card = React.forwardRef<HTMLDivElement, CardProps & HTMLMotionProps<"div">
|
|
|
526
1311
|
return (
|
|
527
1312
|
<div
|
|
528
1313
|
ref={ref}
|
|
529
|
-
className={
|
|
530
|
-
{...(
|
|
1314
|
+
className={cardClass}
|
|
1315
|
+
{...(htmlProps as React.HTMLAttributes<HTMLDivElement>)}
|
|
531
1316
|
>
|
|
532
|
-
{
|
|
1317
|
+
{baseContent}
|
|
533
1318
|
</div>
|
|
534
1319
|
);
|
|
535
1320
|
}
|
|
@@ -554,7 +1339,7 @@ const CardTitle = React.forwardRef<
|
|
|
554
1339
|
>(({ className, ...props }, ref) => (
|
|
555
1340
|
<h3
|
|
556
1341
|
ref={ref}
|
|
557
|
-
className="font-semibold leading-none tracking-tight text-xl bg-gradient-to-br from-foreground to-foreground/
|
|
1342
|
+
className={cn("font-semibold leading-none tracking-tight text-xl bg-gradient-to-br from-foreground to-foreground/75 bg-clip-text text-transparent", className)}
|
|
558
1343
|
{...props}
|
|
559
1344
|
/>
|
|
560
1345
|
))
|
|
@@ -566,7 +1351,7 @@ const CardDescription = React.forwardRef<
|
|
|
566
1351
|
>(({ className, ...props }, ref) => (
|
|
567
1352
|
<p
|
|
568
1353
|
ref={ref}
|
|
569
|
-
className={cn("text-sm text-muted-foreground", className)}
|
|
1354
|
+
className={cn("text-sm text-muted-foreground leading-relaxed mt-1", className)}
|
|
570
1355
|
{...props}
|
|
571
1356
|
/>
|
|
572
1357
|
))
|
|
@@ -576,7 +1361,7 @@ const CardContent = React.forwardRef<
|
|
|
576
1361
|
HTMLDivElement,
|
|
577
1362
|
React.HTMLAttributes<HTMLDivElement>
|
|
578
1363
|
>(({ className, ...props }, ref) => (
|
|
579
|
-
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
1364
|
+
<div ref={ref} className={cn("p-6 pt-0 leading-relaxed text-sm text-foreground/90", className)} {...props} />
|
|
580
1365
|
))
|
|
581
1366
|
CardContent.displayName = "CardContent"
|
|
582
1367
|
|
|
@@ -586,13 +1371,199 @@ const CardFooter = React.forwardRef<
|
|
|
586
1371
|
>(({ className, ...props }, ref) => (
|
|
587
1372
|
<div
|
|
588
1373
|
ref={ref}
|
|
589
|
-
className={cn("flex items-center p-6 pt-0", className)}
|
|
1374
|
+
className={cn("flex items-center p-6 pt-0 border-t border-border/10 mt-auto", className)}
|
|
590
1375
|
{...props}
|
|
591
1376
|
/>
|
|
592
1377
|
))
|
|
593
1378
|
CardFooter.displayName = "CardFooter"
|
|
594
1379
|
|
|
595
1380
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
|
1381
|
+
|
|
1382
|
+
export const GlassCard = React.forwardRef<HTMLDivElement, CardProps>(
|
|
1383
|
+
({ children, ...props }, ref) => (
|
|
1384
|
+
<Card ref={ref} variant="glass" {...props}>
|
|
1385
|
+
{children}
|
|
1386
|
+
</Card>
|
|
1387
|
+
)
|
|
1388
|
+
);
|
|
1389
|
+
GlassCard.displayName = "GlassCard";
|
|
1390
|
+
|
|
1391
|
+
export const GlowCard = React.forwardRef<HTMLDivElement, CardProps>(
|
|
1392
|
+
({ children, ...props }, ref) => (
|
|
1393
|
+
<Card ref={ref} variant="glow" {...props}>
|
|
1394
|
+
{children}
|
|
1395
|
+
</Card>
|
|
1396
|
+
)
|
|
1397
|
+
);
|
|
1398
|
+
GlowCard.displayName = "GlowCard";
|
|
1399
|
+
|
|
1400
|
+
export const GradientCard = React.forwardRef<HTMLDivElement, CardProps>(
|
|
1401
|
+
({ children, ...props }, ref) => (
|
|
1402
|
+
<Card ref={ref} variant="gradient" {...props}>
|
|
1403
|
+
{children}
|
|
1404
|
+
</Card>
|
|
1405
|
+
)
|
|
1406
|
+
);
|
|
1407
|
+
GradientCard.displayName = "GradientCard";
|
|
1408
|
+
|
|
1409
|
+
export const HoverCard = React.forwardRef<HTMLDivElement, CardProps>(
|
|
1410
|
+
({ children, ...props }, ref) => (
|
|
1411
|
+
<Card ref={ref} hover="lift" {...props}>
|
|
1412
|
+
{children}
|
|
1413
|
+
</Card>
|
|
1414
|
+
)
|
|
1415
|
+
);
|
|
1416
|
+
HoverCard.displayName = "HoverCard";
|
|
1417
|
+
|
|
1418
|
+
export const SpotlightCard = React.forwardRef<HTMLDivElement, CardProps>(
|
|
1419
|
+
({ children, ...props }, ref) => (
|
|
1420
|
+
<Card ref={ref} variant="spotlight" {...props}>
|
|
1421
|
+
{children}
|
|
1422
|
+
</Card>
|
|
1423
|
+
)
|
|
1424
|
+
);
|
|
1425
|
+
SpotlightCard.displayName = "SpotlightCard";
|
|
1426
|
+
|
|
1427
|
+
// ============================================
|
|
1428
|
+
// Consolidated Legacy/Special Cards for Compatibility
|
|
1429
|
+
// ============================================
|
|
1430
|
+
|
|
1431
|
+
export const ImageCard = ({ src, title, subtitle, imageUrl, description }: any) => {
|
|
1432
|
+
const finalSrc = src || imageUrl;
|
|
1433
|
+
const finalTitle = title;
|
|
1434
|
+
const finalSubtitle = subtitle || description;
|
|
1435
|
+
return (
|
|
1436
|
+
<div className="group relative overflow-hidden rounded-xl border bg-card text-card-foreground">
|
|
1437
|
+
<div className="aspect-[4/3] w-full bg-muted overflow-hidden">
|
|
1438
|
+
{finalSrc ? (
|
|
1439
|
+
<img
|
|
1440
|
+
src={finalSrc}
|
|
1441
|
+
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
|
1442
|
+
alt={typeof finalTitle === "string" ? finalTitle : "Image"}
|
|
1443
|
+
/>
|
|
1444
|
+
) : (
|
|
1445
|
+
<div className="w-full h-full bg-muted-foreground/20" />
|
|
1446
|
+
)}
|
|
1447
|
+
</div>
|
|
1448
|
+
<div className="p-4">
|
|
1449
|
+
<h3 className="font-semibold text-lg">{finalTitle}</h3>
|
|
1450
|
+
<p className="text-sm text-muted-foreground">{finalSubtitle}</p>
|
|
1451
|
+
</div>
|
|
1452
|
+
</div>
|
|
1453
|
+
);
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
export const ProfileCard = ({ name, role, avatar }: any) => (
|
|
1457
|
+
<div className="flex flex-col items-center p-6 text-center rounded-xl border bg-card shadow-sm">
|
|
1458
|
+
<div className="w-24 h-24 rounded-full bg-muted mb-4 overflow-hidden border-4 border-background shadow-md">
|
|
1459
|
+
{avatar && <img src={avatar} className="w-full h-full object-cover" alt={name} />}
|
|
1460
|
+
</div>
|
|
1461
|
+
<h3 className="text-xl font-bold">{name}</h3>
|
|
1462
|
+
<p className="text-sm text-muted-foreground mb-4">{role}</p>
|
|
1463
|
+
<button className="px-6 py-2 bg-primary text-primary-foreground rounded-full font-medium w-full hover:bg-primary/90 transition-colors">Follow</button>
|
|
1464
|
+
</div>
|
|
1465
|
+
)
|
|
1466
|
+
|
|
1467
|
+
export const ProductCard = ({ title, price, category, src }: any) => (
|
|
1468
|
+
<div className="rounded-xl border bg-card p-4 flex flex-col gap-3 group">
|
|
1469
|
+
<div className="aspect-square w-full rounded-lg bg-muted overflow-hidden relative">
|
|
1470
|
+
{src && <img src={src} className="w-full h-full object-cover" alt={title} />}
|
|
1471
|
+
<button className="absolute top-2 right-2 p-2 bg-background/50 backdrop-blur rounded-full hover:bg-background transition-colors"><Heart className="w-4 h-4" /></button>
|
|
1472
|
+
</div>
|
|
1473
|
+
<div>
|
|
1474
|
+
<p className="text-xs text-muted-foreground mb-1">{category}</p>
|
|
1475
|
+
<h3 className="font-medium truncate">{title}</h3>
|
|
1476
|
+
<p className="font-bold text-lg mt-1">\${price}</p>
|
|
1477
|
+
</div>
|
|
1478
|
+
</div>
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
export const ArticleCard = ({ title, excerpt, date }: any) => (
|
|
1482
|
+
<div className="p-6 rounded-xl border bg-card flex flex-col gap-4 hover:shadow-md transition-shadow cursor-pointer">
|
|
1483
|
+
<span className="text-xs font-medium text-primary">{date}</span>
|
|
1484
|
+
<h3 className="text-xl font-bold leading-tight">{title}</h3>
|
|
1485
|
+
<p className="text-muted-foreground line-clamp-3">{excerpt}</p>
|
|
1486
|
+
<div className="mt-auto pt-4 border-t flex items-center justify-between text-sm">
|
|
1487
|
+
<span className="font-medium">Read more \u2192</span>
|
|
1488
|
+
<button><Share2 className="w-4 h-4 text-muted-foreground hover:text-foreground" /></button>
|
|
1489
|
+
</div>
|
|
1490
|
+
</div>
|
|
1491
|
+
)
|
|
1492
|
+
|
|
1493
|
+
export const StatCardSimple = ({ label, value, trend }: any) => (
|
|
1494
|
+
<div className="p-5 rounded-xl border bg-card">
|
|
1495
|
+
<p className="text-sm font-medium text-muted-foreground mb-2">{label}</p>
|
|
1496
|
+
<div className="flex items-end justify-between">
|
|
1497
|
+
<h4 className="text-3xl font-bold">{value}</h4>
|
|
1498
|
+
<span className={\`text-sm font-medium \${trend?.startsWith('+') ? 'text-green-500' : 'text-red-500'}\`}>{trend}</span>
|
|
1499
|
+
</div>
|
|
1500
|
+
</div>
|
|
1501
|
+
)
|
|
1502
|
+
|
|
1503
|
+
export const PricingCardBasic = ({ name, price, features }: any) => (
|
|
1504
|
+
<div className="p-6 rounded-xl border bg-card flex flex-col items-center text-center">
|
|
1505
|
+
<h3 className="text-xl font-medium mb-2">{name}</h3>
|
|
1506
|
+
<div className="mb-6"><span className="text-4xl font-bold">\${price}</span><span className="text-muted-foreground">/mo</span></div>
|
|
1507
|
+
<ul className="space-y-3 w-full mb-8 text-sm">
|
|
1508
|
+
{features?.map((f: string, i: number) => <li key={i} className="text-muted-foreground border-b pb-2 last:border-0">{f}</li>)}
|
|
1509
|
+
</ul>
|
|
1510
|
+
<button className="w-full py-2 bg-primary text-primary-foreground rounded-lg font-medium mt-auto">Subscribe</button>
|
|
1511
|
+
</div>
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
export const WeatherCard = ({ city, temp, condition }: any) => (
|
|
1515
|
+
<div className="p-6 rounded-xl border bg-gradient-to-br from-blue-500 to-cyan-400 text-white shadow-lg">
|
|
1516
|
+
<div className="flex justify-between items-start mb-8">
|
|
1517
|
+
<div>
|
|
1518
|
+
<h3 className="text-2xl font-bold">{city}</h3>
|
|
1519
|
+
<p className="opacity-80">{condition}</p>
|
|
1520
|
+
</div>
|
|
1521
|
+
<div className="text-5xl font-light">{temp}\xB0</div>
|
|
1522
|
+
</div>
|
|
1523
|
+
<div className="flex gap-4 opacity-90 text-sm">
|
|
1524
|
+
<span>H: {temp + 4}\xB0</span>
|
|
1525
|
+
<span>L: {temp - 3}\xB0</span>
|
|
1526
|
+
</div>
|
|
1527
|
+
</div>
|
|
1528
|
+
)
|
|
1529
|
+
|
|
1530
|
+
export const EventCard = ({ title, date, location }: any) => (
|
|
1531
|
+
<div className="flex p-4 rounded-xl border bg-card gap-4">
|
|
1532
|
+
<div className="flex flex-col items-center justify-center bg-primary/10 text-primary rounded-lg px-4 py-2 min-w-[70px]">
|
|
1533
|
+
<span className="text-xs uppercase font-bold">{date?.split(' ')[0]}</span>
|
|
1534
|
+
<span className="text-2xl font-black">{date?.split(' ')[1]}</span>
|
|
1535
|
+
</div>
|
|
1536
|
+
<div className="flex flex-col justify-center">
|
|
1537
|
+
<h3 className="font-bold text-lg leading-tight mb-1">{title}</h3>
|
|
1538
|
+
<div className="flex items-center text-sm text-muted-foreground gap-1">
|
|
1539
|
+
<MapPin className="w-3 h-3" /> {location}
|
|
1540
|
+
</div>
|
|
1541
|
+
</div>
|
|
1542
|
+
</div>
|
|
1543
|
+
)
|
|
1544
|
+
|
|
1545
|
+
export const TestimonialCardBasic = ({ text, author }: any) => (
|
|
1546
|
+
<div className="p-6 rounded-xl border bg-muted/30 italic relative">
|
|
1547
|
+
<span className="absolute top-4 left-4 text-4xl text-primary/20 font-serif">"</span>
|
|
1548
|
+
<p className="relative z-10 text-muted-foreground mb-4 pt-4">{text}</p>
|
|
1549
|
+
<div className="flex items-center gap-2">
|
|
1550
|
+
<div className="w-8 h-8 rounded-full bg-primary/20" />
|
|
1551
|
+
<span className="font-semibold text-sm not-italic">{author}</span>
|
|
1552
|
+
</div>
|
|
1553
|
+
</div>
|
|
1554
|
+
)
|
|
1555
|
+
|
|
1556
|
+
export const InteractiveCard = ({ title, description }: any) => (
|
|
1557
|
+
<div className="group p-6 rounded-xl border bg-card hover:bg-primary hover:text-primary-foreground transition-all duration-300 cursor-pointer">
|
|
1558
|
+
<div className="w-12 h-12 rounded-lg bg-primary/10 text-primary group-hover:bg-primary-foreground/20 group-hover:text-primary-foreground flex items-center justify-center mb-4 transition-colors">
|
|
1559
|
+
<Star className="w-6 h-6" />
|
|
1560
|
+
</div>
|
|
1561
|
+
<h3 className="text-xl font-bold mb-2">{title}</h3>
|
|
1562
|
+
<p className="text-muted-foreground group-hover:text-primary-foreground/80 transition-colors">{description}</p>
|
|
1563
|
+
</div>
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1566
|
+
|
|
596
1567
|
`
|
|
597
1568
|
};
|
|
598
1569
|
|
|
@@ -603,7 +1574,8 @@ var alert = {
|
|
|
603
1574
|
"class-variance-authority",
|
|
604
1575
|
"clsx",
|
|
605
1576
|
"tailwind-merge",
|
|
606
|
-
"framer-motion"
|
|
1577
|
+
"framer-motion",
|
|
1578
|
+
"lucide-react"
|
|
607
1579
|
],
|
|
608
1580
|
fileName: "alert.tsx",
|
|
609
1581
|
content: `"use client"
|
|
@@ -611,10 +1583,11 @@ var alert = {
|
|
|
611
1583
|
import * as React from "react"
|
|
612
1584
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
613
1585
|
import { cn } from "../utils/cn"
|
|
614
|
-
import { motion,
|
|
1586
|
+
import { motion, AnimatePresence } from "framer-motion"
|
|
1587
|
+
import { AlertCircle, Info, CheckCircle2, XCircle, Cookie, BellRing, WifiOff, AlertTriangle, X } from "lucide-react"
|
|
615
1588
|
|
|
616
1589
|
const alertVariants = cva(
|
|
617
|
-
"relative w-full rounded-
|
|
1590
|
+
"relative w-full rounded-2xl border p-4 [&>svg~*]:pl-7 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 transition-all duration-300 shadow-sm",
|
|
618
1591
|
{
|
|
619
1592
|
variants: {
|
|
620
1593
|
variant: {
|
|
@@ -624,6 +1597,12 @@ const alertVariants = cva(
|
|
|
624
1597
|
warning: "border-amber-500/20 bg-amber-500/10 text-amber-600 dark:text-amber-400 [&>svg]:text-amber-600 dark:[&>svg]:text-amber-400",
|
|
625
1598
|
info: "border-blue-500/20 bg-blue-500/10 text-blue-600 dark:text-blue-400 [&>svg]:text-blue-600 dark:[&>svg]:text-blue-400",
|
|
626
1599
|
glass: "backdrop-blur-md bg-white/5 dark:bg-black/20 border-white/10 dark:border-white/5 text-foreground",
|
|
1600
|
+
floating: "fixed bottom-5 right-5 z-50 max-w-sm bg-card/95 backdrop-blur-md border border-border/80 text-foreground shadow-[0_10px_30px_rgba(0,0,0,0.25)] animate-slide-up",
|
|
1601
|
+
minimal: "border-0 bg-muted/40 p-3 text-sm rounded-xl text-foreground hover:bg-muted/60 shadow-none [&>svg]:top-3.5",
|
|
1602
|
+
neon: "border-purple-500/50 bg-purple-500/10 text-purple-200 shadow-[0_0_15px_rgba(168,85,247,0.3)] [&>svg]:text-purple-400",
|
|
1603
|
+
cyberpunk: "border-l-4 border-yellow-400 bg-black text-yellow-400 shadow-[4px_4px_0_0_rgba(250,204,21,1)] rounded-none font-mono uppercase [&>svg]:text-yellow-400",
|
|
1604
|
+
gradient: "bg-gradient-to-r from-blue-500/10 to-indigo-500/10 border-blue-500/20 text-foreground [&>svg]:text-blue-500",
|
|
1605
|
+
banner: "w-full bg-indigo-600 text-white border-0 shadow-md rounded-none sm:rounded-xl [&>svg]:text-white",
|
|
627
1606
|
},
|
|
628
1607
|
},
|
|
629
1608
|
defaultVariants: {
|
|
@@ -635,76 +1614,85 @@ const alertVariants = cva(
|
|
|
635
1614
|
export interface AlertProps
|
|
636
1615
|
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>,
|
|
637
1616
|
VariantProps<typeof alertVariants> {
|
|
638
|
-
/**
|
|
639
|
-
* Whether to enable entry animations
|
|
640
|
-
* @default true
|
|
641
|
-
*/
|
|
642
1617
|
animate?: boolean;
|
|
643
|
-
/**
|
|
644
|
-
* The title of the alert
|
|
645
|
-
*/
|
|
646
1618
|
title?: React.ReactNode;
|
|
647
|
-
/**
|
|
648
|
-
* The description of the alert
|
|
649
|
-
*/
|
|
650
1619
|
description?: React.ReactNode;
|
|
651
|
-
/**
|
|
652
|
-
* An optional icon to display
|
|
653
|
-
*/
|
|
654
1620
|
icon?: React.ReactNode;
|
|
655
|
-
/**
|
|
656
|
-
* Whether the alert can be dismissed
|
|
657
|
-
*/
|
|
658
1621
|
dismissible?: boolean;
|
|
659
|
-
/**
|
|
660
|
-
* Callback function called when the alert is dismissed
|
|
661
|
-
*/
|
|
662
1622
|
onDismiss?: () => void;
|
|
1623
|
+
actionText?: string;
|
|
1624
|
+
onAction?: () => void;
|
|
663
1625
|
}
|
|
664
1626
|
|
|
665
1627
|
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
666
|
-
(
|
|
667
|
-
|
|
1628
|
+
(
|
|
1629
|
+
{
|
|
1630
|
+
className,
|
|
1631
|
+
variant,
|
|
1632
|
+
animate = true,
|
|
1633
|
+
title,
|
|
1634
|
+
description,
|
|
1635
|
+
icon,
|
|
1636
|
+
dismissible = false,
|
|
1637
|
+
onDismiss,
|
|
1638
|
+
actionText,
|
|
1639
|
+
onAction,
|
|
1640
|
+
children,
|
|
1641
|
+
...props
|
|
1642
|
+
},
|
|
1643
|
+
ref
|
|
1644
|
+
) => {
|
|
668
1645
|
const [isOpen, setIsOpen] = React.useState(true);
|
|
669
1646
|
|
|
670
1647
|
if (!isOpen) return null;
|
|
671
1648
|
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
1649
|
+
const isMinimal = variant === "minimal";
|
|
1650
|
+
const isBanner = variant === "banner";
|
|
1651
|
+
|
|
1652
|
+
const content = (
|
|
675
1653
|
<>
|
|
676
|
-
{icon && <div className="absolute left-4 top-4">{icon}</div>}
|
|
677
|
-
<div className={cn(icon ? "pl-7" : "")}>
|
|
1654
|
+
{icon && <div className={cn("absolute left-4", isMinimal ? "top-3" : "top-4")}>{icon}</div>}
|
|
1655
|
+
<div className={cn(icon ? "pl-7" : "", "pr-8")}>
|
|
678
1656
|
{title && <AlertTitle>{title}</AlertTitle>}
|
|
679
1657
|
{description && <AlertDescription>{description}</AlertDescription>}
|
|
680
1658
|
{!title && !description && children}
|
|
681
1659
|
</div>
|
|
1660
|
+
{isBanner && actionText && (
|
|
1661
|
+
<button
|
|
1662
|
+
onClick={onAction}
|
|
1663
|
+
className="absolute right-12 top-1/2 -translate-y-1/2 text-xs font-bold bg-white text-indigo-600 px-3 py-1.5 rounded-md hover:bg-indigo-50 transition-colors"
|
|
1664
|
+
>
|
|
1665
|
+
{actionText}
|
|
1666
|
+
</button>
|
|
1667
|
+
)}
|
|
682
1668
|
{dismissible && (
|
|
683
1669
|
<button
|
|
684
1670
|
onClick={() => {
|
|
685
1671
|
setIsOpen(false);
|
|
686
1672
|
onDismiss?.();
|
|
687
1673
|
}}
|
|
688
|
-
className="absolute right-4 top-4 opacity-
|
|
1674
|
+
className="absolute right-4 top-4 opacity-50 hover:opacity-100 transition-opacity p-0.5 rounded-md hover:bg-muted"
|
|
689
1675
|
aria-label="Dismiss"
|
|
690
1676
|
>
|
|
691
|
-
<
|
|
1677
|
+
<X className="h-4 w-4" />
|
|
692
1678
|
</button>
|
|
693
1679
|
)}
|
|
694
1680
|
</>
|
|
695
1681
|
);
|
|
696
1682
|
|
|
1683
|
+
const alertClass = cn(alertVariants({ variant }), className);
|
|
1684
|
+
|
|
697
1685
|
if (animate) {
|
|
698
1686
|
return (
|
|
699
1687
|
<motion.div
|
|
700
1688
|
ref={ref}
|
|
701
1689
|
role="alert"
|
|
702
|
-
initial={{ opacity: 0, y:
|
|
703
|
-
animate={{ opacity: 1, y: 0 }}
|
|
704
|
-
exit={{ opacity: 0, y: 10 }}
|
|
705
|
-
transition={{
|
|
706
|
-
className={
|
|
707
|
-
{...(props as
|
|
1690
|
+
initial={{ opacity: 0, y: variant === "floating" ? 30 : 15, scale: variant === "floating" ? 0.95 : 1 }}
|
|
1691
|
+
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
1692
|
+
exit={{ opacity: 0, y: variant === "floating" ? 20 : 10, scale: 0.95 }}
|
|
1693
|
+
transition={{ type: "spring", stiffness: 350, damping: 24 }}
|
|
1694
|
+
className={alertClass}
|
|
1695
|
+
{...(props as any)}
|
|
708
1696
|
>
|
|
709
1697
|
{content}
|
|
710
1698
|
</motion.div>
|
|
@@ -715,8 +1703,8 @@ const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
|
715
1703
|
<div
|
|
716
1704
|
ref={ref}
|
|
717
1705
|
role="alert"
|
|
718
|
-
className={
|
|
719
|
-
{...props}
|
|
1706
|
+
className={alertClass}
|
|
1707
|
+
{...(props as React.HTMLAttributes<HTMLDivElement>)}
|
|
720
1708
|
>
|
|
721
1709
|
{content}
|
|
722
1710
|
</div>
|
|
@@ -731,7 +1719,7 @@ const AlertTitle = React.forwardRef<
|
|
|
731
1719
|
>(({ className, ...props }, ref) => (
|
|
732
1720
|
<h5
|
|
733
1721
|
ref={ref}
|
|
734
|
-
className={cn("mb-1 font-semibold leading-none tracking-tight text-base", className)}
|
|
1722
|
+
className={cn("mb-1 font-semibold leading-none tracking-tight text-base text-foreground", className)}
|
|
735
1723
|
{...props}
|
|
736
1724
|
/>
|
|
737
1725
|
))
|
|
@@ -743,13 +1731,137 @@ const AlertDescription = React.forwardRef<
|
|
|
743
1731
|
>(({ className, ...props }, ref) => (
|
|
744
1732
|
<div
|
|
745
1733
|
ref={ref}
|
|
746
|
-
className={cn("text-sm opacity-90 [&_p]:leading-relaxed", className)}
|
|
1734
|
+
className={cn("text-sm text-muted-foreground leading-relaxed mt-1 opacity-90 [&_p]:leading-relaxed", className)}
|
|
747
1735
|
{...props}
|
|
748
1736
|
/>
|
|
749
1737
|
))
|
|
750
1738
|
AlertDescription.displayName = "AlertDescription"
|
|
751
1739
|
|
|
1740
|
+
// ----------------------------------------------------
|
|
1741
|
+
// Merged subcomponents and wrappers
|
|
1742
|
+
// ----------------------------------------------------
|
|
1743
|
+
|
|
1744
|
+
// 1. ToastAlertWrapper
|
|
1745
|
+
export const ToastAlertWrapper = ({ children, className, title, description, time }: any) => (
|
|
1746
|
+
<div className={cn("max-w-sm w-full bg-background border border-border/80 shadow-xl rounded-2xl p-4 flex gap-4 items-start relative", className)}>
|
|
1747
|
+
<CheckCircle2 className="h-5 w-5 text-green-500 shrink-0 mt-0.5" />
|
|
1748
|
+
<div className="flex-1">
|
|
1749
|
+
{title && <h4 className="font-semibold text-sm">{title}</h4>}
|
|
1750
|
+
{description && <p className="text-sm text-muted-foreground mt-1">{description}</p>}
|
|
1751
|
+
{children}
|
|
1752
|
+
</div>
|
|
1753
|
+
{time && <span className="text-xs text-muted-foreground/60">{time}</span>}
|
|
1754
|
+
</div>
|
|
1755
|
+
)
|
|
1756
|
+
|
|
1757
|
+
// 2. CookieAlert
|
|
1758
|
+
export const CookieAlert = ({ onAccept, onDecline }: { onAccept?: () => void; onDecline?: () => void }) => {
|
|
1759
|
+
const [visible, setVisible] = React.useState(true)
|
|
1760
|
+
if (!visible) return null
|
|
1761
|
+
return (
|
|
1762
|
+
<div className="max-w-md bg-card border rounded-2xl p-6 shadow-2xl space-y-4">
|
|
1763
|
+
<div className="flex items-center gap-3">
|
|
1764
|
+
<Cookie className="h-6 w-6 text-orange-500 animate-bounce" />
|
|
1765
|
+
<h4 className="font-bold text-lg">Cookie Preferences</h4>
|
|
1766
|
+
</div>
|
|
1767
|
+
<p className="text-sm text-muted-foreground">
|
|
1768
|
+
We use cookies to improve your experience. By continuing to visit this site you agree to our use of cookies.
|
|
1769
|
+
</p>
|
|
1770
|
+
<div className="flex gap-3 pt-2">
|
|
1771
|
+
<button
|
|
1772
|
+
onClick={() => { setVisible(false); onAccept?.(); }}
|
|
1773
|
+
className="flex-1 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/95 transition-colors rounded-xl text-sm font-semibold shadow-md shadow-primary/10"
|
|
1774
|
+
>
|
|
1775
|
+
Accept All
|
|
1776
|
+
</button>
|
|
1777
|
+
<button
|
|
1778
|
+
onClick={() => { setVisible(false); onDecline?.(); }}
|
|
1779
|
+
className="flex-1 px-4 py-2 border rounded-xl text-sm font-semibold hover:bg-muted transition-colors"
|
|
1780
|
+
>
|
|
1781
|
+
Decline
|
|
1782
|
+
</button>
|
|
1783
|
+
</div>
|
|
1784
|
+
</div>
|
|
1785
|
+
)
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// 3. OfflineBanner
|
|
1789
|
+
export const OfflineBanner = () => (
|
|
1790
|
+
<div className="w-full bg-red-600 text-white p-3.5 flex justify-center items-center gap-2 text-sm font-semibold shadow-md sm:rounded-xl">
|
|
1791
|
+
<WifiOff className="w-4 h-4 animate-pulse" /> You are currently offline. Some features may be unavailable.
|
|
1792
|
+
</div>
|
|
1793
|
+
)
|
|
1794
|
+
|
|
1795
|
+
// 4. RateLimitAlert
|
|
1796
|
+
export const RateLimitAlert = () => (
|
|
1797
|
+
<div className="border border-orange-500/30 bg-orange-500/10 p-5 rounded-2xl flex gap-3 items-start max-w-md shadow-sm">
|
|
1798
|
+
<AlertTriangle className="w-5 h-5 text-orange-500 shrink-0 mt-0.5 animate-pulse" />
|
|
1799
|
+
<div className="flex-1">
|
|
1800
|
+
<h4 className="font-bold text-orange-600 dark:text-orange-400">Rate Limit Exceeded</h4>
|
|
1801
|
+
<p className="text-sm text-orange-600/80 dark:text-orange-400/80 mt-1 mb-4">
|
|
1802
|
+
You have made too many requests. Please wait 45 seconds before trying again.
|
|
1803
|
+
</p>
|
|
1804
|
+
<div className="w-full h-1.5 bg-orange-500/20 rounded-full overflow-hidden">
|
|
1805
|
+
<motion.div animate={{ width: ["100%", "0%"] }} transition={{ duration: 45, ease: "linear" }} className="h-full bg-orange-500" />
|
|
1806
|
+
</div>
|
|
1807
|
+
</div>
|
|
1808
|
+
</div>
|
|
1809
|
+
)
|
|
1810
|
+
|
|
1811
|
+
// Re-export original/merged components
|
|
752
1812
|
export { Alert, AlertTitle, AlertDescription }
|
|
1813
|
+
export const CyberAlert = ({ title, description, variant, ...props }: any) => (
|
|
1814
|
+
<Alert variant="cyberpunk" title={title} description={description} {...props} />
|
|
1815
|
+
)
|
|
1816
|
+
export const SoftAlert = ({ title, description, variant, ...props }: any) => (
|
|
1817
|
+
<Alert variant={variant === "success" ? "success" : "info"} title={title} description={description} {...props} />
|
|
1818
|
+
)
|
|
1819
|
+
export const MinimalAlert = ({ title, description, variant, ...props }: any) => (
|
|
1820
|
+
<Alert variant="minimal" title={title} description={description} {...props} />
|
|
1821
|
+
)
|
|
1822
|
+
export const LeftBorderAlert = ({ title, description, variant, ...props }: any) => (
|
|
1823
|
+
<Alert variant={variant === "warning" ? "warning" : "default"} className="border-l-4 border-l-primary" title={title} description={description} {...props} />
|
|
1824
|
+
)
|
|
1825
|
+
export const IconTopAlert = ({ title, description, variant, ...props }: any) => (
|
|
1826
|
+
<div className="flex flex-col items-center text-center p-6 bg-card border rounded-2xl" {...props}>
|
|
1827
|
+
<div className="h-12 w-12 rounded-full bg-destructive/10 text-destructive flex items-center justify-center mb-4">
|
|
1828
|
+
<AlertCircle className="h-6 w-6" />
|
|
1829
|
+
</div>
|
|
1830
|
+
<h4 className="font-bold text-lg mb-2">{title}</h4>
|
|
1831
|
+
<p className="text-sm text-muted-foreground">{description}</p>
|
|
1832
|
+
</div>
|
|
1833
|
+
)
|
|
1834
|
+
export const SolidAlert = ({ title, description, variant, ...props }: any) => {
|
|
1835
|
+
const bgClasses: Record<string, string> = {
|
|
1836
|
+
error: "bg-red-600 text-white border-0",
|
|
1837
|
+
success: "bg-emerald-600 text-white border-0",
|
|
1838
|
+
warning: "bg-amber-500 text-black border-0",
|
|
1839
|
+
default: "bg-primary text-primary-foreground border-0",
|
|
1840
|
+
}
|
|
1841
|
+
const bgClass = bgClasses[variant] || bgClasses.default
|
|
1842
|
+
return (
|
|
1843
|
+
<div className={cn("p-4 rounded-xl shadow-lg flex gap-3 items-start", bgClass)} {...props}>
|
|
1844
|
+
<Info className="h-5 w-5 shrink-0 mt-0.5" />
|
|
1845
|
+
<div>
|
|
1846
|
+
<h4 className="font-bold">{title}</h4>
|
|
1847
|
+
<p className="text-sm opacity-90 mt-1">{description}</p>
|
|
1848
|
+
</div>
|
|
1849
|
+
</div>
|
|
1850
|
+
)
|
|
1851
|
+
}
|
|
1852
|
+
export const BannerAlert = ({ message, variant, ...props }: any) => (
|
|
1853
|
+
<Alert variant="banner" title={message} {...props} />
|
|
1854
|
+
)
|
|
1855
|
+
export const NeonAlert = ({ title, description, variant, ...props }: any) => (
|
|
1856
|
+
<Alert variant="neon" title={title} description={description} {...props} />
|
|
1857
|
+
)
|
|
1858
|
+
export const GlassAlert = ({ title, description, variant, ...props }: any) => (
|
|
1859
|
+
<Alert variant="glass" title={title} description={description} {...props} />
|
|
1860
|
+
)
|
|
1861
|
+
export const DismissibleAlert = ({ variant, title, description, ...props }: any) => (
|
|
1862
|
+
<Alert variant={variant} title={title || "Attention"} description={description || "Action required"} dismissible={true} {...props} />
|
|
1863
|
+
)
|
|
1864
|
+
|
|
753
1865
|
`
|
|
754
1866
|
};
|
|
755
1867
|
|
|
@@ -760,7 +1872,8 @@ var badge = {
|
|
|
760
1872
|
"class-variance-authority",
|
|
761
1873
|
"clsx",
|
|
762
1874
|
"tailwind-merge",
|
|
763
|
-
"framer-motion"
|
|
1875
|
+
"framer-motion",
|
|
1876
|
+
"lucide-react"
|
|
764
1877
|
],
|
|
765
1878
|
fileName: "badge.tsx",
|
|
766
1879
|
content: `'use client';
|
|
@@ -768,7 +1881,7 @@ var badge = {
|
|
|
768
1881
|
import * as React from "react"
|
|
769
1882
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
770
1883
|
import { cn } from "../utils/cn"
|
|
771
|
-
import {
|
|
1884
|
+
import { Star } from "lucide-react"
|
|
772
1885
|
|
|
773
1886
|
const badgeVariants = cva(
|
|
774
1887
|
"inline-flex items-center gap-1 rounded-full border font-semibold transition-all focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-default",
|
|
@@ -779,9 +1892,8 @@ const badgeVariants = cva(
|
|
|
779
1892
|
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
780
1893
|
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
781
1894
|
outline: "text-foreground border-border hover:bg-accent",
|
|
782
|
-
// New WOW Variants
|
|
783
1895
|
gradient: "border-transparent bg-gradient-to-r from-violet-500 to-pink-500 text-white shadow-sm",
|
|
784
|
-
neon: "border-
|
|
1896
|
+
neon: "border-purple-500/50 bg-purple-500/10 text-purple-400 shadow-[0_0_10px_rgba(168,85,247,0.3)]",
|
|
785
1897
|
success: "border-transparent bg-emerald-500/20 text-emerald-600 dark:text-emerald-400",
|
|
786
1898
|
},
|
|
787
1899
|
size: {
|
|
@@ -800,19 +1912,12 @@ const badgeVariants = cva(
|
|
|
800
1912
|
export interface BadgeProps
|
|
801
1913
|
extends React.HTMLAttributes<HTMLDivElement>,
|
|
802
1914
|
VariantProps<typeof badgeVariants> {
|
|
803
|
-
/**
|
|
804
|
-
* Whether to show a pulse animation on the dot
|
|
805
|
-
* @default false
|
|
806
|
-
*/
|
|
807
1915
|
pulse?: boolean;
|
|
808
|
-
/**
|
|
809
|
-
* Whether to show a dot indicator
|
|
810
|
-
* @default false
|
|
811
|
-
*/
|
|
812
1916
|
dot?: boolean;
|
|
1917
|
+
text?: string;
|
|
813
1918
|
}
|
|
814
1919
|
|
|
815
|
-
function Badge({ className, variant, size, pulse = false, dot = false, children, ...props }: BadgeProps) {
|
|
1920
|
+
function Badge({ className, variant, size, pulse = false, dot = false, children, text, ...props }: BadgeProps) {
|
|
816
1921
|
const showDot = dot || pulse;
|
|
817
1922
|
|
|
818
1923
|
return (
|
|
@@ -825,12 +1930,355 @@ function Badge({ className, variant, size, pulse = false, dot = false, children,
|
|
|
825
1930
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-current"></span>
|
|
826
1931
|
</span>
|
|
827
1932
|
)}
|
|
828
|
-
{children}
|
|
1933
|
+
{children || text}
|
|
829
1934
|
</div>
|
|
830
1935
|
)
|
|
831
1936
|
}
|
|
832
1937
|
|
|
833
|
-
export { Badge, badgeVariants }
|
|
1938
|
+
export { Badge, badgeVariants }`
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
// src/registry/morphing-geometry.ts
|
|
1942
|
+
var morphingGeometry = {
|
|
1943
|
+
name: "morphing-geometry",
|
|
1944
|
+
dependencies: [
|
|
1945
|
+
"clsx",
|
|
1946
|
+
"tailwind-merge",
|
|
1947
|
+
"framer-motion",
|
|
1948
|
+
"lucide-react"
|
|
1949
|
+
],
|
|
1950
|
+
fileName: "morphing-geometry.tsx",
|
|
1951
|
+
content: `'use client';
|
|
1952
|
+
|
|
1953
|
+
import * as React from 'react';
|
|
1954
|
+
import { motion, HTMLMotionProps } from 'framer-motion';
|
|
1955
|
+
import { Sparkles } from 'lucide-react';
|
|
1956
|
+
import { cn } from '../utils/cn';
|
|
1957
|
+
|
|
1958
|
+
export type MorphingShape = 'pill' | 'circle' | 'square' | 'squircle' | 'custom';
|
|
1959
|
+
export type MorphingVariant = 'gradient' | 'aurora' | 'neon' | 'glass' | 'outline' | 'subtle';
|
|
1960
|
+
export type MorphingColor = 'violet' | 'cyan' | 'emerald' | 'rose' | 'amber' | 'rainbow' | 'mono';
|
|
1961
|
+
export type MorphingSize = 'sm' | 'md' | 'lg' | 'xl' | 'custom';
|
|
1962
|
+
|
|
1963
|
+
export interface MorphingGeometryProps extends Omit<HTMLMotionProps<'div'>, 'children'> {
|
|
1964
|
+
shape?: MorphingShape;
|
|
1965
|
+
radius?: number | string;
|
|
1966
|
+
variant?: MorphingVariant;
|
|
1967
|
+
color?: MorphingColor;
|
|
1968
|
+
size?: MorphingSize;
|
|
1969
|
+
dimension?: number;
|
|
1970
|
+
spin?: boolean;
|
|
1971
|
+
spinDuration?: number;
|
|
1972
|
+
interactive?: boolean;
|
|
1973
|
+
glow?: boolean;
|
|
1974
|
+
icon?: React.ReactNode;
|
|
1975
|
+
children?: React.ReactNode;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
export const MorphingGeometry = React.forwardRef<HTMLDivElement, MorphingGeometryProps>(
|
|
1979
|
+
(
|
|
1980
|
+
{
|
|
1981
|
+
shape = 'squircle',
|
|
1982
|
+
radius,
|
|
1983
|
+
variant = 'gradient',
|
|
1984
|
+
color = 'violet',
|
|
1985
|
+
size = 'md',
|
|
1986
|
+
dimension,
|
|
1987
|
+
spin = false,
|
|
1988
|
+
spinDuration = 6,
|
|
1989
|
+
interactive = false,
|
|
1990
|
+
glow = true,
|
|
1991
|
+
icon,
|
|
1992
|
+
children,
|
|
1993
|
+
className,
|
|
1994
|
+
style,
|
|
1995
|
+
onClick,
|
|
1996
|
+
...props
|
|
1997
|
+
},
|
|
1998
|
+
ref
|
|
1999
|
+
) => {
|
|
2000
|
+
return (
|
|
2001
|
+
<motion.div
|
|
2002
|
+
ref={ref}
|
|
2003
|
+
animate={spin ? { rotate: [0, 90, 180, 270, 360] } : { rotate: 0 }}
|
|
2004
|
+
transition={{ rotate: { duration: spinDuration, repeat: Infinity, ease: 'linear' }, borderRadius: { duration: 0.4 } }}
|
|
2005
|
+
className={cn('relative flex items-center justify-center select-none overflow-hidden transition-all duration-300 w-24 h-24', className)}
|
|
2006
|
+
style={{ borderRadius: radius || '24%', ...style }}
|
|
2007
|
+
{...props}
|
|
2008
|
+
>
|
|
2009
|
+
<div className="relative z-10 flex flex-col items-center justify-center p-2 text-center">
|
|
2010
|
+
{icon || children || <Sparkles className="w-6 h-6 text-white" />}
|
|
2011
|
+
</div>
|
|
2012
|
+
</motion.div>
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
2015
|
+
);
|
|
2016
|
+
|
|
2017
|
+
MorphingGeometry.displayName = 'MorphingGeometry';
|
|
2018
|
+
export default MorphingGeometry;
|
|
2019
|
+
`
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
// src/registry/aurora-border-fx.ts
|
|
2023
|
+
var auroraBorderFX = {
|
|
2024
|
+
name: "aurora-border-fx",
|
|
2025
|
+
dependencies: [
|
|
2026
|
+
"clsx",
|
|
2027
|
+
"tailwind-merge",
|
|
2028
|
+
"framer-motion",
|
|
2029
|
+
"lucide-react"
|
|
2030
|
+
],
|
|
2031
|
+
fileName: "aurora-border-fx.tsx",
|
|
2032
|
+
content: `'use client';
|
|
2033
|
+
|
|
2034
|
+
import * as React from 'react';
|
|
2035
|
+
import { motion, useReducedMotion } from 'framer-motion';
|
|
2036
|
+
import { Sparkles } from 'lucide-react';
|
|
2037
|
+
import { cn } from '../utils/cn';
|
|
2038
|
+
|
|
2039
|
+
export type AuroraFXColor = 'violet' | 'cyan' | 'emerald' | 'rose' | 'amber' | string;
|
|
2040
|
+
export type AuroraFXGlow = 'none' | 'subtle' | 'medium' | 'strong';
|
|
2041
|
+
export type AuroraFXRadius = 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
2042
|
+
|
|
2043
|
+
export interface AuroraColorOption {
|
|
2044
|
+
name: string;
|
|
2045
|
+
hex: string;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
export const defaultAuroraColors: AuroraColorOption[] = [
|
|
2049
|
+
{ name: 'Violet', hex: '#8b5cf6' },
|
|
2050
|
+
{ name: 'Cyan', hex: '#06b6d4' },
|
|
2051
|
+
{ name: 'Emerald', hex: '#10b981' },
|
|
2052
|
+
{ name: 'Rose', hex: '#f43f5e' },
|
|
2053
|
+
{ name: 'Amber', hex: '#f59e0b' },
|
|
2054
|
+
];
|
|
2055
|
+
|
|
2056
|
+
const colorPresetMap: Record<string, string> = {
|
|
2057
|
+
violet: '#8b5cf6',
|
|
2058
|
+
cyan: '#06b6d4',
|
|
2059
|
+
emerald: '#10b981',
|
|
2060
|
+
rose: '#f43f5e',
|
|
2061
|
+
amber: '#f59e0b',
|
|
2062
|
+
};
|
|
2063
|
+
|
|
2064
|
+
const radiusMap: Record<AuroraFXRadius, { outer: string; inner: string }> = {
|
|
2065
|
+
sm: { outer: 'rounded-lg', inner: 'rounded-[calc(0.5rem-1px)]' },
|
|
2066
|
+
md: { outer: 'rounded-xl', inner: 'rounded-[calc(0.75rem-1px)]' },
|
|
2067
|
+
lg: { outer: 'rounded-2xl', inner: 'rounded-[calc(1rem-1px)]' },
|
|
2068
|
+
xl: { outer: 'rounded-3xl', inner: 'rounded-[calc(1.5rem-1.5px)]' },
|
|
2069
|
+
full: { outer: 'rounded-full', inner: 'rounded-full' },
|
|
2070
|
+
};
|
|
2071
|
+
|
|
2072
|
+
const glowOpacityMap: Record<AuroraFXGlow, number> = {
|
|
2073
|
+
none: 0,
|
|
2074
|
+
subtle: 0.25,
|
|
2075
|
+
medium: 0.45,
|
|
2076
|
+
strong: 0.75,
|
|
2077
|
+
};
|
|
2078
|
+
|
|
2079
|
+
export interface AuroraBorderFXProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
2080
|
+
color?: AuroraFXColor;
|
|
2081
|
+
glow?: AuroraFXGlow;
|
|
2082
|
+
radius?: AuroraFXRadius;
|
|
2083
|
+
badgeText?: string;
|
|
2084
|
+
badgeIcon?: React.ReactNode;
|
|
2085
|
+
title?: string;
|
|
2086
|
+
description?: string;
|
|
2087
|
+
showColorPicker?: boolean;
|
|
2088
|
+
colors?: AuroraColorOption[];
|
|
2089
|
+
activeColor?: string;
|
|
2090
|
+
onColorChange?: (colorHex: string) => void;
|
|
2091
|
+
previewSlot?: React.ReactNode;
|
|
2092
|
+
footerSlot?: React.ReactNode;
|
|
2093
|
+
children?: React.ReactNode;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
export const AuroraBorderFX = React.forwardRef<HTMLDivElement, AuroraBorderFXProps>(
|
|
2097
|
+
(
|
|
2098
|
+
{
|
|
2099
|
+
color = 'violet',
|
|
2100
|
+
glow = 'medium',
|
|
2101
|
+
radius = 'lg',
|
|
2102
|
+
badgeText = 'Aurora Border FX',
|
|
2103
|
+
badgeIcon = <Sparkles className="w-3 h-3" />,
|
|
2104
|
+
title = 'Reactive Aurora Borders',
|
|
2105
|
+
description = 'Smooth multi-color conic gradients that dynamically track and react with zero JavaScript canvas lag.',
|
|
2106
|
+
showColorPicker = true,
|
|
2107
|
+
colors = defaultAuroraColors,
|
|
2108
|
+
activeColor: controlledColor,
|
|
2109
|
+
onColorChange,
|
|
2110
|
+
previewSlot,
|
|
2111
|
+
footerSlot,
|
|
2112
|
+
className,
|
|
2113
|
+
children,
|
|
2114
|
+
...props
|
|
2115
|
+
},
|
|
2116
|
+
ref
|
|
2117
|
+
) => {
|
|
2118
|
+
const resolvedInitialColor = colorPresetMap[color] || color || '#8b5cf6';
|
|
2119
|
+
const [internalColor, setInternalColor] = React.useState<string>(resolvedInitialColor);
|
|
2120
|
+
|
|
2121
|
+
React.useEffect(() => {
|
|
2122
|
+
if (colorPresetMap[color]) {
|
|
2123
|
+
setInternalColor(colorPresetMap[color]);
|
|
2124
|
+
} else if (color) {
|
|
2125
|
+
setInternalColor(color);
|
|
2126
|
+
}
|
|
2127
|
+
}, [color]);
|
|
2128
|
+
|
|
2129
|
+
const currentColor = controlledColor !== undefined ? controlledColor : internalColor;
|
|
2130
|
+
const radiusConfig = radiusMap[radius] || radiusMap.lg;
|
|
2131
|
+
const glowOpacity = glowOpacityMap[glow] ?? 0.45;
|
|
2132
|
+
|
|
2133
|
+
const handleSelectColor = (hex: string) => {
|
|
2134
|
+
if (controlledColor === undefined) {
|
|
2135
|
+
setInternalColor(hex);
|
|
2136
|
+
}
|
|
2137
|
+
onColorChange?.(hex);
|
|
2138
|
+
};
|
|
2139
|
+
|
|
2140
|
+
return (
|
|
2141
|
+
<div
|
|
2142
|
+
ref={ref}
|
|
2143
|
+
className={cn(
|
|
2144
|
+
'relative isolate p-5 sm:p-6 overflow-hidden flex flex-col justify-between group transition-all duration-300',
|
|
2145
|
+
'border border-border/80 bg-card/60 backdrop-blur-xl shadow-xl',
|
|
2146
|
+
radiusConfig.outer,
|
|
2147
|
+
className
|
|
2148
|
+
)}
|
|
2149
|
+
{...props}
|
|
2150
|
+
>
|
|
2151
|
+
{glow !== 'none' && (
|
|
2152
|
+
<div
|
|
2153
|
+
className="absolute -top-12 -right-12 w-48 h-48 rounded-full blur-[85px] pointer-events-none transition-colors duration-500 -z-10"
|
|
2154
|
+
style={{
|
|
2155
|
+
backgroundColor: currentColor,
|
|
2156
|
+
opacity: glowOpacity,
|
|
2157
|
+
}}
|
|
2158
|
+
/>
|
|
2159
|
+
)}
|
|
2160
|
+
|
|
2161
|
+
{glow !== 'none' && glow !== 'subtle' && (
|
|
2162
|
+
<div
|
|
2163
|
+
className="absolute -bottom-10 -left-10 w-40 h-40 rounded-full blur-[90px] pointer-events-none transition-colors duration-700 -z-10"
|
|
2164
|
+
style={{
|
|
2165
|
+
backgroundColor: currentColor,
|
|
2166
|
+
opacity: glowOpacity * 0.4,
|
|
2167
|
+
}}
|
|
2168
|
+
/>
|
|
2169
|
+
)}
|
|
2170
|
+
|
|
2171
|
+
{children ? (
|
|
2172
|
+
<div className="relative z-10 w-full h-full">{children}</div>
|
|
2173
|
+
) : (
|
|
2174
|
+
<div className="relative z-10 flex flex-col justify-between h-full space-y-5">
|
|
2175
|
+
<div className="space-y-3">
|
|
2176
|
+
<div className="flex items-center justify-between gap-3">
|
|
2177
|
+
{badgeText && (
|
|
2178
|
+
<div
|
|
2179
|
+
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-semibold border transition-all duration-300 shadow-xs"
|
|
2180
|
+
style={{
|
|
2181
|
+
backgroundColor: \`\${currentColor}18\`,
|
|
2182
|
+
borderColor: \`\${currentColor}40\`,
|
|
2183
|
+
color: currentColor,
|
|
2184
|
+
}}
|
|
2185
|
+
>
|
|
2186
|
+
{badgeIcon}
|
|
2187
|
+
<span>{badgeText}</span>
|
|
2188
|
+
</div>
|
|
2189
|
+
)}
|
|
2190
|
+
|
|
2191
|
+
{showColorPicker && colors && colors.length > 0 && (
|
|
2192
|
+
<div className="flex items-center gap-1.5 bg-muted/60 dark:bg-muted/40 p-1 rounded-full border border-border/60 backdrop-blur-md">
|
|
2193
|
+
{colors.map((c) => {
|
|
2194
|
+
const isActive = currentColor.toLowerCase() === c.hex.toLowerCase();
|
|
2195
|
+
return (
|
|
2196
|
+
<button
|
|
2197
|
+
key={c.name}
|
|
2198
|
+
type="button"
|
|
2199
|
+
onClick={() => handleSelectColor(c.hex)}
|
|
2200
|
+
className={cn(
|
|
2201
|
+
'w-3.5 h-3.5 rounded-full transition-all duration-200 cursor-pointer',
|
|
2202
|
+
isActive
|
|
2203
|
+
? 'scale-125 ring-2 ring-foreground/40 shadow-xs'
|
|
2204
|
+
: 'hover:scale-110 opacity-70 hover:opacity-100'
|
|
2205
|
+
)}
|
|
2206
|
+
style={{ backgroundColor: c.hex }}
|
|
2207
|
+
title={\`Switch to \${c.name}\`}
|
|
2208
|
+
aria-label={\`Switch glow to \${c.name}\`}
|
|
2209
|
+
/>
|
|
2210
|
+
);
|
|
2211
|
+
})}
|
|
2212
|
+
</div>
|
|
2213
|
+
)}
|
|
2214
|
+
</div>
|
|
2215
|
+
|
|
2216
|
+
<div>
|
|
2217
|
+
{title && (
|
|
2218
|
+
<h3 className="text-base sm:text-lg font-bold tracking-tight text-foreground">
|
|
2219
|
+
{title}
|
|
2220
|
+
</h3>
|
|
2221
|
+
)}
|
|
2222
|
+
{description && (
|
|
2223
|
+
<p className="text-xs sm:text-sm text-muted-foreground leading-relaxed mt-1">
|
|
2224
|
+
{description}
|
|
2225
|
+
</p>
|
|
2226
|
+
)}
|
|
2227
|
+
</div>
|
|
2228
|
+
</div>
|
|
2229
|
+
|
|
2230
|
+
<div className="pt-2 flex items-center justify-center">
|
|
2231
|
+
{previewSlot ? (
|
|
2232
|
+
previewSlot
|
|
2233
|
+
) : (
|
|
2234
|
+
<div
|
|
2235
|
+
className={cn(
|
|
2236
|
+
'relative p-[1.5px] overflow-hidden transition-all duration-300 w-full max-w-[280px]',
|
|
2237
|
+
radiusConfig.inner
|
|
2238
|
+
)}
|
|
2239
|
+
style={{
|
|
2240
|
+
background: \`linear-gradient(135deg, \${currentColor}, transparent 60%, \${currentColor}90)\`,
|
|
2241
|
+
}}
|
|
2242
|
+
>
|
|
2243
|
+
<div
|
|
2244
|
+
className={cn(
|
|
2245
|
+
'bg-card/90 dark:bg-card/80 px-4 py-3 flex items-center justify-between backdrop-blur-md shadow-inner',
|
|
2246
|
+
radiusConfig.inner
|
|
2247
|
+
)}
|
|
2248
|
+
>
|
|
2249
|
+
<div className="flex items-center gap-2.5">
|
|
2250
|
+
<div
|
|
2251
|
+
className="w-2.5 h-2.5 rounded-full animate-pulse shrink-0"
|
|
2252
|
+
style={{ backgroundColor: currentColor }}
|
|
2253
|
+
/>
|
|
2254
|
+
<span className="text-xs font-mono font-semibold text-foreground">
|
|
2255
|
+
Interactive Aurora Pill
|
|
2256
|
+
</span>
|
|
2257
|
+
</div>
|
|
2258
|
+
<span
|
|
2259
|
+
className="text-[10px] font-mono px-2 py-0.5 rounded-md font-medium border"
|
|
2260
|
+
style={{
|
|
2261
|
+
backgroundColor: \`\${currentColor}12\`,
|
|
2262
|
+
borderColor: \`\${currentColor}30\`,
|
|
2263
|
+
color: currentColor,
|
|
2264
|
+
}}
|
|
2265
|
+
>
|
|
2266
|
+
{currentColor.toUpperCase()}
|
|
2267
|
+
</span>
|
|
2268
|
+
</div>
|
|
2269
|
+
</div>
|
|
2270
|
+
)}
|
|
2271
|
+
</div>
|
|
2272
|
+
|
|
2273
|
+
{footerSlot && <div className="pt-2 border-t border-border/50">{footerSlot}</div>}
|
|
2274
|
+
</div>
|
|
2275
|
+
)}
|
|
2276
|
+
</div>
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
);
|
|
2280
|
+
|
|
2281
|
+
AuroraBorderFX.displayName = 'AuroraBorderFX';
|
|
834
2282
|
`
|
|
835
2283
|
};
|
|
836
2284
|
|
|
@@ -840,113 +2288,454 @@ var registry = {
|
|
|
840
2288
|
modal,
|
|
841
2289
|
card,
|
|
842
2290
|
alert,
|
|
843
|
-
badge
|
|
2291
|
+
badge,
|
|
2292
|
+
"morphing-geometry": morphingGeometry,
|
|
2293
|
+
"aurora-border-fx": auroraBorderFX
|
|
844
2294
|
};
|
|
845
2295
|
|
|
846
|
-
// src/
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
2296
|
+
// src/commands/add.ts
|
|
2297
|
+
function askQuestion(query) {
|
|
2298
|
+
const rl = readline.createInterface({
|
|
2299
|
+
input: process.stdin,
|
|
2300
|
+
output: process.stdout
|
|
2301
|
+
});
|
|
2302
|
+
return new Promise(
|
|
2303
|
+
(resolve4) => rl.question(query, (ans) => {
|
|
2304
|
+
rl.close();
|
|
2305
|
+
resolve4(ans);
|
|
2306
|
+
})
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
async function addCommand(components, options = {}) {
|
|
2310
|
+
const allRegistryKeys = Object.keys(registry);
|
|
2311
|
+
let targetComponents = [...components];
|
|
2312
|
+
if (options.all || targetComponents.includes("--all")) {
|
|
2313
|
+
targetComponents = allRegistryKeys;
|
|
2314
|
+
console.log(`
|
|
2315
|
+
\x1B[36m\u26A1 Adding all ${targetComponents.length} components from NexoreUI registry...\x1B[0m`);
|
|
2316
|
+
}
|
|
2317
|
+
if (targetComponents.length === 0) {
|
|
2318
|
+
console.error("\x1B[31mError: Please specify components to add or use --all.\x1B[0m");
|
|
2319
|
+
console.log("Example: npx nexoreui add button modal table --all");
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
const project = detectProject(process.cwd());
|
|
2323
|
+
console.log(`
|
|
2324
|
+
\x1B[34mDetected project type:\x1B[0m ${project.projectType.toUpperCase()}`);
|
|
2325
|
+
console.log(`\x1B[34mDetected package manager:\x1B[0m ${project.packageManager}
|
|
2326
|
+
`);
|
|
2327
|
+
let customComponentsDir;
|
|
2328
|
+
let customUtilsFile;
|
|
2329
|
+
try {
|
|
2330
|
+
const configPath = path3.join(project.baseDir, "nexore.json");
|
|
2331
|
+
if (fs3.existsSync(configPath)) {
|
|
2332
|
+
const cfg = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
2333
|
+
if (cfg.aliases?.components) {
|
|
2334
|
+
customComponentsDir = cfg.aliases.components.replace(/^@\//, project.hasSrcDir ? "src/" : "");
|
|
2335
|
+
}
|
|
2336
|
+
if (cfg.aliases?.utils) {
|
|
2337
|
+
const utilBase = cfg.aliases.utils.replace(/^@\//, project.hasSrcDir ? "src/" : "");
|
|
2338
|
+
customUtilsFile = utilBase.endsWith(".ts") || utilBase.endsWith(".js") ? utilBase : `${utilBase}.ts`;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
} catch {
|
|
2342
|
+
}
|
|
2343
|
+
const componentsToInstall = /* @__PURE__ */ new Set();
|
|
2344
|
+
const invalidComponents = [];
|
|
2345
|
+
const queue = [...targetComponents.filter((c) => c !== "--all")];
|
|
2346
|
+
while (queue.length > 0) {
|
|
2347
|
+
const compName = queue.shift();
|
|
2348
|
+
const registryItem = registry[compName];
|
|
2349
|
+
if (!registryItem) {
|
|
2350
|
+
invalidComponents.push(compName);
|
|
2351
|
+
continue;
|
|
854
2352
|
}
|
|
855
|
-
if (
|
|
856
|
-
|
|
2353
|
+
if (!componentsToInstall.has(compName)) {
|
|
2354
|
+
componentsToInstall.add(compName);
|
|
2355
|
+
if (registryItem.componentsDependencies) {
|
|
2356
|
+
for (const dep of registryItem.componentsDependencies) {
|
|
2357
|
+
queue.push(dep);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
857
2360
|
}
|
|
858
|
-
return { framework: "Next.js (Pages Router)", componentsDir: "components/ui" };
|
|
859
2361
|
}
|
|
860
|
-
if (
|
|
861
|
-
|
|
862
|
-
|
|
2362
|
+
if (invalidComponents.length > 0) {
|
|
2363
|
+
console.error(`\x1B[31mError: Component(s) not found in registry: ${invalidComponents.join(", ")}\x1B[0m`);
|
|
2364
|
+
console.log("Run \x1B[32mnpx nexoreui list\x1B[0m to see all available components.");
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
const defaultComponentsDir = customComponentsDir || (project.hasSrcDir ? "src/components/ui" : "components/ui");
|
|
2368
|
+
const defaultUtilsFile = customUtilsFile || (project.hasSrcDir ? "src/lib/utils.ts" : "lib/utils.ts");
|
|
2369
|
+
let componentsDirInput = defaultComponentsDir;
|
|
2370
|
+
let utilsFileInput = defaultUtilsFile;
|
|
2371
|
+
if (!options.yes && !customComponentsDir) {
|
|
2372
|
+
const compPrompt = await askQuestion(`Where would you like to install the components? (default: ${defaultComponentsDir}): `);
|
|
2373
|
+
componentsDirInput = compPrompt.trim() || defaultComponentsDir;
|
|
2374
|
+
const utilsPrompt = await askQuestion(`Where should we create the utilities file (cn helper)? (default: ${defaultUtilsFile}): `);
|
|
2375
|
+
utilsFileInput = utilsPrompt.trim() || defaultUtilsFile;
|
|
2376
|
+
}
|
|
2377
|
+
const absoluteComponentsDir = path3.resolve(project.baseDir, componentsDirInput);
|
|
2378
|
+
const absoluteUtilsFile = path3.resolve(project.baseDir, utilsFileInput);
|
|
2379
|
+
console.log(`\x1B[33mInstalling components to:\x1B[0m ${absoluteComponentsDir}`);
|
|
2380
|
+
console.log(`\x1B[33mUsing cn helper from:\x1B[0m ${absoluteUtilsFile}
|
|
2381
|
+
`);
|
|
2382
|
+
ensureDir(absoluteComponentsDir);
|
|
2383
|
+
const didCreateCn = ensureCnUtil(absoluteUtilsFile);
|
|
2384
|
+
if (didCreateCn) {
|
|
2385
|
+
console.log(`\x1B[32m\u2714 Created utilities file (cn helper) at:\x1B[0m ${utilsFileInput}`);
|
|
2386
|
+
}
|
|
2387
|
+
const npmDependencies = /* @__PURE__ */ new Set();
|
|
2388
|
+
npmDependencies.add("clsx");
|
|
2389
|
+
npmDependencies.add("tailwind-merge");
|
|
2390
|
+
npmDependencies.add("lucide-react");
|
|
2391
|
+
npmDependencies.add("framer-motion");
|
|
2392
|
+
for (const compName of componentsToInstall) {
|
|
2393
|
+
const registryItem = registry[compName];
|
|
2394
|
+
const targetPath = path3.join(absoluteComponentsDir, registryItem.fileName);
|
|
2395
|
+
copyComponentFile(registryItem.content, targetPath, absoluteUtilsFile);
|
|
2396
|
+
console.log(`\x1B[32m\u2714 Added component:\x1B[0m ${compName} -> ${path3.join(componentsDirInput, registryItem.fileName)}`);
|
|
2397
|
+
registryItem.dependencies.forEach((dep) => npmDependencies.add(dep));
|
|
2398
|
+
}
|
|
2399
|
+
const depsArray = Array.from(npmDependencies);
|
|
2400
|
+
let depsToInstall = [...depsArray];
|
|
2401
|
+
try {
|
|
2402
|
+
const packageJsonPath = path3.join(project.baseDir, "package.json");
|
|
2403
|
+
if (fs3.existsSync(packageJsonPath)) {
|
|
2404
|
+
const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf8"));
|
|
2405
|
+
const existingDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
2406
|
+
depsToInstall = depsArray.filter((dep) => !existingDeps[dep]);
|
|
863
2407
|
}
|
|
864
|
-
|
|
2408
|
+
} catch {
|
|
865
2409
|
}
|
|
866
|
-
if (
|
|
867
|
-
|
|
2410
|
+
if (depsToInstall.length > 0) {
|
|
2411
|
+
console.log(`
|
|
2412
|
+
\x1B[33mInstalling external dependencies:\x1B[0m ${depsToInstall.join(", ")}...`);
|
|
2413
|
+
let installCmd = "npm install";
|
|
2414
|
+
if (project.packageManager === "pnpm") installCmd = "pnpm add";
|
|
2415
|
+
else if (project.packageManager === "yarn") installCmd = "yarn add";
|
|
2416
|
+
else if (project.packageManager === "bun") installCmd = "bun add";
|
|
2417
|
+
try {
|
|
2418
|
+
(0, import_child_process.execSync)(`${installCmd} ${depsToInstall.join(" ")}`, {
|
|
2419
|
+
stdio: "inherit",
|
|
2420
|
+
cwd: project.baseDir
|
|
2421
|
+
});
|
|
2422
|
+
console.log("\x1B[32m\u2714 Dependencies installed successfully!\x1B[0m");
|
|
2423
|
+
} catch {
|
|
2424
|
+
console.error("\x1B[31mFailed to install dependencies automatically. Please run:\x1B[0m");
|
|
2425
|
+
console.log(` ${installCmd} ${depsToInstall.join(" ")}`);
|
|
2426
|
+
}
|
|
868
2427
|
}
|
|
869
|
-
|
|
2428
|
+
console.log(`
|
|
2429
|
+
\x1B[32m\x1B[1m\u{1F389} Done! ${componentsToInstall.size} NexoreUI component(s) ready to use.\x1B[0m
|
|
2430
|
+
`);
|
|
870
2431
|
}
|
|
871
2432
|
|
|
872
|
-
// src/
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
2433
|
+
// src/commands/list.ts
|
|
2434
|
+
function listCommand() {
|
|
2435
|
+
console.log("\n\x1B[34m\x1B[1m=== Available NexoreUI Components ===\x1B[0m\n");
|
|
2436
|
+
Object.keys(registry).forEach((name) => {
|
|
2437
|
+
const item = registry[name];
|
|
2438
|
+
console.log(`- \x1B[32m\x1B[1m${name}\x1B[0m (${item.fileName})`);
|
|
2439
|
+
if (item.dependencies.length > 0) {
|
|
2440
|
+
console.log(` \x1B[90mDependencies: ${item.dependencies.join(", ")}\x1B[0m`);
|
|
2441
|
+
}
|
|
2442
|
+
if (item.componentsDependencies && item.componentsDependencies.length > 0) {
|
|
2443
|
+
console.log(` \x1B[33mRequires component: ${item.componentsDependencies.join(", ")}\x1B[0m`);
|
|
2444
|
+
}
|
|
2445
|
+
console.log("");
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// src/commands/init.ts
|
|
2450
|
+
var fs5 = __toESM(require("fs"));
|
|
2451
|
+
var path5 = __toESM(require("path"));
|
|
2452
|
+
var readline2 = __toESM(require("readline"));
|
|
2453
|
+
|
|
2454
|
+
// src/utils/config.ts
|
|
2455
|
+
var fs4 = __toESM(require("fs"));
|
|
2456
|
+
var path4 = __toESM(require("path"));
|
|
2457
|
+
var import_child_process2 = require("child_process");
|
|
2458
|
+
var THEME_PALETTES = {
|
|
2459
|
+
indigo: { light: "hsl(250 85% 50%)", dark: "hsl(250 85% 65%)", rgb: "99 60 220" },
|
|
2460
|
+
violet: { light: "hsl(262.1 83.3% 57.8%)", dark: "hsl(263.4 70% 50.4%)", rgb: "139 92 246" },
|
|
2461
|
+
emerald: { light: "hsl(142.1 76.2% 36.3%)", dark: "hsl(142.1 70.6% 45.3%)", rgb: "16 185 129" },
|
|
2462
|
+
rose: { light: "hsl(346.8 77.2% 49.8%)", dark: "hsl(346.8 77.2% 55%)", rgb: "244 63 94" },
|
|
2463
|
+
amber: { light: "hsl(37.7 92.1% 50.2%)", dark: "hsl(37.7 92.1% 55%)", rgb: "245 158 11" },
|
|
2464
|
+
cyan: { light: "hsl(190.4 95% 39%)", dark: "hsl(188.7 94.5% 42.7%)", rgb: "6 182 212" },
|
|
2465
|
+
slate: { light: "hsl(240 5.9% 10%)", dark: "hsl(0 0% 98%)", rgb: "244 244 245" },
|
|
2466
|
+
neon: { light: "hsl(173 80% 40%)", dark: "hsl(173 100% 50%)", rgb: "0 255 220" }
|
|
2467
|
+
};
|
|
2468
|
+
function ensurePathAlias(baseDir, projectType, hasSrcDir) {
|
|
2469
|
+
let updated = false;
|
|
2470
|
+
const tsConfigPath = path4.join(baseDir, "tsconfig.json");
|
|
2471
|
+
const jsConfigPath = path4.join(baseDir, "jsconfig.json");
|
|
2472
|
+
const targetConfig = fs4.existsSync(tsConfigPath) ? tsConfigPath : fs4.existsSync(jsConfigPath) ? jsConfigPath : null;
|
|
2473
|
+
if (targetConfig) {
|
|
2474
|
+
try {
|
|
2475
|
+
const content = fs4.readFileSync(targetConfig, "utf8");
|
|
2476
|
+
const parsed = JSON.parse(content);
|
|
2477
|
+
parsed.compilerOptions = parsed.compilerOptions || {};
|
|
2478
|
+
parsed.compilerOptions.baseUrl = parsed.compilerOptions.baseUrl || ".";
|
|
2479
|
+
parsed.compilerOptions.paths = parsed.compilerOptions.paths || {};
|
|
2480
|
+
const aliasTarget = hasSrcDir ? ["./src/*"] : ["./*"];
|
|
2481
|
+
if (!parsed.compilerOptions.paths["@/*"]) {
|
|
2482
|
+
parsed.compilerOptions.paths["@/*"] = aliasTarget;
|
|
2483
|
+
fs4.writeFileSync(targetConfig, JSON.stringify(parsed, null, 2), "utf8");
|
|
2484
|
+
updated = true;
|
|
2485
|
+
}
|
|
2486
|
+
} catch {
|
|
2487
|
+
}
|
|
880
2488
|
}
|
|
881
|
-
|
|
2489
|
+
if (projectType === "vite") {
|
|
2490
|
+
const viteConfigFiles = ["vite.config.ts", "vite.config.js", "vite.config.mjs"];
|
|
2491
|
+
for (const fileName of viteConfigFiles) {
|
|
2492
|
+
const vitePath = path4.join(baseDir, fileName);
|
|
2493
|
+
if (fs4.existsSync(vitePath)) {
|
|
2494
|
+
let viteContent = fs4.readFileSync(vitePath, "utf8");
|
|
2495
|
+
if (!viteContent.includes("alias") && !viteContent.includes("'@'")) {
|
|
2496
|
+
const hasPathImport = viteContent.includes("from 'path'") || viteContent.includes('from "path"');
|
|
2497
|
+
let headerAdditions = "";
|
|
2498
|
+
if (!hasPathImport) {
|
|
2499
|
+
headerAdditions += `import path from 'path'
|
|
2500
|
+
import { fileURLToPath } from 'url'
|
|
2501
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
2502
|
+
`;
|
|
2503
|
+
}
|
|
2504
|
+
if (viteContent.includes("defineConfig({")) {
|
|
2505
|
+
viteContent = headerAdditions + viteContent.replace(
|
|
2506
|
+
"defineConfig({",
|
|
2507
|
+
`defineConfig({
|
|
2508
|
+
resolve: {
|
|
2509
|
+
alias: {
|
|
2510
|
+
'@': path.resolve(__dirname, './${hasSrcDir ? "src" : "."}'),
|
|
2511
|
+
},
|
|
2512
|
+
},`
|
|
2513
|
+
);
|
|
2514
|
+
fs4.writeFileSync(vitePath, viteContent, "utf8");
|
|
2515
|
+
updated = true;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
break;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return updated;
|
|
2523
|
+
}
|
|
2524
|
+
function injectThemeCss(baseDir, cssRelativePath, themeName, radiusValue) {
|
|
2525
|
+
const cssAbsolutePath = path4.join(baseDir, cssRelativePath);
|
|
2526
|
+
const palette = THEME_PALETTES[themeName] || THEME_PALETTES.cyan;
|
|
2527
|
+
const radius = typeof radiusValue === "number" ? radiusValue : parseFloat(radiusValue) || 1;
|
|
2528
|
+
const themeBlock = `
|
|
2529
|
+
@source "../node_modules/nexoreui/dist/**/*.{js,mjs}";
|
|
2530
|
+
|
|
2531
|
+
@theme {
|
|
2532
|
+
--color-background: var(--background);
|
|
2533
|
+
--color-foreground: var(--foreground);
|
|
2534
|
+
--color-card: var(--card);
|
|
2535
|
+
--color-card-foreground: var(--card-foreground);
|
|
2536
|
+
--color-primary: var(--primary);
|
|
2537
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
2538
|
+
--color-border: var(--border);
|
|
2539
|
+
--radius-lg: var(--radius);
|
|
2540
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
2541
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
2542
|
+
--font-sans: system-ui, -apple-system, sans-serif;
|
|
882
2543
|
}
|
|
883
2544
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
2545
|
+
:root {
|
|
2546
|
+
--background: hsl(0 0% 100%);
|
|
2547
|
+
--foreground: hsl(240 10% 3.9%);
|
|
2548
|
+
--card: hsl(0 0% 100%);
|
|
2549
|
+
--card-foreground: hsl(240 10% 3.9%);
|
|
2550
|
+
--primary: ${palette.light};
|
|
2551
|
+
--primary-foreground: hsl(0 0% 100%);
|
|
2552
|
+
--border: hsl(240 5.9% 90%);
|
|
2553
|
+
--radius: ${radius}rem;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
.dark {
|
|
2557
|
+
--background: hsl(240 10% 3.9%);
|
|
2558
|
+
--foreground: hsl(0 0% 98%);
|
|
2559
|
+
--card: hsl(240 10% 3.9%);
|
|
2560
|
+
--card-foreground: hsl(0 0% 98%);
|
|
2561
|
+
--primary: ${palette.dark};
|
|
2562
|
+
--primary-foreground: hsl(0 0% 100%);
|
|
2563
|
+
--border: hsl(240 3.7% 15.9%);
|
|
2564
|
+
--radius: ${radius}rem;
|
|
2565
|
+
}
|
|
2566
|
+
`;
|
|
2567
|
+
if (fs4.existsSync(cssAbsolutePath)) {
|
|
2568
|
+
const existingContent = fs4.readFileSync(cssAbsolutePath, "utf8");
|
|
2569
|
+
if (!existingContent.includes("--color-primary") && !existingContent.includes("nexoreui/dist")) {
|
|
2570
|
+
fs4.writeFileSync(cssAbsolutePath, existingContent.trim() + "\n" + themeBlock, "utf8");
|
|
2571
|
+
return true;
|
|
2572
|
+
}
|
|
2573
|
+
} else {
|
|
2574
|
+
const cssDir = path4.dirname(cssAbsolutePath);
|
|
2575
|
+
if (!fs4.existsSync(cssDir)) fs4.mkdirSync(cssDir, { recursive: true });
|
|
2576
|
+
fs4.writeFileSync(cssAbsolutePath, `@import "tailwindcss";
|
|
2577
|
+
` + themeBlock, "utf8");
|
|
2578
|
+
return true;
|
|
891
2579
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
2580
|
+
return false;
|
|
2581
|
+
}
|
|
2582
|
+
function installPeerDependencies(baseDir, packageManager, dependencies = ["clsx", "tailwind-merge", "lucide-react", "framer-motion"]) {
|
|
2583
|
+
try {
|
|
2584
|
+
const packageJsonPath = path4.join(baseDir, "package.json");
|
|
2585
|
+
let missingDeps = [...dependencies];
|
|
2586
|
+
if (fs4.existsSync(packageJsonPath)) {
|
|
2587
|
+
const pkg = JSON.parse(fs4.readFileSync(packageJsonPath, "utf8"));
|
|
2588
|
+
const installed = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2589
|
+
missingDeps = dependencies.filter((dep) => !installed[dep]);
|
|
2590
|
+
}
|
|
2591
|
+
if (missingDeps.length === 0) return true;
|
|
2592
|
+
let installCmd = "npm install";
|
|
2593
|
+
if (packageManager === "pnpm") installCmd = "pnpm add";
|
|
2594
|
+
else if (packageManager === "yarn") installCmd = "yarn add";
|
|
2595
|
+
else if (packageManager === "bun") installCmd = "bun add";
|
|
2596
|
+
console.log(`
|
|
2597
|
+
\x1B[33m\u26A1 Installing peer dependencies:\x1B[0m ${missingDeps.join(", ")}...`);
|
|
2598
|
+
(0, import_child_process2.execSync)(`${installCmd} ${missingDeps.join(" ")}`, {
|
|
2599
|
+
stdio: "inherit",
|
|
2600
|
+
cwd: baseDir
|
|
2601
|
+
});
|
|
2602
|
+
return true;
|
|
2603
|
+
} catch (err) {
|
|
2604
|
+
console.warn("\x1B[33mWarning: Automatic peer dependency installation skipped.\x1B[0m");
|
|
2605
|
+
return false;
|
|
897
2606
|
}
|
|
898
|
-
|
|
899
|
-
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
// src/commands/init.ts
|
|
2610
|
+
function askQuestion2(query) {
|
|
2611
|
+
const rl = readline2.createInterface({
|
|
2612
|
+
input: process.stdin,
|
|
2613
|
+
output: process.stdout
|
|
2614
|
+
});
|
|
2615
|
+
return new Promise(
|
|
2616
|
+
(resolve4) => rl.question(query, (ans) => {
|
|
2617
|
+
rl.close();
|
|
2618
|
+
resolve4(ans);
|
|
2619
|
+
})
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2622
|
+
async function initCommand(options = {}) {
|
|
900
2623
|
console.log(`
|
|
901
|
-
\x1B[
|
|
902
|
-
console.log(`Detected: \x1B[36m${projectInfo.framework}\x1B[0m project`);
|
|
903
|
-
console.log(`Output: \x1B[36m${outputDir}\x1B[0m
|
|
2624
|
+
\x1B[36m\x1B[1m=== Initializing NexoreUI in your project ===\x1B[0m
|
|
904
2625
|
`);
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
let
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
2626
|
+
const project = detectProject(process.cwd());
|
|
2627
|
+
console.log(`\x1B[32m\u2714 Detected Project:\x1B[0m ${project.projectType.toUpperCase()} (${project.packageManager})`);
|
|
2628
|
+
let theme = options.theme || "cyan";
|
|
2629
|
+
let radius = options.radius || "1.0";
|
|
2630
|
+
const defaultComponentsDir = project.hasSrcDir ? "src/components/ui" : "components/ui";
|
|
2631
|
+
const defaultUtilsFile = project.hasSrcDir ? "src/lib/utils.ts" : "lib/utils.ts";
|
|
2632
|
+
const defaultCssFile = project.projectType === "next" ? project.hasSrcDir ? "src/app/globals.css" : "app/globals.css" : project.hasSrcDir ? "src/index.css" : "src/index.css";
|
|
2633
|
+
let componentsDir = defaultComponentsDir;
|
|
2634
|
+
let utilsFile = defaultUtilsFile;
|
|
2635
|
+
if (!options.yes) {
|
|
2636
|
+
if (!options.theme) {
|
|
2637
|
+
const themeAns = await askQuestion2(`Which color theme would you like to use? (cyan, indigo, violet, emerald, rose, amber, slate, neon) [default: cyan]: `);
|
|
2638
|
+
if (themeAns.trim() && THEME_PALETTES[themeAns.trim().toLowerCase()]) {
|
|
2639
|
+
theme = themeAns.trim().toLowerCase();
|
|
2640
|
+
}
|
|
918
2641
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
entry.dependencies.forEach((d) => allDeps.add(d));
|
|
2642
|
+
if (!options.radius) {
|
|
2643
|
+
const radiusAns = await askQuestion2(`Which radius value would you like to use? (0, 0.3, 0.5, 0.75, 1.0) [default: 1.0]: `);
|
|
2644
|
+
if (radiusAns.trim()) {
|
|
2645
|
+
radius = radiusAns.trim();
|
|
2646
|
+
}
|
|
925
2647
|
}
|
|
2648
|
+
const compAns = await askQuestion2(`Where should UI components be created? (default: ${defaultComponentsDir}): `);
|
|
2649
|
+
if (compAns.trim()) componentsDir = compAns.trim();
|
|
2650
|
+
const utilsAns = await askQuestion2(`Where should utility functions (cn helper) be placed? (default: ${defaultUtilsFile}): `);
|
|
2651
|
+
if (utilsAns.trim()) utilsFile = utilsAns.trim();
|
|
2652
|
+
}
|
|
2653
|
+
const absoluteComponentsDir = path5.resolve(project.baseDir, componentsDir);
|
|
2654
|
+
const absoluteUtilsFile = path5.resolve(project.baseDir, utilsFile);
|
|
2655
|
+
ensureDir(absoluteComponentsDir);
|
|
2656
|
+
ensureCnUtil(absoluteUtilsFile);
|
|
2657
|
+
const didUpdateAlias = ensurePathAlias(project.baseDir, project.projectType, project.hasSrcDir);
|
|
2658
|
+
if (didUpdateAlias) {
|
|
2659
|
+
console.log(`\x1B[32m\u2714\x1B[0m Configured path alias \x1B[1m'@/*'\x1B[0m in project config`);
|
|
926
2660
|
}
|
|
2661
|
+
const didInjectCss = injectThemeCss(project.baseDir, defaultCssFile, theme, radius);
|
|
2662
|
+
if (didInjectCss) {
|
|
2663
|
+
console.log(`\x1B[32m\u2714\x1B[0m Injected Tailwind CSS v4 @theme tokens into \x1B[1m${defaultCssFile}\x1B[0m`);
|
|
2664
|
+
}
|
|
2665
|
+
installPeerDependencies(project.baseDir, project.packageManager);
|
|
2666
|
+
const config = {
|
|
2667
|
+
$schema: "https://nexoreui.site/schema.json",
|
|
2668
|
+
style: "default",
|
|
2669
|
+
theme,
|
|
2670
|
+
radius: Number(radius),
|
|
2671
|
+
framework: project.projectType,
|
|
2672
|
+
packageManager: project.packageManager,
|
|
2673
|
+
font: "system",
|
|
2674
|
+
density: "default",
|
|
2675
|
+
animation: "energetic",
|
|
2676
|
+
defaultMode: "light",
|
|
2677
|
+
tailwind: {
|
|
2678
|
+
config: "tailwind.config.js",
|
|
2679
|
+
css: defaultCssFile,
|
|
2680
|
+
baseColor: "zinc",
|
|
2681
|
+
cssVariables: true
|
|
2682
|
+
},
|
|
2683
|
+
aliases: {
|
|
2684
|
+
components: `@/${componentsDir.replace(/^src\//, "")}`,
|
|
2685
|
+
utils: `@/${utilsFile.replace(/^src\//, "").replace(/\.(ts|js)$/, "")}`
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
const configPath = path5.join(project.baseDir, "nexore.json");
|
|
2689
|
+
fs5.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
2690
|
+
console.log(`\x1B[32m\u2714\x1B[0m Generated \x1B[1mnexore.json\x1B[0m (Theme: ${theme}, Radius: ${radius}rem)`);
|
|
2691
|
+
console.log(`\x1B[32m\u2714\x1B[0m Utilities ready at \x1B[1m${utilsFile}\x1B[0m`);
|
|
2692
|
+
console.log(`\x1B[32m\u2714\x1B[0m Components directory ready at \x1B[1m${componentsDir}\x1B[0m`);
|
|
927
2693
|
console.log(`
|
|
928
|
-
\x1B[32m
|
|
929
|
-
|
|
930
|
-
console.log(`
|
|
931
|
-
Install peer dependencies:`);
|
|
932
|
-
console.log(` \x1B[36mpnpm add ${Array.from(allDeps).join(" ")}\x1B[0m
|
|
2694
|
+
\x1B[32m\x1B[1m\u{1F389} NexoreUI initialized successfully! You can now add components:\x1B[0m`);
|
|
2695
|
+
console.log(` \x1B[36mnpx nexoreui add button card modal table --all\x1B[0m
|
|
933
2696
|
`);
|
|
934
|
-
}
|
|
935
2697
|
}
|
|
936
2698
|
|
|
937
|
-
// src/commands/
|
|
938
|
-
|
|
939
|
-
|
|
2699
|
+
// src/commands/create.ts
|
|
2700
|
+
var fs6 = __toESM(require("fs"));
|
|
2701
|
+
var path6 = __toESM(require("path"));
|
|
2702
|
+
var import_child_process3 = require("child_process");
|
|
2703
|
+
async function createCommand(projectName, options = {}) {
|
|
2704
|
+
const name = projectName || "my-nexore-app";
|
|
2705
|
+
const targetDir = path6.resolve(process.cwd(), name);
|
|
940
2706
|
console.log(`
|
|
941
|
-
\x1B[
|
|
2707
|
+
\x1B[36m\x1B[1m\u{1F680} Creating a new NexoreUI Project:\x1B[0m \x1B[32m${name}\x1B[0m
|
|
942
2708
|
`);
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
2709
|
+
if (fs6.existsSync(targetDir) && fs6.readdirSync(targetDir).length > 0) {
|
|
2710
|
+
console.error(`\x1B[31mError: Target directory ${name} already exists and is not empty.\x1B[0m`);
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
console.log(`\x1B[33m\u26A1 Scaffolding React + Vite + Tailwind CSS template...\x1B[0m`);
|
|
2714
|
+
try {
|
|
2715
|
+
(0, import_child_process3.execSync)(`npm create vite@latest ${name} -- --template react-ts`, { stdio: "inherit" });
|
|
2716
|
+
} catch (err) {
|
|
2717
|
+
console.error(`\x1B[31mFailed to scaffold Vite project.\x1B[0m`);
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
process.chdir(targetDir);
|
|
2721
|
+
console.log(`
|
|
2722
|
+
\x1B[33m\u{1F4E6} Installing NexoreUI, Tailwind CSS, and core packages...\x1B[0m`);
|
|
2723
|
+
(0, import_child_process3.execSync)(`npm install nexoreui lucide-react clsx tailwind-merge framer-motion @tailwindcss/vite tailwindcss`, {
|
|
2724
|
+
stdio: "inherit"
|
|
946
2725
|
});
|
|
2726
|
+
await initCommand({
|
|
2727
|
+
yes: true,
|
|
2728
|
+
theme: options.theme || "cyan",
|
|
2729
|
+
radius: options.radius || "1.0"
|
|
2730
|
+
});
|
|
2731
|
+
console.log(`
|
|
2732
|
+
\x1B[32m\x1B[1m\u2728 Project ${name} is ready!\x1B[0m`);
|
|
947
2733
|
console.log(`
|
|
948
|
-
|
|
949
|
-
|
|
2734
|
+
To get started:
|
|
2735
|
+
`);
|
|
2736
|
+
console.log(` \x1B[36mcd ${name}\x1B[0m`);
|
|
2737
|
+
console.log(` \x1B[36mnpx nexoreui add button card modal table --all\x1B[0m`);
|
|
2738
|
+
console.log(` \x1B[36mnpm run dev\x1B[0m
|
|
950
2739
|
`);
|
|
951
2740
|
}
|
|
952
2741
|
|
|
@@ -958,20 +2747,59 @@ async function main() {
|
|
|
958
2747
|
printHelp();
|
|
959
2748
|
return;
|
|
960
2749
|
}
|
|
961
|
-
if (command === "
|
|
2750
|
+
if (command === "create") {
|
|
2751
|
+
const projectName = args[1] && !args[1].startsWith("-") ? args[1] : void 0;
|
|
2752
|
+
let theme;
|
|
2753
|
+
let radius;
|
|
2754
|
+
for (let i = 1; i < args.length; i++) {
|
|
2755
|
+
const arg = args[i];
|
|
2756
|
+
if (arg === "--theme" && args[i + 1]) {
|
|
2757
|
+
theme = args[++i];
|
|
2758
|
+
} else if (arg.startsWith("--theme=")) {
|
|
2759
|
+
theme = arg.split("=")[1];
|
|
2760
|
+
} else if (arg === "--radius" && args[i + 1]) {
|
|
2761
|
+
radius = args[++i];
|
|
2762
|
+
} else if (arg.startsWith("--radius=")) {
|
|
2763
|
+
radius = arg.split("=")[1];
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
await createCommand(projectName, { theme, radius });
|
|
2767
|
+
} else if (command === "init") {
|
|
2768
|
+
let yes = false;
|
|
2769
|
+
let theme;
|
|
2770
|
+
let radius;
|
|
2771
|
+
for (let i = 1; i < args.length; i++) {
|
|
2772
|
+
const arg = args[i];
|
|
2773
|
+
if (arg === "-y" || arg === "--yes") {
|
|
2774
|
+
yes = true;
|
|
2775
|
+
} else if (arg === "--theme" && args[i + 1]) {
|
|
2776
|
+
theme = args[++i];
|
|
2777
|
+
} else if (arg.startsWith("--theme=")) {
|
|
2778
|
+
theme = arg.split("=")[1];
|
|
2779
|
+
} else if (arg === "--radius" && args[i + 1]) {
|
|
2780
|
+
radius = args[++i];
|
|
2781
|
+
} else if (arg.startsWith("--radius=")) {
|
|
2782
|
+
radius = arg.split("=")[1];
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
await initCommand({ yes, theme, radius });
|
|
2786
|
+
} else if (command === "list") {
|
|
962
2787
|
listCommand();
|
|
963
2788
|
} else if (command === "add") {
|
|
964
2789
|
const components = [];
|
|
965
2790
|
let yes = false;
|
|
2791
|
+
let all = false;
|
|
966
2792
|
for (let i = 1; i < args.length; i++) {
|
|
967
2793
|
const arg = args[i];
|
|
968
2794
|
if (arg === "-y" || arg === "--yes") {
|
|
969
2795
|
yes = true;
|
|
2796
|
+
} else if (arg === "--all" || arg === "-a") {
|
|
2797
|
+
all = true;
|
|
970
2798
|
} else if (!arg.startsWith("-")) {
|
|
971
2799
|
components.push(arg);
|
|
972
2800
|
}
|
|
973
2801
|
}
|
|
974
|
-
await addCommand(components, { yes });
|
|
2802
|
+
await addCommand(components, { yes, all });
|
|
975
2803
|
} else {
|
|
976
2804
|
console.error(`\x1B[31mUnknown command: ${command}\x1B[0m`);
|
|
977
2805
|
printHelp();
|
|
@@ -979,16 +2807,23 @@ async function main() {
|
|
|
979
2807
|
}
|
|
980
2808
|
function printHelp() {
|
|
981
2809
|
console.log(`
|
|
982
|
-
\x1B[
|
|
2810
|
+
\x1B[36m\x1B[1mNexoreUI CLI\x1B[0m
|
|
2811
|
+
\x1B[90mModern, animated, production-ready React components with Tailwind CSS v4\x1B[0m
|
|
2812
|
+
|
|
983
2813
|
Usage:
|
|
984
2814
|
npx nexoreui [command] [options]
|
|
985
2815
|
|
|
986
2816
|
Commands:
|
|
987
|
-
\x1B[
|
|
988
|
-
\x1B[
|
|
2817
|
+
\x1B[32mcreate [name]\x1B[0m Create a new fully configured NexoreUI starter project
|
|
2818
|
+
\x1B[32minit\x1B[0m Initialize NexoreUI in your project (configure theme, aliases, and CSS)
|
|
2819
|
+
\x1B[32madd [components...]\x1B[0m Add components to your project (use --all to install all 40+ components)
|
|
2820
|
+
\x1B[32mlist\x1B[0m List all available components in registry
|
|
989
2821
|
|
|
990
2822
|
Options:
|
|
991
|
-
\x1B[33m
|
|
2823
|
+
\x1B[33m--theme <name>\x1B[0m Set color palette (cyan, indigo, violet, emerald, rose, amber, slate, neon)
|
|
2824
|
+
\x1B[33m--radius <val>\x1B[0m Set border radius (0, 0.3, 0.5, 0.75, 1.0)
|
|
2825
|
+
\x1B[33m--all, -a\x1B[0m Install all available components at once
|
|
2826
|
+
\x1B[33m-y, --yes\x1B[0m Skip prompts and use defaults automatically
|
|
992
2827
|
\x1B[33m-h, --help\x1B[0m Show help information
|
|
993
2828
|
`);
|
|
994
2829
|
}
|