shield-check.tsx109 lines · main
1'use client';
2
3import type { Variants } 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 ShieldCheckIconHandle {
11 startAnimation: () => void;
12 stopAnimation: () => void;
13}
14
15interface ShieldCheckIconProps extends HTMLAttributes<HTMLDivElement> {
16 size?: number;
17}
18
19const PATH_VARIANTS: Variants = {
20 normal: {
21 opacity: 1,
22 pathLength: 1,
23 scale: 1,
24 transition: {
25 duration: 0.3,
26 opacity: { duration: 0.1 },
27 },
28 },
29 animate: {
30 opacity: [0, 1],
31 pathLength: [0, 1],
32 scale: [0.5, 1],
33 transition: {
34 duration: 0.4,
35 opacity: { duration: 0.1 },
36 },
37 },
38};
39
40const ShieldCheckIcon = forwardRef<ShieldCheckIconHandle, ShieldCheckIconProps>(
41 ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {
42 const controls = useAnimation();
43 const isControlledRef = useRef(false);
44
45 useImperativeHandle(ref, () => {
46 isControlledRef.current = true;
47
48 return {
49 startAnimation: () => controls.start('animate'),
50 stopAnimation: () => controls.start('normal'),
51 };
52 });
53
54 const handleMouseEnter = useCallback(
55 (e: React.MouseEvent<HTMLDivElement>) => {
56 if (isControlledRef.current) {
57 onMouseEnter?.(e);
58 } else {
59 controls.start('animate');
60 }
61 },
62 [controls, onMouseEnter],
63 );
64
65 const handleMouseLeave = useCallback(
66 (e: React.MouseEvent<HTMLDivElement>) => {
67 if (isControlledRef.current) {
68 onMouseLeave?.(e);
69 } else {
70 controls.start('normal');
71 }
72 },
73 [controls, onMouseLeave],
74 );
75
76 return (
77 <div
78 className={cn(className)}
79 onMouseEnter={handleMouseEnter}
80 onMouseLeave={handleMouseLeave}
81 {...props}
82 >
83 <svg
84 fill="none"
85 height={size}
86 stroke="currentColor"
87 strokeLinecap="round"
88 strokeLinejoin="round"
89 strokeWidth="2"
90 viewBox="0 0 24 24"
91 width={size}
92 xmlns="http://www.w3.org/2000/svg"
93 >
94 <path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />
95 <motion.path
96 animate={controls}
97 d="m9 12 2 2 4-4"
98 initial="normal"
99 variants={PATH_VARIANTS}
100 />
101 </svg>
102 </div>
103 );
104 },
105);
106
107ShieldCheckIcon.displayName = 'ShieldCheckIcon';
108
109export { ShieldCheckIcon };