@moontra/moonui-pro 2.18.5 → 2.19.0

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.
@@ -1,55 +1,246 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
- import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";
4
+ import { motion, useMotionValue, useSpring, useTransform, type SpringOptions } from "framer-motion";
5
+ import { cva, type VariantProps } from "class-variance-authority";
5
6
  import { cn } from "../../lib/utils";
6
7
 
7
- export interface HoverCard3DProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ // Gelişmiş spring konfigürasyonu tipleri
9
+ export interface SpringConfig extends SpringOptions {
10
+ stiffness?: number;
11
+ damping?: number;
12
+ mass?: number;
13
+ velocity?: number;
14
+ restDelta?: number;
15
+ restSpeed?: number;
16
+ }
17
+
18
+ // Overlay render prop tipi
19
+ export type OverlayRenderProp = (props: {
20
+ isHovered: boolean;
21
+ rotateX: number;
22
+ rotateY: number;
23
+ }) => React.ReactNode;
24
+
25
+ // 3D efekt varyantları
26
+ const hoverCard3DVariants = cva(
27
+ "relative w-full h-full",
28
+ {
29
+ variants: {
30
+ variant: {
31
+ subtle: "",
32
+ dramatic: "",
33
+ gaming: "",
34
+ elegant: "",
35
+ neon: "",
36
+ },
37
+ shadowIntensity: {
38
+ none: "",
39
+ light: "",
40
+ medium: "",
41
+ heavy: "",
42
+ extreme: "",
43
+ },
44
+ glowEffect: {
45
+ none: "",
46
+ subtle: "",
47
+ vibrant: "",
48
+ neon: "",
49
+ }
50
+ },
51
+ defaultVariants: {
52
+ variant: "subtle",
53
+ shadowIntensity: "medium",
54
+ glowEffect: "none",
55
+ }
56
+ }
57
+ );
58
+
59
+ export interface HoverCard3DProps
60
+ extends React.HTMLAttributes<HTMLDivElement>,
61
+ VariantProps<typeof hoverCard3DVariants> {
62
+ /** İçerik */
8
63
  children: React.ReactNode;
9
- rotateAmount?: number;
64
+ /** Maksimum rotasyon açısı (derece) */
65
+ maxRotation?: number;
66
+ /** Hover durumunda ölçekleme faktörü */
10
67
  scale?: number;
68
+ /** 3D perspektif değeri (px) */
11
69
  perspective?: number;
12
- springConfig?: {
13
- stiffness?: number;
14
- damping?: number;
70
+ /** Animasyon hızı (0-1 arası, 1 en hızlı) */
71
+ animationSpeed?: number;
72
+ /** Spring animasyon konfigürasyonu */
73
+ springConfig?: SpringConfig;
74
+ /** Özel overlay içeriği veya render prop */
75
+ overlay?: React.ReactNode | OverlayRenderProp;
76
+ /** Overlay'in her zaman görünür olup olmayacağı */
77
+ overlayAlwaysVisible?: boolean;
78
+ /** Glow efekti rengi (CSS color değeri) */
79
+ glowColor?: string;
80
+ /** Glow efekti blur değeri (px) */
81
+ glowBlur?: number;
82
+ /** Glow efekti spread değeri (px) */
83
+ glowSpread?: number;
84
+ /** Touch desteği aktif mi? */
85
+ enableTouch?: boolean;
86
+ /** Klavye desteği aktif mi? */
87
+ enableKeyboard?: boolean;
88
+ /** Rotasyon eksenleri */
89
+ rotateAxes?: {
90
+ x?: boolean;
91
+ y?: boolean;
15
92
  };
93
+ /** Animasyon başlangıç gecikmesi (ms) */
94
+ animationDelay?: number;
95
+ /** Hover'da tetiklenecek callback */
96
+ onHoverStart?: () => void;
97
+ /** Hover bittiğinde tetiklenecek callback */
98
+ onHoverEnd?: () => void;
99
+ /** Rotasyon değiştiğinde tetiklenecek callback */
100
+ onRotationChange?: (rotateX: number, rotateY: number) => void;
101
+ /** ARIA label */
102
+ ariaLabel?: string;
103
+ /** Otomatik odaklanma */
104
+ autoFocus?: boolean;
16
105
  }
17
106
 
107
+ // Varyant bazlı konfigürasyonlar
108
+ const variantConfigs = {
109
+ subtle: {
110
+ maxRotation: 10,
111
+ scale: 1.02,
112
+ springConfig: { stiffness: 400, damping: 30 },
113
+ },
114
+ dramatic: {
115
+ maxRotation: 25,
116
+ scale: 1.1,
117
+ springConfig: { stiffness: 200, damping: 20 },
118
+ },
119
+ gaming: {
120
+ maxRotation: 30,
121
+ scale: 1.15,
122
+ springConfig: { stiffness: 300, damping: 15 },
123
+ },
124
+ elegant: {
125
+ maxRotation: 8,
126
+ scale: 1.03,
127
+ springConfig: { stiffness: 500, damping: 40 },
128
+ },
129
+ neon: {
130
+ maxRotation: 20,
131
+ scale: 1.08,
132
+ springConfig: { stiffness: 250, damping: 25 },
133
+ },
134
+ };
135
+
136
+ // Shadow intensity değerleri
137
+ const shadowIntensityMap = {
138
+ none: "none",
139
+ light: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
140
+ medium: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
141
+ heavy: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
142
+ extreme: "0 25px 50px -12px rgb(0 0 0 / 0.25)",
143
+ };
144
+
18
145
  const HoverCard3D = React.forwardRef<HTMLDivElement, HoverCard3DProps>(
19
146
  (
20
147
  {
21
148
  children,
22
149
  className,
23
- rotateAmount = 15,
24
- scale = 1.05,
150
+ variant = "subtle",
151
+ shadowIntensity = "medium",
152
+ glowEffect = "none",
153
+ maxRotation,
154
+ scale,
25
155
  perspective = 1000,
26
- springConfig = { stiffness: 300, damping: 20 },
156
+ animationSpeed = 1,
157
+ springConfig,
158
+ overlay,
159
+ overlayAlwaysVisible = false,
160
+ glowColor = "rgb(99, 102, 241)",
161
+ glowBlur = 20,
162
+ glowSpread = 5,
163
+ enableTouch = true,
164
+ enableKeyboard = true,
165
+ rotateAxes = { x: true, y: true },
166
+ animationDelay = 0,
167
+ onHoverStart,
168
+ onHoverEnd,
169
+ onRotationChange,
170
+ ariaLabel,
171
+ autoFocus = false,
27
172
  ...props
28
173
  },
29
174
  ref
30
175
  ) => {
31
176
  const cardRef = React.useRef<HTMLDivElement>(null);
177
+ const [isHovered, setIsHovered] = React.useState(false);
178
+ const [isFocused, setIsFocused] = React.useState(false);
179
+
180
+ // Varyant bazlı default değerler
181
+ const variantConfig = variantConfigs[variant as keyof typeof variantConfigs] || variantConfigs.subtle;
182
+ const finalMaxRotation = maxRotation ?? variantConfig.maxRotation;
183
+ const finalScale = scale ?? variantConfig.scale;
184
+ const finalSpringConfig = springConfig ?? variantConfig.springConfig;
185
+
186
+ // Animasyon hızı faktörü
187
+ const speedFactor = Math.max(0.1, Math.min(1, animationSpeed));
188
+ const adjustedSpringConfig = {
189
+ ...finalSpringConfig,
190
+ stiffness: (finalSpringConfig.stiffness || 300) * speedFactor,
191
+ damping: (finalSpringConfig.damping || 20) / speedFactor,
192
+ };
193
+
32
194
  const mouseX = useMotionValue(0);
33
195
  const mouseY = useMotionValue(0);
34
196
 
197
+ // Rotasyon değerleri
35
198
  const rotateX = useSpring(
36
- useTransform(mouseY, [-0.5, 0.5], [rotateAmount, -rotateAmount]),
37
- springConfig
199
+ useTransform(
200
+ mouseY,
201
+ [-0.5, 0.5],
202
+ rotateAxes.x ? [finalMaxRotation, -finalMaxRotation] : [0, 0]
203
+ ),
204
+ adjustedSpringConfig
38
205
  );
39
206
  const rotateY = useSpring(
40
- useTransform(mouseX, [-0.5, 0.5], [-rotateAmount, rotateAmount]),
41
- springConfig
207
+ useTransform(
208
+ mouseX,
209
+ [-0.5, 0.5],
210
+ rotateAxes.y ? [-finalMaxRotation, finalMaxRotation] : [0, 0]
211
+ ),
212
+ adjustedSpringConfig
42
213
  );
43
214
 
44
- const handleMouseMove = React.useCallback(
45
- (e: React.MouseEvent<HTMLDivElement>) => {
215
+ // Rotasyon değişim callback'i
216
+ React.useEffect(() => {
217
+ if (onRotationChange) {
218
+ const unsubscribeX = rotateX.on("change", (x) => {
219
+ const y = rotateY.get();
220
+ onRotationChange(x, y);
221
+ });
222
+ const unsubscribeY = rotateY.on("change", (y) => {
223
+ const x = rotateX.get();
224
+ onRotationChange(x, y);
225
+ });
226
+
227
+ return () => {
228
+ unsubscribeX();
229
+ unsubscribeY();
230
+ };
231
+ }
232
+ }, [rotateX, rotateY, onRotationChange]);
233
+
234
+ // Mouse/Touch pozisyon hesaplama
235
+ const calculatePosition = React.useCallback(
236
+ (clientX: number, clientY: number) => {
46
237
  if (!cardRef.current) return;
47
238
 
48
239
  const rect = cardRef.current.getBoundingClientRect();
49
240
  const width = rect.width;
50
241
  const height = rect.height;
51
- const x = e.clientX - rect.left;
52
- const y = e.clientY - rect.top;
242
+ const x = clientX - rect.left;
243
+ const y = clientY - rect.top;
53
244
 
54
245
  const xPct = x / width - 0.5;
55
246
  const yPct = y / height - 0.5;
@@ -60,10 +251,131 @@ const HoverCard3D = React.forwardRef<HTMLDivElement, HoverCard3DProps>(
60
251
  [mouseX, mouseY]
61
252
  );
62
253
 
254
+ // Event handler'lar
255
+ const handleMouseMove = React.useCallback(
256
+ (e: React.MouseEvent<HTMLDivElement>) => {
257
+ calculatePosition(e.clientX, e.clientY);
258
+ },
259
+ [calculatePosition]
260
+ );
261
+
262
+ const handleTouchMove = React.useCallback(
263
+ (e: React.TouchEvent<HTMLDivElement>) => {
264
+ if (!enableTouch) return;
265
+ const touch = e.touches[0];
266
+ calculatePosition(touch.clientX, touch.clientY);
267
+ },
268
+ [calculatePosition, enableTouch]
269
+ );
270
+
271
+ const handleMouseEnter = React.useCallback(() => {
272
+ setIsHovered(true);
273
+ onHoverStart?.();
274
+ }, [onHoverStart]);
275
+
63
276
  const handleMouseLeave = React.useCallback(() => {
64
277
  mouseX.set(0);
65
278
  mouseY.set(0);
66
- }, [mouseX, mouseY]);
279
+ setIsHovered(false);
280
+ onHoverEnd?.();
281
+ }, [mouseX, mouseY, onHoverEnd]);
282
+
283
+ const handleTouchEnd = React.useCallback(() => {
284
+ if (!enableTouch) return;
285
+ handleMouseLeave();
286
+ }, [handleMouseLeave, enableTouch]);
287
+
288
+ // Klavye desteği
289
+ const handleKeyDown = React.useCallback(
290
+ (e: React.KeyboardEvent<HTMLDivElement>) => {
291
+ if (!enableKeyboard) return;
292
+
293
+ const step = 0.1;
294
+ let newX = mouseX.get();
295
+ let newY = mouseY.get();
296
+
297
+ switch (e.key) {
298
+ case "ArrowUp":
299
+ newY = Math.max(-0.5, newY - step);
300
+ break;
301
+ case "ArrowDown":
302
+ newY = Math.min(0.5, newY + step);
303
+ break;
304
+ case "ArrowLeft":
305
+ newX = Math.max(-0.5, newX - step);
306
+ break;
307
+ case "ArrowRight":
308
+ newX = Math.min(0.5, newX + step);
309
+ break;
310
+ case "Enter":
311
+ case " ":
312
+ setIsHovered(!isHovered);
313
+ if (!isHovered) {
314
+ onHoverStart?.();
315
+ } else {
316
+ onHoverEnd?.();
317
+ }
318
+ break;
319
+ case "Escape":
320
+ newX = 0;
321
+ newY = 0;
322
+ setIsHovered(false);
323
+ onHoverEnd?.();
324
+ break;
325
+ default:
326
+ return;
327
+ }
328
+
329
+ e.preventDefault();
330
+ mouseX.set(newX);
331
+ mouseY.set(newY);
332
+ },
333
+ [mouseX, mouseY, isHovered, enableKeyboard, onHoverStart, onHoverEnd]
334
+ );
335
+
336
+ const handleFocus = React.useCallback(() => {
337
+ setIsFocused(true);
338
+ }, []);
339
+
340
+ const handleBlur = React.useCallback(() => {
341
+ setIsFocused(false);
342
+ if (!isHovered) {
343
+ mouseX.set(0);
344
+ mouseY.set(0);
345
+ }
346
+ }, [mouseX, mouseY, isHovered]);
347
+
348
+ // Glow efekti stili
349
+ const glowStyle = React.useMemo(() => {
350
+ if (glowEffect === "none") return {};
351
+
352
+ const intensity = {
353
+ subtle: 0.3,
354
+ vibrant: 0.6,
355
+ neon: 1,
356
+ }[glowEffect as string] || 0.3;
357
+
358
+ return {
359
+ boxShadow: isHovered
360
+ ? `0 0 ${glowBlur}px ${glowSpread}px ${glowColor}${Math.round(intensity * 255).toString(16).padStart(2, '0')}`
361
+ : undefined,
362
+ };
363
+ }, [glowEffect, glowColor, glowBlur, glowSpread, isHovered]);
364
+
365
+ // Overlay içeriği
366
+ const overlayContent = React.useMemo(() => {
367
+ if (!overlay) return null;
368
+
369
+ if (typeof overlay === "function") {
370
+ return overlay({
371
+ isHovered,
372
+ rotateX: rotateX.get(),
373
+ rotateY: rotateY.get(),
374
+ });
375
+ }
376
+
377
+ return overlay;
378
+ }, [overlay, isHovered, rotateX, rotateY]);
67
379
 
68
380
  return (
69
381
  <div
@@ -74,23 +386,80 @@ const HoverCard3D = React.forwardRef<HTMLDivElement, HoverCard3DProps>(
74
386
  >
75
387
  <motion.div
76
388
  ref={cardRef}
77
- className="w-full h-full transition-shadow duration-300 hover:shadow-xl"
389
+ className={cn(
390
+ hoverCard3DVariants({ variant, shadowIntensity, glowEffect }),
391
+ "transition-shadow duration-300",
392
+ isFocused && "ring-2 ring-primary ring-offset-2 ring-offset-background"
393
+ )}
78
394
  style={{
79
395
  rotateX,
80
396
  rotateY,
81
397
  transformStyle: "preserve-3d",
398
+ boxShadow: shadowIntensityMap[shadowIntensity as keyof typeof shadowIntensityMap],
399
+ ...glowStyle,
82
400
  }}
83
401
  onMouseMove={handleMouseMove}
402
+ onMouseEnter={handleMouseEnter}
84
403
  onMouseLeave={handleMouseLeave}
85
- whileHover={{ scale }}
86
- transition={{ type: "spring", ...springConfig }}
404
+ onTouchMove={handleTouchMove}
405
+ onTouchEnd={handleTouchEnd}
406
+ onKeyDown={handleKeyDown}
407
+ onFocus={handleFocus}
408
+ onBlur={handleBlur}
409
+ whileHover={{ scale: finalScale }}
410
+ initial={{ scale: 1 }}
411
+ transition={{
412
+ type: "spring",
413
+ delay: animationDelay / 1000,
414
+ ...adjustedSpringConfig,
415
+ }}
416
+ tabIndex={enableKeyboard ? 0 : -1}
417
+ role="button"
418
+ aria-label={ariaLabel || "3D hover card"}
419
+ autoFocus={autoFocus}
87
420
  >
88
- <div
89
- className="absolute inset-0 rounded-lg bg-gradient-to-br from-white/20 to-white/0 opacity-0 hover:opacity-100 transition-opacity duration-300"
90
- style={{
91
- transform: "translateZ(1px)",
92
- }}
93
- />
421
+ {/* Overlay katmanı */}
422
+ {(overlayAlwaysVisible || isHovered) && overlayContent && (
423
+ <div
424
+ className="absolute inset-0 rounded-lg pointer-events-none"
425
+ style={{
426
+ transform: "translateZ(1px)",
427
+ }}
428
+ >
429
+ {overlayContent}
430
+ </div>
431
+ )}
432
+
433
+ {/* Varsayılan highlight efekti */}
434
+ {!overlay && variant !== "neon" && (
435
+ <div
436
+ className={cn(
437
+ "absolute inset-0 rounded-lg bg-gradient-to-br from-white/20 to-white/0",
438
+ "opacity-0 transition-opacity duration-300 pointer-events-none",
439
+ isHovered && "opacity-100"
440
+ )}
441
+ style={{
442
+ transform: "translateZ(1px)",
443
+ }}
444
+ />
445
+ )}
446
+
447
+ {/* Neon varyantı için özel efekt */}
448
+ {variant === "neon" && (
449
+ <div
450
+ className={cn(
451
+ "absolute inset-0 rounded-lg",
452
+ "opacity-0 transition-opacity duration-300 pointer-events-none",
453
+ isHovered && "opacity-100"
454
+ )}
455
+ style={{
456
+ transform: "translateZ(2px)",
457
+ background: `linear-gradient(45deg, ${glowColor}20, transparent, ${glowColor}20)`,
458
+ filter: "blur(10px)",
459
+ }}
460
+ />
461
+ )}
462
+
94
463
  {children}
95
464
  </motion.div>
96
465
  </div>
@@ -100,4 +469,4 @@ const HoverCard3D = React.forwardRef<HTMLDivElement, HoverCard3DProps>(
100
469
 
101
470
  HoverCard3D.displayName = "HoverCard3D";
102
471
 
103
- export { HoverCard3D };
472
+ export { HoverCard3D, hoverCard3DVariants };
@@ -170,4 +170,9 @@ export {
170
170
  HoverCard, HoverCardTrigger, HoverCardContent
171
171
  } from './hover-card';
172
172
 
173
+ export {
174
+ HoverCard3D, hoverCard3DVariants
175
+ } from './hover-card-3d';
176
+ export type { HoverCard3DProps, SpringConfig, OverlayRenderProp } from './hover-card-3d';
177
+
173
178
  // Note: Micro-interaction components are exported from their individual directories
@@ -1,185 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { motion, AnimatePresence } from "framer-motion";
5
- import { Check, X, Loader2 } from "lucide-react";
6
- import { cn } from "../../lib/utils";
7
- import { cva, type VariantProps } from "class-variance-authority";
8
-
9
- const animatedButtonVariants = cva(
10
- "relative inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
11
- {
12
- variants: {
13
- variant: {
14
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
15
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
16
- outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
18
- ghost: "hover:bg-accent hover:text-accent-foreground",
19
- link: "text-primary underline-offset-4 hover:underline",
20
- },
21
- size: {
22
- default: "h-10 px-4 py-2",
23
- sm: "h-9 rounded-md px-3",
24
- lg: "h-11 rounded-md px-8",
25
- icon: "h-10 w-10",
26
- },
27
- },
28
- defaultVariants: {
29
- variant: "default",
30
- size: "default",
31
- },
32
- }
33
- );
34
-
35
- export interface AnimatedButtonProps
36
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
37
- VariantProps<typeof animatedButtonVariants> {
38
- state?: "idle" | "loading" | "success" | "error";
39
- loadingText?: string;
40
- successText?: string;
41
- errorText?: string;
42
- }
43
-
44
- const AnimatedButton = React.forwardRef<HTMLButtonElement, AnimatedButtonProps>(
45
- (
46
- {
47
- className,
48
- variant,
49
- size,
50
- state = "idle",
51
- loadingText = "Loading...",
52
- successText = "Success!",
53
- errorText = "Error!",
54
- children,
55
- disabled,
56
- ...props
57
- },
58
- ref
59
- ) => {
60
- const [buttonWidth, setButtonWidth] = React.useState<number | "auto">("auto");
61
- const buttonRef = React.useRef<HTMLButtonElement>(null);
62
-
63
- React.useEffect(() => {
64
- if (buttonRef.current && state === "idle") {
65
- setButtonWidth(buttonRef.current.offsetWidth);
66
- }
67
- }, [state, children]);
68
-
69
- const isDisabled = disabled || state !== "idle";
70
-
71
- const contentVariants = {
72
- idle: { opacity: 1, y: 0 },
73
- loading: { opacity: 0, y: 10 },
74
- success: { opacity: 0, y: 10 },
75
- error: { opacity: 0, y: 10 },
76
- };
77
-
78
- const iconVariants = {
79
- hidden: { opacity: 0, scale: 0.8, y: -10 },
80
- visible: { opacity: 1, scale: 1, y: 0 },
81
- };
82
-
83
- return (
84
- <motion.button
85
- ref={(el) => {
86
- buttonRef.current = el;
87
- if (ref) {
88
- if (typeof ref === "function") ref(el);
89
- else ref.current = el;
90
- }
91
- }}
92
- className={cn(animatedButtonVariants({ variant, size, className }))}
93
- disabled={isDisabled}
94
- animate={{
95
- width: state !== "idle" ? buttonWidth : "auto",
96
- }}
97
- transition={{ duration: 0.2 }}
98
- {...props}
99
- >
100
- <AnimatePresence mode="wait">
101
- {state === "idle" && (
102
- <motion.span
103
- key="idle"
104
- variants={contentVariants}
105
- initial="loading"
106
- animate="idle"
107
- exit="loading"
108
- transition={{ duration: 0.2 }}
109
- className="inline-flex items-center"
110
- >
111
- {children}
112
- </motion.span>
113
- )}
114
-
115
- {state === "loading" && (
116
- <motion.span
117
- key="loading"
118
- className="inline-flex items-center gap-2"
119
- initial={{ opacity: 0, y: -10 }}
120
- animate={{ opacity: 1, y: 0 }}
121
- exit={{ opacity: 0, y: 10 }}
122
- transition={{ duration: 0.2 }}
123
- >
124
- <Loader2 className="h-4 w-4 animate-spin" />
125
- {loadingText}
126
- </motion.span>
127
- )}
128
-
129
- {state === "success" && (
130
- <motion.span
131
- key="success"
132
- className="inline-flex items-center gap-2"
133
- initial={{ opacity: 0, y: -10 }}
134
- animate={{ opacity: 1, y: 0 }}
135
- exit={{ opacity: 0, y: 10 }}
136
- transition={{ duration: 0.2 }}
137
- >
138
- <motion.div
139
- variants={iconVariants}
140
- initial="hidden"
141
- animate="visible"
142
- transition={{ duration: 0.3, delay: 0.1 }}
143
- >
144
- <Check className="h-4 w-4" />
145
- </motion.div>
146
- {successText}
147
- </motion.span>
148
- )}
149
-
150
- {state === "error" && (
151
- <motion.span
152
- key="error"
153
- className="inline-flex items-center gap-2"
154
- initial={{ opacity: 0, y: -10 }}
155
- animate={{ opacity: 1, y: 0 }}
156
- exit={{ opacity: 0, y: 10 }}
157
- transition={{ duration: 0.2 }}
158
- >
159
- <motion.div
160
- variants={iconVariants}
161
- initial="hidden"
162
- animate="visible"
163
- transition={{ duration: 0.3, delay: 0.1 }}
164
- animate={{
165
- x: [0, -2, 2, -2, 2, 0],
166
- }}
167
- transition={{
168
- duration: 0.4,
169
- delay: 0.1,
170
- }}
171
- >
172
- <X className="h-4 w-4" />
173
- </motion.div>
174
- {errorText}
175
- </motion.span>
176
- )}
177
- </AnimatePresence>
178
- </motion.button>
179
- );
180
- }
181
- );
182
-
183
- AnimatedButton.displayName = "AnimatedButton";
184
-
185
- export { AnimatedButton, animatedButtonVariants };