file-text.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. "use client";
  2. import { motion, useAnimation } from "motion/react";
  3. import type React from "react";
  4. import type { HTMLAttributes } from "react";
  5. import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
  6. import { cn } from "@/lib/utils";
  7. export interface FileTextIconHandle {
  8. startAnimation: () => void;
  9. stopAnimation: () => void;
  10. }
  11. interface FileTextIconProps extends HTMLAttributes<HTMLDivElement> {
  12. size?: number;
  13. }
  14. const FILE_TEXT = forwardRef<FileTextIconHandle, FileTextIconProps>(
  15. ({ onMouseEnter, onMouseLeave, className, size = 28, ...props }, ref) => {
  16. const controls = useAnimation();
  17. const isControlledRef = useRef(false);
  18. useImperativeHandle(ref, () => {
  19. isControlledRef.current = true;
  20. return {
  21. startAnimation: () => controls.start("animate"),
  22. stopAnimation: () => controls.start("normal"),
  23. };
  24. });
  25. const handleMouseEnter = useCallback(
  26. (e: React.MouseEvent<HTMLDivElement>) => {
  27. if (isControlledRef.current) {
  28. onMouseEnter?.(e);
  29. } else {
  30. controls.start("animate");
  31. }
  32. },
  33. [controls, onMouseEnter]
  34. );
  35. const handleMouseLeave = useCallback(
  36. (e: React.MouseEvent<HTMLDivElement>) => {
  37. if (isControlledRef.current) {
  38. onMouseLeave?.(e);
  39. } else {
  40. controls.start("normal");
  41. }
  42. },
  43. [controls, onMouseLeave]
  44. );
  45. return (
  46. <div
  47. className={cn(className)}
  48. onMouseEnter={handleMouseEnter}
  49. onMouseLeave={handleMouseLeave}
  50. {...props}
  51. >
  52. <motion.svg
  53. animate={controls}
  54. fill="none"
  55. height={size}
  56. initial="normal"
  57. stroke="currentColor"
  58. strokeLinecap="round"
  59. strokeLinejoin="round"
  60. strokeWidth="2"
  61. variants={{
  62. normal: { scale: 1 },
  63. animate: {
  64. scale: 1.05,
  65. transition: {
  66. duration: 0.3,
  67. ease: "easeOut",
  68. },
  69. },
  70. }}
  71. viewBox="0 0 24 24"
  72. width={size}
  73. xmlns="http://www.w3.org/2000/svg"
  74. >
  75. <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
  76. <path d="M14 2v4a2 2 0 0 0 2 2h4" />
  77. <motion.path
  78. d="M10 9H8"
  79. stroke="currentColor"
  80. strokeWidth="2"
  81. variants={{
  82. normal: {
  83. pathLength: 1,
  84. x1: 8,
  85. x2: 10,
  86. },
  87. animate: {
  88. pathLength: [1, 0, 1],
  89. x1: [8, 10, 8],
  90. x2: [10, 10, 10],
  91. transition: {
  92. duration: 0.7,
  93. delay: 0.3,
  94. },
  95. },
  96. }}
  97. />
  98. <motion.path
  99. d="M16 13H8"
  100. stroke="currentColor"
  101. strokeWidth="2"
  102. variants={{
  103. normal: {
  104. pathLength: 1,
  105. x1: 8,
  106. x2: 16,
  107. },
  108. animate: {
  109. pathLength: [1, 0, 1],
  110. x1: [8, 16, 8],
  111. x2: [16, 16, 16],
  112. transition: {
  113. duration: 0.7,
  114. delay: 0.5,
  115. },
  116. },
  117. }}
  118. />
  119. <motion.path
  120. d="M16 17H8"
  121. stroke="currentColor"
  122. strokeWidth="2"
  123. variants={{
  124. normal: {
  125. pathLength: 1,
  126. x1: 8,
  127. x2: 16,
  128. },
  129. animate: {
  130. pathLength: [1, 0, 1],
  131. x1: [8, 16, 8],
  132. x2: [16, 16, 16],
  133. transition: {
  134. duration: 0.7,
  135. delay: 0.7,
  136. },
  137. },
  138. }}
  139. />
  140. </motion.svg>
  141. </div>
  142. );
  143. }
  144. );
  145. FILE_TEXT.displayName = "FileTextIcon";
  146. export { FILE_TEXT as FileTextIcon };