layout-grid.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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 LayoutGridIconHandle {
  8. startAnimation: () => void;
  9. stopAnimation: () => void;
  10. }
  11. interface LayoutGridIconProps extends HTMLAttributes<HTMLDivElement> {
  12. size?: number;
  13. }
  14. const RECT_1_VARIANTS: Variants = {
  15. normal: { translateX: 0, translateY: 0 },
  16. animate: {
  17. translateX: [0, 11, 11, 0],
  18. translateY: [0, 0, 0, 0],
  19. transition: { duration: 0.8, ease: "easeInOut", times: [0, 0.4, 0.6, 1] },
  20. },
  21. };
  22. const RECT_2_VARIANTS: Variants = {
  23. normal: { translateX: 0, translateY: 0 },
  24. animate: {
  25. translateX: [0, 0, 0, 0],
  26. translateY: [0, 11, 11, 0],
  27. transition: { duration: 0.8, ease: "easeInOut", times: [0, 0.4, 0.6, 1] },
  28. },
  29. };
  30. const RECT_3_VARIANTS: Variants = {
  31. normal: { translateX: 0, translateY: 0 },
  32. animate: {
  33. translateX: [0, -11, -11, 0],
  34. translateY: [0, 0, 0, 0],
  35. transition: { duration: 0.8, ease: "easeInOut", times: [0, 0.4, 0.6, 1] },
  36. },
  37. };
  38. const RECT_4_VARIANTS: Variants = {
  39. normal: { translateX: 0, translateY: 0 },
  40. animate: {
  41. translateX: [0, 0, 0, 0],
  42. translateY: [0, -11, -11, 0],
  43. transition: { duration: 0.8, ease: "easeInOut", times: [0, 0.4, 0.6, 1] },
  44. },
  45. };
  46. const LayoutGridIcon = forwardRef<LayoutGridIconHandle, LayoutGridIconProps>(
  47. ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {
  48. const controls = useAnimation();
  49. const isControlledRef = useRef(false);
  50. useImperativeHandle(ref, () => {
  51. isControlledRef.current = true;
  52. return {
  53. startAnimation: () => controls.start("animate"),
  54. stopAnimation: () => controls.start("normal"),
  55. };
  56. });
  57. const handleMouseEnter = useCallback(
  58. (e: React.MouseEvent<HTMLDivElement>) => {
  59. if (isControlledRef.current) {
  60. onMouseEnter?.(e);
  61. } else {
  62. controls.start("animate");
  63. }
  64. },
  65. [controls, onMouseEnter]
  66. );
  67. const handleMouseLeave = useCallback(
  68. (e: React.MouseEvent<HTMLDivElement>) => {
  69. if (isControlledRef.current) {
  70. onMouseLeave?.(e);
  71. } else {
  72. controls.start("normal");
  73. }
  74. },
  75. [controls, onMouseLeave]
  76. );
  77. return (
  78. <div
  79. className={cn(className)}
  80. onMouseEnter={handleMouseEnter}
  81. onMouseLeave={handleMouseLeave}
  82. {...props}
  83. >
  84. <svg
  85. fill="none"
  86. height={size}
  87. stroke="currentColor"
  88. strokeLinecap="round"
  89. strokeLinejoin="round"
  90. strokeWidth="2"
  91. viewBox="0 0 24 24"
  92. width={size}
  93. xmlns="http://www.w3.org/2000/svg"
  94. >
  95. <motion.rect
  96. animate={controls}
  97. height="7"
  98. initial="normal"
  99. rx="1"
  100. variants={RECT_1_VARIANTS}
  101. width="7"
  102. x="3"
  103. y="3"
  104. />
  105. <motion.rect
  106. animate={controls}
  107. height="7"
  108. initial="normal"
  109. rx="1"
  110. variants={RECT_2_VARIANTS}
  111. width="7"
  112. x="14"
  113. y="3"
  114. />
  115. <motion.rect
  116. animate={controls}
  117. height="7"
  118. initial="normal"
  119. rx="1"
  120. variants={RECT_3_VARIANTS}
  121. width="7"
  122. x="14"
  123. y="14"
  124. />
  125. <motion.rect
  126. animate={controls}
  127. height="7"
  128. initial="normal"
  129. rx="1"
  130. variants={RECT_4_VARIANTS}
  131. width="7"
  132. x="3"
  133. y="14"
  134. />
  135. </svg>
  136. </div>
  137. );
  138. }
  139. );
  140. LayoutGridIcon.displayName = "LayoutGridIcon";
  141. export { LayoutGridIcon };