{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "floating-elements", "registryDependencies": [ "https://reusables.vercel.app/r/utils.json" ], "files": [ { "path": "registry/reusables/floating-elements.tsx", "content": "\"use client\"\n\nimport { useCallback, useEffect, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface AnimationConfig {\n minDistance?: number\n maxDistance?: number\n duration?: number\n easing?: string\n shouldRandomizeInitialPosition?: boolean\n}\n\ninterface FloatingElementsProps {\n children: React.ReactNode\n className?: string\n elementClassName?: string\n gridClassName?: string\n titleClassName?: string\n title?: string\n animationConfig?: AnimationConfig\n}\n\nconst defaultAnimationConfig: AnimationConfig = {\n minDistance: 0,\n maxDistance: 50,\n duration: 3000,\n easing: \"cubic-bezier(0.4, 0, 0.2, 1)\",\n shouldRandomizeInitialPosition: true,\n}\n\nexport const FloatingElements = ({\n children,\n className,\n elementClassName,\n gridClassName,\n titleClassName,\n title,\n animationConfig = defaultAnimationConfig,\n}: FloatingElementsProps) => {\n const animationFrameIds = useRef([])\n const elements = useRef([])\n const startTimes = useRef([])\n const currentPositions = useRef<{ x: number; y: number }[]>([])\n const targetPositions = useRef<{ x: number; y: number }[]>([])\n\n const config = { ...defaultAnimationConfig, ...animationConfig }\n\n const generateRandomPosition = useCallback(() => {\n const distance =\n config.minDistance! +\n Math.random() * (config.maxDistance! - config.minDistance!)\n const angle = Math.random() * Math.PI * 2\n return {\n x: Math.cos(angle) * distance,\n y: Math.sin(angle) * distance,\n }\n }, [config.maxDistance, config.minDistance])\n\n const animate = useCallback(\n (timestamp: number) => {\n elements.current.forEach((element, index) => {\n if (!startTimes.current[index]) {\n startTimes.current[index] = timestamp\n currentPositions.current[index] = { x: 0, y: 0 }\n targetPositions.current[index] = generateRandomPosition()\n }\n\n const elapsed = timestamp - startTimes.current[index]\n const progress = Math.min(elapsed / config.duration!, 1)\n\n if (progress === 1) {\n // Instead of snapping, smoothly transition to the next position\n currentPositions.current[index] = targetPositions.current[index]\n targetPositions.current[index] = generateRandomPosition()\n startTimes.current[index] = timestamp\n return\n }\n\n // Interpolate between current and target positions\n const current = currentPositions.current[index]\n const target = targetPositions.current[index]\n const x = current.x + (target.x - current.x) * progress\n const y = current.y + (target.y - current.y) * progress\n\n element.style.transform = `translate3d(${x}px, ${y}px, 0)`\n })\n\n animationFrameIds.current.push(requestAnimationFrame(animate))\n },\n [config.duration, generateRandomPosition]\n )\n\n useEffect(() => {\n const elementsArray = Array.from(\n document.querySelectorAll(\".floating-element\")\n ) as HTMLElement[]\n\n elements.current = elementsArray\n currentPositions.current = elementsArray.map(() => ({ x: 0, y: 0 }))\n targetPositions.current = elementsArray.map(() => generateRandomPosition())\n\n if (config.shouldRandomizeInitialPosition) {\n elementsArray.forEach((element, index) => {\n const initialPosition = generateRandomPosition()\n currentPositions.current[index] = initialPosition\n element.style.transform = `translate3d(${initialPosition.x}px, ${initialPosition.y}px, 0)`\n })\n }\n\n animationFrameIds.current.push(requestAnimationFrame(animate))\n\n return () => {\n animationFrameIds.current.forEach(cancelAnimationFrame)\n animationFrameIds.current = []\n }\n }, [animate, config.shouldRandomizeInitialPosition, generateRandomPosition])\n\n return (\n \n {title && (\n

{title}

\n )}\n \n {Array.isArray(children) ?\n children.map((child, index) => (\n \n {child}\n \n ))\n : \n {children}\n \n }\n \n \n )\n}\n", "type": "registry:component", "target": "components/floating-elements.tsx" } ], "type": "registry:component" }