arrow-right.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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 ArrowRightIconHandle {
  8. startAnimation: () => void;
  9. stopAnimation: () => void;
  10. }
  11. interface ArrowRightIconProps extends HTMLAttributes<HTMLDivElement> {
  12. size?: number;
  13. }
  14. const PATH_VARIANTS: Variants = {
  15. normal: { d: "M5 12h14" },
  16. animate: {
  17. d: ["M5 12h14", "M5 12h9", "M5 12h14"],
  18. transition: {
  19. duration: 0.4,
  20. },
  21. },
  22. };
  23. const SECONDARY_PATH_VARIANTS: Variants = {
  24. normal: { d: "m12 5 7 7-7 7", translateX: 0 },
  25. animate: {
  26. d: "m12 5 7 7-7 7",
  27. translateX: [0, -3, 0],
  28. transition: {
  29. duration: 0.4,
  30. },
  31. },
  32. };
  33. const ArrowRightIcon = forwardRef<ArrowRightIconHandle, ArrowRightIconProps>(
  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. <svg
  72. fill="none"
  73. height={size}
  74. stroke="currentColor"
  75. strokeLinecap="round"
  76. strokeLinejoin="round"
  77. strokeWidth="2"
  78. viewBox="0 0 24 24"
  79. width={size}
  80. xmlns="http://www.w3.org/2000/svg"
  81. >
  82. <motion.path
  83. animate={controls}
  84. d="M5 12h14"
  85. variants={PATH_VARIANTS}
  86. />
  87. <motion.path
  88. animate={controls}
  89. d="m12 5 7 7-7 7"
  90. variants={SECONDARY_PATH_VARIANTS}
  91. />
  92. </svg>
  93. </div>
  94. );
  95. }
  96. );
  97. ArrowRightIcon.displayName = "ArrowRightIcon";
  98. export { ArrowRightIcon };