{
    "name": "dot-loader",
    "title": "Dot Loader",
    "description": "A compact 7x7 dot grid loader with smooth animation to visualize frame sequences or loading states in your app.",
    "type": "registry:ui",
    "files": [
        {
            "path": "components/gsap/dot-loader.tsx",
            "type": "registry:ui",
            "target": "~/components/gsap/dot-loader.tsx",
            "content": "\"use client\";\n\nimport { ComponentProps, useCallback, useEffect, useRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype DotLoaderProps = {\n    frames: number[][];\n    dotClassName?: string;\n    isPlaying?: boolean;\n    duration?: number;\n    repeatCount?: number;\n    onComplete?: () => void;\n} & ComponentProps<\"div\">;\n\nexport const DotLoader = ({\n    frames,\n    isPlaying = true,\n    duration = 100,\n    dotClassName,\n    className,\n    repeatCount = -1,\n    onComplete,\n    ...props\n}: DotLoaderProps) => {\n    const gridRef = useRef<HTMLDivElement>(null);\n    const currentIndex = useRef(0);\n    const repeats = useRef(0);\n    const interval = useRef<NodeJS.Timeout>(null);\n\n    const applyFrameToDots = useCallback(\n        (dots: HTMLDivElement[], frameIndex: number) => {\n            const frame = frames[frameIndex];\n            if (!frame) return;\n\n            dots.forEach((dot, index) => {\n                dot.classList.toggle(\"active\", frame.includes(index));\n            });\n        },\n        [frames],\n    );\n\n    useEffect(() => {\n        currentIndex.current = 0;\n        repeats.current = 0;\n    }, [frames]);\n\n    useEffect(() => {\n        if (isPlaying) {\n            if (currentIndex.current >= frames.length) {\n                currentIndex.current = 0;\n            }\n            const dotElements = gridRef.current?.children;\n            if (!dotElements) return;\n            const dots = Array.from(dotElements) as HTMLDivElement[];\n            interval.current = setInterval(() => {\n                applyFrameToDots(dots, currentIndex.current);\n                if (currentIndex.current + 1 >= frames.length) {\n                    if (repeatCount != -1 && repeats.current + 1 >= repeatCount) {\n                        clearInterval(interval.current!);\n                        onComplete?.();\n                    }\n                    repeats.current++;\n                }\n                currentIndex.current = (currentIndex.current + 1) % frames.length;\n            }, duration);\n        } else {\n            if (interval.current) clearInterval(interval.current);\n        }\n\n        return () => {\n            if (interval.current) clearInterval(interval.current);\n        };\n    }, [frames, isPlaying, applyFrameToDots, duration, repeatCount, onComplete]);\n\n    return (\n        <div {...props} ref={gridRef} className={cn(\"grid w-fit grid-cols-7 gap-0.5\", className)}>\n            {Array.from({ length: 49 }).map((_, i) => (\n                <div key={i} className={cn(\"h-1.5 w-1.5 rounded-sm\", dotClassName)} />\n            ))}\n        </div>\n    );\n};\n"
        }
    ]
}
