credit-card.tsx111 lines · main
1'use client';
2
3import type { Transition } from 'motion/react';
4import { motion, useAnimation } from 'motion/react';
5import type { HTMLAttributes } from 'react';
6import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
7
8import { cn } from '@/lib/utils';
9
10export interface CreditCardIconHandle {
11 startAnimation: () => void;
12 stopAnimation: () => void;
13}
14
15interface CreditCardIconProps extends HTMLAttributes<HTMLDivElement> {
16 size?: number;
17}
18
19const DEFAULT_TRANSITION: Transition = {
20 duration: 0.45,
21 ease: 'easeOut',
22};
23
24const CreditCardIcon = forwardRef<CreditCardIconHandle, CreditCardIconProps>(
25 ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {
26 const controls = useAnimation();
27 const isControlledRef = useRef(false);
28
29 useImperativeHandle(ref, () => {
30 isControlledRef.current = true;
31 return {
32 startAnimation: () => controls.start('animate'),
33 stopAnimation: () => controls.start('normal'),
34 };
35 });
36
37 const handleMouseEnter = useCallback(
38 (e: React.MouseEvent<HTMLDivElement>) => {
39 if (isControlledRef.current) {
40 onMouseEnter?.(e);
41 } else {
42 controls.start('animate');
43 }
44 },
45 [controls, onMouseEnter],
46 );
47
48 const handleMouseLeave = useCallback(
49 (e: React.MouseEvent<HTMLDivElement>) => {
50 if (isControlledRef.current) {
51 onMouseLeave?.(e);
52 } else {
53 controls.start('normal');
54 }
55 },
56 [controls, onMouseLeave],
57 );
58
59 return (
60 <div
61 className={cn(className)}
62 onMouseEnter={handleMouseEnter}
63 onMouseLeave={handleMouseLeave}
64 {...props}
65 >
66 <svg
67 fill="none"
68 height={size}
69 stroke="currentColor"
70 strokeLinecap="round"
71 strokeLinejoin="round"
72 strokeWidth="2"
73 viewBox="0 0 24 24"
74 width={size}
75 xmlns="http://www.w3.org/2000/svg"
76 >
77 <motion.rect
78 width="20"
79 height="14"
80 x="2"
81 y="5"
82 rx="2"
83 animate={controls}
84 transition={DEFAULT_TRANSITION}
85 variants={{
86 normal: { rotate: 0 },
87 animate: { rotate: [0, -3, 3, 0] },
88 }}
89 style={{ transformOrigin: '12px 12px' }}
90 />
91 <motion.line
92 x1="2"
93 x2="22"
94 y1="10"
95 y2="10"
96 animate={controls}
97 transition={DEFAULT_TRANSITION}
98 variants={{
99 normal: { pathLength: 1, opacity: 1 },
100 animate: { pathLength: [1, 0, 1], opacity: [1, 0.4, 1] },
101 }}
102 />
103 </svg>
104 </div>
105 );
106 },
107);
108
109CreditCardIcon.displayName = 'CreditCardIcon';
110
111export { CreditCardIcon };