nexoreui-cli 0.1.0 → 0.1.1
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/LICENSE +21 -0
- package/dist/index.js +1509 -200
- package/dist/index.js.map +1 -1
- package/package.json +7 -8
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,185 @@ 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
1938
|
export { Badge, badgeVariants }
|
|
1939
|
+
|
|
1940
|
+
// ----------------------------------------------------
|
|
1941
|
+
// Consolidated Badge Components
|
|
1942
|
+
// ----------------------------------------------------
|
|
1943
|
+
|
|
1944
|
+
export const GlowBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1945
|
+
<Badge variant="neon" className={className} {...props}>{children}</Badge>
|
|
1946
|
+
)
|
|
1947
|
+
|
|
1948
|
+
export const GlassBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1949
|
+
<Badge variant="outline" className={cn("backdrop-blur-md bg-white/10 dark:bg-black/20 border-white/20 dark:border-white/10", className)} {...props}>{children}</Badge>
|
|
1950
|
+
)
|
|
1951
|
+
|
|
1952
|
+
export const DotBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1953
|
+
<Badge dot={true} className={className} {...props}>{children}</Badge>
|
|
1954
|
+
)
|
|
1955
|
+
|
|
1956
|
+
export const GradientBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1957
|
+
<Badge variant="gradient" className={className} {...props}>{children}</Badge>
|
|
1958
|
+
)
|
|
1959
|
+
|
|
1960
|
+
export const OutlineGlowBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1961
|
+
<Badge variant="neon" className={cn("bg-transparent border border-purple-500/50", className)} {...props}>{children}</Badge>
|
|
1962
|
+
)
|
|
1963
|
+
|
|
1964
|
+
export const PulseBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1965
|
+
<Badge pulse={true} className={className} {...props}>{children}</Badge>
|
|
1966
|
+
)
|
|
1967
|
+
|
|
1968
|
+
export const SoftBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1969
|
+
<Badge variant="secondary" className={className} {...props}>{children}</Badge>
|
|
1970
|
+
)
|
|
1971
|
+
|
|
1972
|
+
export const TagBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1973
|
+
<Badge className={cn("rounded-md", className)} {...props}>{children}</Badge>
|
|
1974
|
+
)
|
|
1975
|
+
|
|
1976
|
+
export const PremiumBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1977
|
+
<Badge variant="gradient" className={cn("bg-gradient-to-r from-yellow-500 via-amber-500 to-orange-500", className)} {...props}>{children}</Badge>
|
|
1978
|
+
)
|
|
1979
|
+
|
|
1980
|
+
export const MinimalBadge = ({ children, className, ...props }: BadgeProps) => (
|
|
1981
|
+
<Badge size="sm" variant="outline" className={className} {...props}>{children}</Badge>
|
|
1982
|
+
)
|
|
1983
|
+
|
|
1984
|
+
export const NotificationBadge = ({ count, className }: { count: number; className?: string }) => (
|
|
1985
|
+
<div className={cn("flex h-5 min-w-[20px] px-1 items-center justify-center rounded-full bg-destructive text-[10px] font-bold text-destructive-foreground shadow-sm", className)}>
|
|
1986
|
+
{count}
|
|
1987
|
+
</div>
|
|
1988
|
+
)
|
|
1989
|
+
|
|
1990
|
+
export const RibbonBadge = ({ children, text, className }: { children?: React.ReactNode; text?: string; className?: string }) => (
|
|
1991
|
+
<div className={cn("absolute top-0 right-0 bg-primary text-primary-foreground text-[10px] font-bold px-3 py-1 rounded-bl-xl uppercase tracking-wider", className)}>
|
|
1992
|
+
{children || text}
|
|
1993
|
+
</div>
|
|
1994
|
+
)
|
|
1995
|
+
|
|
1996
|
+
export interface OutlineDotBadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
1997
|
+
status?: string;
|
|
1998
|
+
text?: string;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
export const OutlineDotBadge = ({ children, className, status, text, ...props }: OutlineDotBadgeProps) => {
|
|
2002
|
+
const statusColors: Record<string, string> = {
|
|
2003
|
+
online: "bg-emerald-500",
|
|
2004
|
+
offline: "bg-muted-foreground/50",
|
|
2005
|
+
away: "bg-amber-500",
|
|
2006
|
+
busy: "bg-red-500",
|
|
2007
|
+
}
|
|
2008
|
+
const dotColor = status ? statusColors[status] || "bg-emerald-500" : "bg-primary"
|
|
2009
|
+
return (
|
|
2010
|
+
<Badge variant="outline" className={className} {...props}>
|
|
2011
|
+
<span className={cn("h-1.5 w-1.5 rounded-full mr-1.5", dotColor)} />
|
|
2012
|
+
{children || text}
|
|
2013
|
+
</Badge>
|
|
2014
|
+
)
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
export interface GradientOutlineBadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
2018
|
+
text?: string;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
export const GradientOutlineBadge = ({ children, text, className, ...props }: GradientOutlineBadgeProps) => (
|
|
2022
|
+
<Badge className={cn("p-[1px] bg-gradient-to-r from-violet-500 to-pink-500 rounded-full border-0", className)} {...props}>
|
|
2023
|
+
<div className="bg-background text-foreground rounded-full px-2.5 py-0.5 text-xs font-semibold">
|
|
2024
|
+
{children || text}
|
|
2025
|
+
</div>
|
|
2026
|
+
</Badge>
|
|
2027
|
+
)
|
|
2028
|
+
|
|
2029
|
+
export interface IconBadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
2030
|
+
icon?: React.ReactNode;
|
|
2031
|
+
text?: string;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
export const IconBadge = ({ children, icon, text, className, ...props }: IconBadgeProps) => {
|
|
2035
|
+
const renderIcon = () => {
|
|
2036
|
+
if (!icon) return null;
|
|
2037
|
+
if (typeof icon === "string") {
|
|
2038
|
+
if (icon.toLowerCase() === "star") {
|
|
2039
|
+
return <Star className="h-3 w-3 mr-1" />
|
|
2040
|
+
}
|
|
2041
|
+
return <span className="mr-1">{icon}</span>
|
|
2042
|
+
}
|
|
2043
|
+
return <span className="mr-1">{icon}</span>
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
return (
|
|
2047
|
+
<Badge className={cn("inline-flex items-center gap-1", className)} {...props}>
|
|
2048
|
+
{renderIcon()}
|
|
2049
|
+
{children || text}
|
|
2050
|
+
</Badge>
|
|
2051
|
+
)
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
export interface FloatingBadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
2055
|
+
text?: string;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
export const FloatingBadge = ({ children, text, className, ...props }: FloatingBadgeProps) => (
|
|
2059
|
+
<Badge className={cn("absolute -top-2 -right-2 z-10 animate-[bounce_3s_infinite]", className)} {...props}>
|
|
2060
|
+
{children || text}
|
|
2061
|
+
</Badge>
|
|
2062
|
+
)
|
|
2063
|
+
|
|
2064
|
+
export interface ProgressBadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
2065
|
+
progress?: number;
|
|
2066
|
+
text?: string;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
export const ProgressBadge = ({ children, progress = 50, text, className, ...props }: ProgressBadgeProps) => (
|
|
2070
|
+
<Badge variant="outline" className={cn("relative overflow-hidden", className)} {...props}>
|
|
2071
|
+
<div className="absolute inset-y-0 left-0 bg-primary/10 transition-all duration-300" style={{ width: \`\${progress}%\` }} />
|
|
2072
|
+
<span className="relative z-10">{children || (text ? \`\${text} (\${progress}%)\` : \`\${progress}%\`)}</span>
|
|
2073
|
+
</Badge>
|
|
2074
|
+
)
|
|
2075
|
+
|
|
2076
|
+
export interface StatusRingBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
2077
|
+
status?: "success" | "error" | "warning" | "active" | string;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
export const StatusRingBadge = ({ status = "success", children, className, ...props }: StatusRingBadgeProps) => {
|
|
2081
|
+
const ringColors: Record<string, string> = {
|
|
2082
|
+
success: "border-emerald-500/30 ring-emerald-500/20 bg-emerald-500",
|
|
2083
|
+
active: "border-emerald-500/30 ring-emerald-500/20 bg-emerald-500",
|
|
2084
|
+
error: "border-red-500/30 ring-red-500/20 bg-red-500",
|
|
2085
|
+
warning: "border-amber-500/30 ring-amber-500/20 bg-amber-500",
|
|
2086
|
+
}
|
|
2087
|
+
const colorClass = ringColors[status] || ringColors.success;
|
|
2088
|
+
return (
|
|
2089
|
+
<span className={cn("relative flex h-2.5 w-2.5 rounded-full ring-4 border-2 border-transparent", colorClass, className)} {...props} />
|
|
2090
|
+
)
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
export interface NeonOutlineBadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
2094
|
+
text?: string;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
export const NeonOutlineBadge = ({ children, text, className, ...props }: NeonOutlineBadgeProps) => (
|
|
2098
|
+
<Badge variant="outline" className={cn("border-2 border-primary text-primary shadow-[0_0_10px_rgba(var(--primary-rgb),0.4)] bg-transparent", className)} {...props}>
|
|
2099
|
+
{children || text}
|
|
2100
|
+
</Badge>
|
|
2101
|
+
)
|
|
2102
|
+
|
|
2103
|
+
export interface TagLabelProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
2104
|
+
text?: string;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
export const TagLabel = ({ children, text, className, ...props }: TagLabelProps) => (
|
|
2108
|
+
<span className={cn("px-2 py-1 bg-muted text-muted-foreground rounded-md text-xs font-medium text-muted-foreground hover:bg-muted/80 hover:text-foreground cursor-pointer transition-colors before:content-['#'] before:mr-0.5 before:opacity-50", className)} {...props}>
|
|
2109
|
+
{children || text}
|
|
2110
|
+
</span>
|
|
2111
|
+
)
|
|
834
2112
|
`
|
|
835
2113
|
};
|
|
836
2114
|
|
|
@@ -843,111 +2121,142 @@ var registry = {
|
|
|
843
2121
|
badge
|
|
844
2122
|
};
|
|
845
2123
|
|
|
846
|
-
// src/utils/detect.ts
|
|
847
|
-
var fs = __toESM(require("fs"));
|
|
848
|
-
var path = __toESM(require("path"));
|
|
849
|
-
function detectProject() {
|
|
850
|
-
const cwd = process.cwd();
|
|
851
|
-
if (fs.existsSync(path.join(cwd, "next.config.ts")) || fs.existsSync(path.join(cwd, "next.config.js")) || fs.existsSync(path.join(cwd, "next.config.mjs"))) {
|
|
852
|
-
if (fs.existsSync(path.join(cwd, "app"))) {
|
|
853
|
-
return { framework: "Next.js (App Router)", componentsDir: "app/components/ui" };
|
|
854
|
-
}
|
|
855
|
-
if (fs.existsSync(path.join(cwd, "src", "app"))) {
|
|
856
|
-
return { framework: "Next.js (App Router)", componentsDir: "src/app/components/ui" };
|
|
857
|
-
}
|
|
858
|
-
return { framework: "Next.js (Pages Router)", componentsDir: "components/ui" };
|
|
859
|
-
}
|
|
860
|
-
if (fs.existsSync(path.join(cwd, "vite.config.ts")) || fs.existsSync(path.join(cwd, "vite.config.js"))) {
|
|
861
|
-
if (fs.existsSync(path.join(cwd, "src"))) {
|
|
862
|
-
return { framework: "Vite", componentsDir: "src/components/ui" };
|
|
863
|
-
}
|
|
864
|
-
return { framework: "Vite", componentsDir: "components/ui" };
|
|
865
|
-
}
|
|
866
|
-
if (fs.existsSync(path.join(cwd, "src"))) {
|
|
867
|
-
return { framework: "React", componentsDir: "src/components/ui" };
|
|
868
|
-
}
|
|
869
|
-
return { framework: "Unknown", componentsDir: "components/ui" };
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// src/utils/copy.ts
|
|
873
|
-
var fs2 = __toESM(require("fs"));
|
|
874
|
-
var path2 = __toESM(require("path"));
|
|
875
|
-
function copyComponent(name, entry, outputDir) {
|
|
876
|
-
const filePath = path2.join(outputDir, entry.fileName);
|
|
877
|
-
const dir = path2.dirname(filePath);
|
|
878
|
-
if (!fs2.existsSync(dir)) {
|
|
879
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
880
|
-
}
|
|
881
|
-
fs2.writeFileSync(filePath, entry.content, "utf-8");
|
|
882
|
-
}
|
|
883
|
-
|
|
884
2124
|
// src/commands/add.ts
|
|
2125
|
+
function askQuestion(query) {
|
|
2126
|
+
const rl = readline.createInterface({
|
|
2127
|
+
input: process.stdin,
|
|
2128
|
+
output: process.stdout
|
|
2129
|
+
});
|
|
2130
|
+
return new Promise(
|
|
2131
|
+
(resolve2) => rl.question(query, (ans) => {
|
|
2132
|
+
rl.close();
|
|
2133
|
+
resolve2(ans);
|
|
2134
|
+
})
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
885
2137
|
async function addCommand(components, options) {
|
|
886
2138
|
if (components.length === 0) {
|
|
887
|
-
console.error("\x1B[
|
|
888
|
-
console.log("Example: npx nexoreui add button modal
|
|
889
|
-
|
|
890
|
-
process.exit(1);
|
|
2139
|
+
console.error("\x1B[31mError: Please specify components to add.\x1B[0m");
|
|
2140
|
+
console.log("Example: npx nexoreui add button modal");
|
|
2141
|
+
return;
|
|
891
2142
|
}
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
2143
|
+
const project = detectProject(process.cwd());
|
|
2144
|
+
console.log(`
|
|
2145
|
+
\x1B[34mDetected project type:\x1B[0m ${project.projectType.toUpperCase()}`);
|
|
2146
|
+
console.log(`\x1B[34mDetected package manager:\x1B[0m ${project.packageManager}
|
|
2147
|
+
`);
|
|
2148
|
+
const componentsToInstall = /* @__PURE__ */ new Set();
|
|
2149
|
+
const invalidComponents = [];
|
|
2150
|
+
const queue = [...components];
|
|
2151
|
+
while (queue.length > 0) {
|
|
2152
|
+
const compName = queue.shift();
|
|
2153
|
+
const registryItem = registry[compName];
|
|
2154
|
+
if (!registryItem) {
|
|
2155
|
+
invalidComponents.push(compName);
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
if (!componentsToInstall.has(compName)) {
|
|
2159
|
+
componentsToInstall.add(compName);
|
|
2160
|
+
if (registryItem.componentsDependencies) {
|
|
2161
|
+
for (const dep of registryItem.componentsDependencies) {
|
|
2162
|
+
queue.push(dep);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
if (invalidComponents.length > 0) {
|
|
2168
|
+
console.error(`\x1B[31mError: Component(s) not found in registry: ${invalidComponents.join(", ")}\x1B[0m`);
|
|
895
2169
|
console.log("Run \x1B[32mnpx nexoreui list\x1B[0m to see all available components.");
|
|
896
|
-
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
const defaultComponentsDir = project.hasSrcDir ? "src/components/ui" : "components/ui";
|
|
2173
|
+
const defaultUtilsFile = project.hasSrcDir ? "src/lib/utils.ts" : "lib/utils.ts";
|
|
2174
|
+
let componentsDirInput = "";
|
|
2175
|
+
let utilsFileInput = "";
|
|
2176
|
+
if (options.yes) {
|
|
2177
|
+
componentsDirInput = defaultComponentsDir;
|
|
2178
|
+
utilsFileInput = defaultUtilsFile;
|
|
2179
|
+
} else {
|
|
2180
|
+
const compPrompt = await askQuestion(`Where would you like to install the components? (default: ${defaultComponentsDir}): `);
|
|
2181
|
+
componentsDirInput = compPrompt.trim() || defaultComponentsDir;
|
|
2182
|
+
const utilsPrompt = await askQuestion(`Where should we create the utilities file (cn helper)? (default: ${defaultUtilsFile}): `);
|
|
2183
|
+
utilsFileInput = utilsPrompt.trim() || defaultUtilsFile;
|
|
897
2184
|
}
|
|
898
|
-
const
|
|
899
|
-
const
|
|
2185
|
+
const absoluteComponentsDir = path3.resolve(project.baseDir, componentsDirInput);
|
|
2186
|
+
const absoluteUtilsFile = path3.resolve(project.baseDir, utilsFileInput);
|
|
900
2187
|
console.log(`
|
|
901
|
-
\x1B[
|
|
902
|
-
console.log(
|
|
903
|
-
console.log(`Output: \x1B[36m${outputDir}\x1B[0m
|
|
2188
|
+
\x1B[33mInstalling components to:\x1B[0m ${absoluteComponentsDir}`);
|
|
2189
|
+
console.log(`\x1B[33mUsing cn helper from:\x1B[0m ${absoluteUtilsFile}
|
|
904
2190
|
`);
|
|
905
|
-
|
|
906
|
-
|
|
2191
|
+
ensureDir(absoluteComponentsDir);
|
|
2192
|
+
const didCreateCn = ensureCnUtil(absoluteUtilsFile);
|
|
2193
|
+
if (didCreateCn) {
|
|
2194
|
+
console.log(`\x1B[32mCreated utilities file (cn helper) at:\x1B[0m ${utilsFileInput}`);
|
|
2195
|
+
} else {
|
|
2196
|
+
console.log(`\x1B[90mUtilities file already exists at:\x1B[0m ${utilsFileInput}`);
|
|
907
2197
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
console.error(` \x1B[31m\u2717\x1B[0m ${name}: ${err.message}`);
|
|
918
|
-
}
|
|
2198
|
+
const npmDependencies = /* @__PURE__ */ new Set();
|
|
2199
|
+
npmDependencies.add("clsx");
|
|
2200
|
+
npmDependencies.add("tailwind-merge");
|
|
2201
|
+
for (const compName of componentsToInstall) {
|
|
2202
|
+
const registryItem = registry[compName];
|
|
2203
|
+
const targetPath = path3.join(absoluteComponentsDir, registryItem.fileName);
|
|
2204
|
+
copyComponentFile(registryItem.content, targetPath, absoluteUtilsFile);
|
|
2205
|
+
console.log(`\x1B[32mAdded component:\x1B[0m ${compName} -> ${path3.join(componentsDirInput, registryItem.fileName)}`);
|
|
2206
|
+
registryItem.dependencies.forEach((dep) => npmDependencies.add(dep));
|
|
919
2207
|
}
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
2208
|
+
const depsArray = Array.from(npmDependencies);
|
|
2209
|
+
let depsToInstall = [...depsArray];
|
|
2210
|
+
try {
|
|
2211
|
+
const packageJsonPath = path3.join(project.baseDir, "package.json");
|
|
2212
|
+
if (fs3.existsSync(packageJsonPath)) {
|
|
2213
|
+
const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf8"));
|
|
2214
|
+
const existingDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
2215
|
+
depsToInstall = depsArray.filter((dep) => !existingDeps[dep]);
|
|
925
2216
|
}
|
|
2217
|
+
} catch (e) {
|
|
926
2218
|
}
|
|
927
|
-
|
|
928
|
-
\x1B[32m${successCount} component(s) added successfully.\x1B[0m`);
|
|
929
|
-
if (allDeps.size > 0) {
|
|
2219
|
+
if (depsToInstall.length > 0) {
|
|
930
2220
|
console.log(`
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
2221
|
+
\x1B[33mInstalling external dependencies:\x1B[0m ${depsToInstall.join(", ")}...`);
|
|
2222
|
+
let installCmd = "npm install";
|
|
2223
|
+
if (project.packageManager === "pnpm") {
|
|
2224
|
+
installCmd = "pnpm add";
|
|
2225
|
+
} else if (project.packageManager === "yarn") {
|
|
2226
|
+
installCmd = "yarn add";
|
|
2227
|
+
} else if (project.packageManager === "bun") {
|
|
2228
|
+
installCmd = "bun add";
|
|
2229
|
+
}
|
|
2230
|
+
try {
|
|
2231
|
+
(0, import_child_process.execSync)(`${installCmd} ${depsToInstall.join(" ")}`, {
|
|
2232
|
+
stdio: "inherit",
|
|
2233
|
+
cwd: project.baseDir
|
|
2234
|
+
});
|
|
2235
|
+
console.log("\x1B[32mDependencies installed successfully!\x1B[0m");
|
|
2236
|
+
} catch (err) {
|
|
2237
|
+
console.error("\x1B[31mFailed to install dependencies. Please run the command manually:\x1B[0m");
|
|
2238
|
+
console.log(` ${installCmd} ${depsToInstall.join(" ")}`);
|
|
2239
|
+
}
|
|
2240
|
+
} else {
|
|
2241
|
+
console.log("\n\x1B[90mAll external dependencies are already installed.\x1B[0m");
|
|
934
2242
|
}
|
|
2243
|
+
console.log("\n\x1B[32m\x1B[1mDone! NexoreUI components are ready to use.\x1B[0m\n");
|
|
935
2244
|
}
|
|
936
2245
|
|
|
937
2246
|
// src/commands/list.ts
|
|
938
2247
|
function listCommand() {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
`);
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
2248
|
+
console.log("\n\x1B[34m\x1B[1m=== Available NexoreUI Components ===\x1B[0m\n");
|
|
2249
|
+
Object.keys(registry).forEach((name) => {
|
|
2250
|
+
const item = registry[name];
|
|
2251
|
+
console.log(`- \x1B[32m\x1B[1m${name}\x1B[0m (${item.fileName})`);
|
|
2252
|
+
if (item.dependencies.length > 0) {
|
|
2253
|
+
console.log(` \x1B[90mDependencies: ${item.dependencies.join(", ")}\x1B[0m`);
|
|
2254
|
+
}
|
|
2255
|
+
if (item.componentsDependencies && item.componentsDependencies.length > 0) {
|
|
2256
|
+
console.log(` \x1B[33mRequires component: ${item.componentsDependencies.join(", ")}\x1B[0m`);
|
|
2257
|
+
}
|
|
2258
|
+
console.log("");
|
|
946
2259
|
});
|
|
947
|
-
console.log(`
|
|
948
|
-
\x1B[90mTotal: ${names.length} components\x1B[0m`);
|
|
949
|
-
console.log(`Usage: \x1B[36mnpx nexoreui add ${names[0]}\x1B[0m
|
|
950
|
-
`);
|
|
951
2260
|
}
|
|
952
2261
|
|
|
953
2262
|
// src/index.ts
|