{
  "title": "Dot Loader",
  "description": "Frame-by-frame 9x9 LED dot matrix loader animation component.",
  "name": "dot-display-dot-loader",
  "dependencies": [
    "cn"
  ],
  "registryDependencies": [],
  "type": "registry:ui",
  "files": [
    {
      "path": "components/paceui/dot-display/dot-loader.tsx",
      "type": "registry:ui",
      "content": "import { type ComponentProps, useCallback, useEffect, useRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type DotLoaderProps = {\n    frames: number[][];\n    columns?: 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    columns = 9,\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    const totalDots = columns * columns;\n\n    return (\n        <div\n            {...props}\n            ref={gridRef}\n            className={cn(\"grid w-fit gap-0.5\", columns === 7 ? \"grid-cols-7\" : \"grid-cols-9\", className)}>\n            {Array.from({ length: totalDots }).map((_, i) => (\n                <div key={i} className={cn(\"h-1.5 w-1.5 rounded-sm\", dotClassName)} />\n            ))}\n        </div>\n    );\n};\n"
    }
  ],
  "mcp": {
    "description": "Provides refined UI interactions and accessible controls. Frame-by-frame 9x9 LED dot matrix loader animation component. Optimal for building reusable interface elements and user input flows.",
    "tags": [
      "component",
      "dot-display"
    ]
  }
}