layers.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. "use client";
  2. import type { Transition } 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 LayersIconHandle {
  8. startAnimation: () => void;
  9. stopAnimation: () => void;
  10. }
  11. interface LayersIconProps extends HTMLAttributes<HTMLDivElement> {
  12. size?: number;
  13. }
  14. const DEFAULT_TRANSITION: Transition = {
  15. type: "spring",
  16. stiffness: 100,
  17. damping: 14,
  18. mass: 1,
  19. };
  20. const LayersIcon = forwardRef<LayersIconHandle, LayersIconProps>(
  21. ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {
  22. const controls = useAnimation();
  23. const isControlledRef = useRef(false);
  24. useImperativeHandle(ref, () => {
  25. isControlledRef.current = true;
  26. return {
  27. startAnimation: async () => {
  28. await controls.start("firstState");
  29. await controls.start("secondState");
  30. },
  31. stopAnimation: () => controls.start("normal"),
  32. };
  33. });
  34. const handleMouseEnter = useCallback(
  35. async (e: React.MouseEvent<HTMLDivElement>) => {
  36. if (isControlledRef.current) {
  37. onMouseEnter?.(e);
  38. } else {
  39. await controls.start("firstState");
  40. await controls.start("secondState");
  41. }
  42. },
  43. [controls, onMouseEnter]
  44. );
  45. const handleMouseLeave = useCallback(
  46. (e: React.MouseEvent<HTMLDivElement>) => {
  47. if (isControlledRef.current) {
  48. onMouseLeave?.(e);
  49. } else {
  50. controls.start("normal");
  51. }
  52. },
  53. [controls, onMouseLeave]
  54. );
  55. return (
  56. <div
  57. className={cn(className)}
  58. onMouseEnter={handleMouseEnter}
  59. onMouseLeave={handleMouseLeave}
  60. {...props}
  61. >
  62. <svg
  63. fill="none"
  64. height={size}
  65. stroke="currentColor"
  66. strokeLinecap="round"
  67. strokeLinejoin="round"
  68. strokeWidth="2"
  69. viewBox="0 0 24 24"
  70. width={size}
  71. xmlns="http://www.w3.org/2000/svg"
  72. >
  73. <path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z" />
  74. <motion.path
  75. animate={controls}
  76. d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"
  77. transition={DEFAULT_TRANSITION}
  78. variants={{
  79. normal: { y: 0 },
  80. firstState: { y: -9 },
  81. secondState: { y: 0 },
  82. }}
  83. />
  84. <motion.path
  85. animate={controls}
  86. d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"
  87. transition={DEFAULT_TRANSITION}
  88. variants={{
  89. normal: { y: 0 },
  90. firstState: { y: -5 },
  91. secondState: { y: 0 },
  92. }}
  93. />
  94. </svg>
  95. </div>
  96. );
  97. }
  98. );
  99. LayersIcon.displayName = "LayersIcon";
  100. export { LayersIcon };