earth.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. "use client";
  2. import type { Transition, 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 EarthIconHandle {
  8. startAnimation: () => void;
  9. stopAnimation: () => void;
  10. }
  11. interface EarthIconProps extends HTMLAttributes<HTMLDivElement> {
  12. size?: number;
  13. }
  14. const CIRCLE_TRANSITION: Transition = {
  15. duration: 0.3,
  16. delay: 0.1,
  17. opacity: { delay: 0.15 },
  18. };
  19. const CIRCLE_VARIANTS: Variants = {
  20. normal: {
  21. pathLength: 1,
  22. opacity: 1,
  23. },
  24. animate: {
  25. pathLength: [0, 1],
  26. opacity: [0, 1],
  27. },
  28. };
  29. const EarthIcon = forwardRef<EarthIconHandle, EarthIconProps>(
  30. ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {
  31. const controls = useAnimation();
  32. const isControlledRef = useRef(false);
  33. useImperativeHandle(ref, () => {
  34. isControlledRef.current = true;
  35. return {
  36. startAnimation: () => controls.start("animate"),
  37. stopAnimation: () => controls.start("normal"),
  38. };
  39. });
  40. const handleMouseEnter = useCallback(
  41. (e: React.MouseEvent<HTMLDivElement>) => {
  42. if (isControlledRef.current) {
  43. onMouseEnter?.(e);
  44. } else {
  45. controls.start("animate");
  46. }
  47. },
  48. [controls, onMouseEnter]
  49. );
  50. const handleMouseLeave = useCallback(
  51. (e: React.MouseEvent<HTMLDivElement>) => {
  52. if (isControlledRef.current) {
  53. onMouseLeave?.(e);
  54. } else {
  55. controls.start("normal");
  56. }
  57. },
  58. [controls, onMouseLeave]
  59. );
  60. return (
  61. <div
  62. className={cn(className)}
  63. onMouseEnter={handleMouseEnter}
  64. onMouseLeave={handleMouseLeave}
  65. {...props}
  66. >
  67. <svg
  68. fill="none"
  69. height={size}
  70. stroke="currentColor"
  71. strokeLinecap="round"
  72. strokeLinejoin="round"
  73. strokeWidth="2"
  74. viewBox="0 0 24 24"
  75. width={size}
  76. xmlns="http://www.w3.org/2000/svg"
  77. >
  78. <motion.path
  79. animate={controls}
  80. d="M21.54 15H17a2 2 0 0 0-2 2v4.54"
  81. transition={{ duration: 0.7, delay: 0.5, opacity: { delay: 0.5 } }}
  82. variants={{
  83. normal: {
  84. pathLength: 1,
  85. opacity: 1,
  86. pathOffset: 0,
  87. },
  88. animate: {
  89. pathLength: [0, 1],
  90. opacity: [0, 1],
  91. pathOffset: [1, 0],
  92. },
  93. }}
  94. />
  95. <motion.path
  96. animate={controls}
  97. d="M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17"
  98. transition={{ duration: 0.7, delay: 0.5, opacity: { delay: 0.5 } }}
  99. variants={{
  100. normal: {
  101. pathLength: 1,
  102. opacity: 1,
  103. pathOffset: 0,
  104. },
  105. animate: {
  106. pathLength: [0, 1],
  107. opacity: [0, 1],
  108. pathOffset: [1, 0],
  109. },
  110. }}
  111. />
  112. <motion.path
  113. animate={controls}
  114. d="M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05"
  115. transition={{ duration: 0.7, delay: 0.5, opacity: { delay: 0.5 } }}
  116. variants={{
  117. normal: {
  118. pathLength: 1,
  119. opacity: 1,
  120. pathOffset: 0,
  121. },
  122. animate: {
  123. pathLength: [0, 1],
  124. opacity: [0, 1],
  125. pathOffset: [1, 0],
  126. },
  127. }}
  128. />
  129. <motion.circle
  130. animate={controls}
  131. cx="12"
  132. cy="12"
  133. r="10"
  134. transition={CIRCLE_TRANSITION}
  135. variants={CIRCLE_VARIANTS}
  136. />
  137. </svg>
  138. </div>
  139. );
  140. }
  141. );
  142. EarthIcon.displayName = "EarthIcon";
  143. export { EarthIcon };