book-text.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. "use client";
  2. import { motion, useAnimation } from "motion/react";
  3. import type { HTMLAttributes } from "react";
  4. import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
  5. import { cn } from "@/lib/utils";
  6. export interface BookTextIconHandle {
  7. startAnimation: () => void;
  8. stopAnimation: () => void;
  9. }
  10. interface BookTextIconProps extends HTMLAttributes<HTMLDivElement> {
  11. size?: number;
  12. }
  13. const BookTextIcon = forwardRef<BookTextIconHandle, BookTextIconProps>(
  14. ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {
  15. const controls = useAnimation();
  16. const isControlledRef = useRef(false);
  17. useImperativeHandle(ref, () => {
  18. isControlledRef.current = true;
  19. return {
  20. startAnimation: () => controls.start("animate"),
  21. stopAnimation: () => controls.start("normal"),
  22. };
  23. });
  24. const handleMouseEnter = useCallback(
  25. (e: React.MouseEvent<HTMLDivElement>) => {
  26. if (isControlledRef.current) {
  27. onMouseEnter?.(e);
  28. } else {
  29. controls.start("animate");
  30. }
  31. },
  32. [controls, onMouseEnter]
  33. );
  34. const handleMouseLeave = useCallback(
  35. (e: React.MouseEvent<HTMLDivElement>) => {
  36. if (isControlledRef.current) {
  37. onMouseLeave?.(e);
  38. } else {
  39. controls.start("normal");
  40. }
  41. },
  42. [controls, onMouseLeave]
  43. );
  44. return (
  45. <div
  46. className={cn(className)}
  47. onMouseEnter={handleMouseEnter}
  48. onMouseLeave={handleMouseLeave}
  49. {...props}
  50. >
  51. <motion.svg
  52. animate={controls}
  53. fill="none"
  54. height={size}
  55. stroke="currentColor"
  56. strokeLinecap="round"
  57. strokeLinejoin="round"
  58. strokeWidth="2"
  59. variants={{
  60. animate: {
  61. scale: [1, 1.04, 1],
  62. rotate: [0, -8, 8, -8, 0],
  63. y: [0, -2, 0],
  64. transition: {
  65. duration: 0.6,
  66. ease: "easeInOut",
  67. times: [0, 0.2, 0.5, 0.8, 1],
  68. },
  69. },
  70. normal: {
  71. scale: 1,
  72. rotate: 0,
  73. y: 0,
  74. },
  75. }}
  76. viewBox="0 0 24 24"
  77. width={size}
  78. xmlns="http://www.w3.org/2000/svg"
  79. >
  80. <path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" />
  81. <path d="M8 11h8" />
  82. <path d="M8 7h6" />
  83. </motion.svg>
  84. </div>
  85. );
  86. }
  87. );
  88. BookTextIcon.displayName = "BookTextIcon";
  89. export { BookTextIcon };