wallet.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. "use client";
  2. import type { Variants } from "motion/react";
  3. import { motion, useAnimation } from "motion/react";
  4. import type { HTMLAttributes } from "react";
  5. import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
  6. import { cn } from "@/lib/utils";
  7. export interface WalletIconHandle {
  8. startAnimation: () => void;
  9. stopAnimation: () => void;
  10. }
  11. interface WalletIconProps extends HTMLAttributes<HTMLDivElement> {
  12. size?: number;
  13. }
  14. const VARIANTS: Variants = {
  15. normal: {
  16. y: 0,
  17. rotate: 0,
  18. transition: {
  19. duration: 0.3,
  20. ease: "easeOut",
  21. },
  22. },
  23. animate: {
  24. y: [0, -3, 0],
  25. rotate: [0, -4, 0],
  26. transition: {
  27. duration: 0.55,
  28. ease: "easeInOut",
  29. times: [0, 0.45, 1],
  30. },
  31. },
  32. };
  33. const WalletIcon = forwardRef<WalletIconHandle, WalletIconProps>(
  34. ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {
  35. const controls = useAnimation();
  36. const isControlledRef = useRef(false);
  37. useImperativeHandle(ref, () => {
  38. isControlledRef.current = true;
  39. return {
  40. startAnimation: () => controls.start("animate"),
  41. stopAnimation: () => controls.start("normal"),
  42. };
  43. });
  44. const handleMouseEnter = useCallback(
  45. (e: React.MouseEvent<HTMLDivElement>) => {
  46. if (isControlledRef.current) {
  47. onMouseEnter?.(e);
  48. } else {
  49. controls.start("animate");
  50. }
  51. },
  52. [controls, onMouseEnter]
  53. );
  54. const handleMouseLeave = useCallback(
  55. (e: React.MouseEvent<HTMLDivElement>) => {
  56. if (isControlledRef.current) {
  57. onMouseLeave?.(e);
  58. } else {
  59. controls.start("normal");
  60. }
  61. },
  62. [controls, onMouseLeave]
  63. );
  64. return (
  65. <div
  66. className={cn(className)}
  67. onMouseEnter={handleMouseEnter}
  68. onMouseLeave={handleMouseLeave}
  69. {...props}
  70. >
  71. <motion.svg
  72. animate={controls}
  73. fill="none"
  74. height={size}
  75. initial="normal"
  76. stroke="currentColor"
  77. strokeLinecap="round"
  78. strokeLinejoin="round"
  79. strokeWidth="2"
  80. style={{ transformOrigin: "12px 12px" }}
  81. variants={VARIANTS}
  82. viewBox="0 0 24 24"
  83. width={size}
  84. xmlns="http://www.w3.org/2000/svg"
  85. >
  86. <path d="M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" />
  87. <path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" />
  88. </motion.svg>
  89. </div>
  90. );
  91. }
  92. );
  93. WalletIcon.displayName = "WalletIcon";
  94. export { WalletIcon };