blocks.tsx 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  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 BlocksIconHandle {
  8. startAnimation: () => void;
  9. stopAnimation: () => void;
  10. }
  11. interface BlocksIconProps extends HTMLAttributes<HTMLDivElement> {
  12. size?: number;
  13. }
  14. const VARIANTS: Variants = {
  15. normal: { translateX: 0, translateY: 0 },
  16. animate: { translateX: -4, translateY: 4 },
  17. };
  18. const BlocksIcon = forwardRef<BlocksIconHandle, BlocksIconProps>(
  19. ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {
  20. const controls = useAnimation();
  21. const isControlledRef = useRef(false);
  22. useImperativeHandle(ref, () => {
  23. isControlledRef.current = true;
  24. return {
  25. startAnimation: () => controls.start("animate"),
  26. stopAnimation: () => controls.start("normal"),
  27. };
  28. });
  29. const handleMouseEnter = useCallback(
  30. (e: React.MouseEvent<HTMLDivElement>) => {
  31. if (isControlledRef.current) {
  32. onMouseEnter?.(e);
  33. } else {
  34. controls.start("animate");
  35. }
  36. },
  37. [controls, onMouseEnter]
  38. );
  39. const handleMouseLeave = useCallback(
  40. (e: React.MouseEvent<HTMLDivElement>) => {
  41. if (isControlledRef.current) {
  42. onMouseLeave?.(e);
  43. } else {
  44. controls.start("normal");
  45. }
  46. },
  47. [controls, onMouseLeave]
  48. );
  49. return (
  50. <div
  51. className={cn(className)}
  52. onMouseEnter={handleMouseEnter}
  53. onMouseLeave={handleMouseLeave}
  54. {...props}
  55. >
  56. <svg
  57. fill="none"
  58. height={size}
  59. stroke="currentColor"
  60. strokeLinecap="round"
  61. strokeLinejoin="round"
  62. strokeWidth="2"
  63. viewBox="0 0 24 24"
  64. width={size}
  65. xmlns="http://www.w3.org/2000/svg"
  66. >
  67. <path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3" />
  68. <motion.path
  69. animate={controls}
  70. d="M14 3h7v7h-7z"
  71. variants={VARIANTS}
  72. />
  73. </svg>
  74. </div>
  75. );
  76. }
  77. );
  78. BlocksIcon.displayName = "BlocksIcon";
  79. export { BlocksIcon };