Skip to content

Commit 7d4986a

Browse files
committed
fix(animated-beam): expose repeat timing controls
1 parent 77fe55b commit 7d4986a

4 files changed

Lines changed: 17 additions & 7 deletions

File tree

apps/www/content/docs/components/animated-beam.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ import { AnimatedBeam } from "@/components/ui/animated-beam"
8383
| `reverse` | `boolean` | `false` | Whether the beam should be reversed. |
8484
| `duration` | `number` | `5` | The duration of the beam. |
8585
| `delay` | `number` | `0` | The delay of the beam. |
86+
| `repeat` | `number` | `Infinity` | The number of times the beam animation should repeat. |
87+
| `repeatDelay` | `number` | `0` | The delay between repeated beam animation cycles. |
8688
| `pathColor` | `string` | `gray` | The color of the beam. |
8789
| `pathWidth` | `number` | `2` | The width of the beam. |
8890
| `pathOpacity` | `number` | `0.2` | The opacity of the beam. |

apps/www/public/llms-full.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ Description: An animated beam of light which travels along a path. Useful for sh
166166
--- file: magicui/animated-beam.tsx ---
167167
"use client"
168168

169-
import { RefObject, useEffect, useId, useState } from "react"
169+
import { useEffect, useId, useState, type RefObject } from "react"
170170
import { motion } from "motion/react"
171171

172172
import { cn } from "@/lib/utils"
@@ -185,6 +185,8 @@ export interface AnimatedBeamProps {
185185
gradientStopColor?: string
186186
delay?: number
187187
duration?: number
188+
repeat?: number
189+
repeatDelay?: number
188190
startXOffset?: number
189191
startYOffset?: number
190192
endXOffset?: number
@@ -205,6 +207,8 @@ export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
205207
pathOpacity = 0.2,
206208
gradientStartColor = "#ffaa40",
207209
gradientStopColor = "#9c40ff",
210+
repeat = Infinity,
211+
repeatDelay = 0,
208212
startXOffset = 0,
209213
startYOffset = 0,
210214
endXOffset = 0,
@@ -332,8 +336,8 @@ export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
332336
delay,
333337
duration,
334338
ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo
335-
repeat: Infinity,
336-
repeatDelay: 0,
339+
repeat,
340+
repeatDelay,
337341
}}
338342
>
339343
<stop stopColor={gradientStartColor} stopOpacity="0"></stop>

apps/www/public/r/animated-beam.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"files": [
1111
{
1212
"path": "registry/magicui/animated-beam.tsx",
13-
"content": "\"use client\"\n\nimport { RefObject, useEffect, useId, useState } 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 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 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: Infinity,\n repeatDelay: 0,\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",
13+
"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",
1414
"type": "registry:ui"
1515
}
1616
]

apps/www/registry/magicui/animated-beam.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22

3-
import { RefObject, useEffect, useId, useState } from "react"
3+
import { useEffect, useId, useState, type RefObject } from "react"
44
import { motion } from "motion/react"
55

66
import { cn } from "@/lib/utils"
@@ -19,6 +19,8 @@ export interface AnimatedBeamProps {
1919
gradientStopColor?: string
2020
delay?: number
2121
duration?: number
22+
repeat?: number
23+
repeatDelay?: number
2224
startXOffset?: number
2325
startYOffset?: number
2426
endXOffset?: number
@@ -39,6 +41,8 @@ export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
3941
pathOpacity = 0.2,
4042
gradientStartColor = "#ffaa40",
4143
gradientStopColor = "#9c40ff",
44+
repeat = Infinity,
45+
repeatDelay = 0,
4246
startXOffset = 0,
4347
startYOffset = 0,
4448
endXOffset = 0,
@@ -166,8 +170,8 @@ export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
166170
delay,
167171
duration,
168172
ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo
169-
repeat: Infinity,
170-
repeatDelay: 0,
173+
repeat,
174+
repeatDelay,
171175
}}
172176
>
173177
<stop stopColor={gradientStartColor} stopOpacity="0"></stop>

0 commit comments

Comments
 (0)