+ "content": "\"use client\"\n\nimport { useEffect, useId, useState, type RefObject } from \"react\"\nimport { motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface AnimatedBeamProps {\n className?: string\n containerRef: RefObject<HTMLElement | null> // Container ref\n fromRef: RefObject<HTMLElement | null>\n toRef: RefObject<HTMLElement | null>\n curvature?: number\n reverse?: boolean\n pathColor?: string\n pathWidth?: number\n pathOpacity?: number\n gradientStartColor?: string\n gradientStopColor?: string\n delay?: number\n duration?: number\n repeat?: number\n repeatDelay?: number\n startXOffset?: number\n startYOffset?: number\n endXOffset?: number\n endYOffset?: number\n}\n\nexport const AnimatedBeam: React.FC<AnimatedBeamProps> = ({\n className,\n containerRef,\n fromRef,\n toRef,\n curvature = 0,\n reverse = false, // Include the reverse prop\n duration = 5,\n delay = 0,\n pathColor = \"gray\",\n pathWidth = 2,\n pathOpacity = 0.2,\n gradientStartColor = \"#ffaa40\",\n gradientStopColor = \"#9c40ff\",\n repeat = Infinity,\n repeatDelay = 0,\n startXOffset = 0,\n startYOffset = 0,\n endXOffset = 0,\n endYOffset = 0,\n}) => {\n const id = useId()\n const [pathD, setPathD] = useState(\"\")\n const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 })\n\n // Calculate the gradient coordinates based on the reverse prop\n const gradientCoordinates = reverse\n ? {\n x1: [\"90%\", \"-10%\"],\n x2: [\"100%\", \"0%\"],\n y1: [\"0%\", \"0%\"],\n y2: [\"0%\", \"0%\"],\n }\n : {\n x1: [\"10%\", \"110%\"],\n x2: [\"0%\", \"100%\"],\n y1: [\"0%\", \"0%\"],\n y2: [\"0%\", \"0%\"],\n }\n\n useEffect(() => {\n const updatePath = () => {\n if (containerRef.current && fromRef.current && toRef.current) {\n const containerRect = containerRef.current.getBoundingClientRect()\n const rectA = fromRef.current.getBoundingClientRect()\n const rectB = toRef.current.getBoundingClientRect()\n\n const svgWidth = containerRect.width\n const svgHeight = containerRect.height\n setSvgDimensions({ width: svgWidth, height: svgHeight })\n\n const startX =\n rectA.left - containerRect.left + rectA.width / 2 + startXOffset\n const startY =\n rectA.top - containerRect.top + rectA.height / 2 + startYOffset\n const endX =\n rectB.left - containerRect.left + rectB.width / 2 + endXOffset\n const endY =\n rectB.top - containerRect.top + rectB.height / 2 + endYOffset\n\n const controlY = startY - curvature\n const d = `M ${startX},${startY} Q ${\n (startX + endX) / 2\n },${controlY} ${endX},${endY}`\n setPathD(d)\n }\n }\n\n // Initialize ResizeObserver\n const resizeObserver = new ResizeObserver(() => {\n updatePath()\n })\n\n // Observe the container element\n if (containerRef.current) {\n resizeObserver.observe(containerRef.current)\n }\n\n // Call the updatePath initially to set the initial path\n updatePath()\n\n // Clean up the observer on component unmount\n return () => {\n resizeObserver.disconnect()\n }\n }, [\n containerRef,\n fromRef,\n toRef,\n curvature,\n startXOffset,\n startYOffset,\n endXOffset,\n endYOffset,\n ])\n\n return (\n <svg\n fill=\"none\"\n width={svgDimensions.width}\n height={svgDimensions.height}\n xmlns=\"http://www.w3.org/2000/svg\"\n className={cn(\n \"pointer-events-none absolute top-0 left-0 transform-gpu stroke-2\",\n className\n )}\n viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}\n >\n <path\n d={pathD}\n stroke={pathColor}\n strokeWidth={pathWidth}\n strokeOpacity={pathOpacity}\n strokeLinecap=\"round\"\n />\n <path\n d={pathD}\n strokeWidth={pathWidth}\n stroke={`url(#${id})`}\n strokeOpacity=\"1\"\n strokeLinecap=\"round\"\n />\n <defs>\n <motion.linearGradient\n className=\"transform-gpu\"\n id={id}\n gradientUnits={\"userSpaceOnUse\"}\n initial={{\n x1: \"0%\",\n x2: \"0%\",\n y1: \"0%\",\n y2: \"0%\",\n }}\n animate={{\n x1: gradientCoordinates.x1,\n x2: gradientCoordinates.x2,\n y1: gradientCoordinates.y1,\n y2: gradientCoordinates.y2,\n }}\n transition={{\n delay,\n duration,\n ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo\n repeat,\n repeatDelay,\n }}\n >\n <stop stopColor={gradientStartColor} stopOpacity=\"0\"></stop>\n <stop stopColor={gradientStartColor}></stop>\n <stop offset=\"32.5%\" stopColor={gradientStopColor}></stop>\n <stop\n offset=\"100%\"\n stopColor={gradientStopColor}\n stopOpacity=\"0\"\n ></stop>\n </motion.linearGradient>\n </defs>\n </svg>\n )\n}\n",
0 commit comments