{
  "title": "Dot Flow",
  "description": "Animated step flow combining text status transitions with dot matrix animations.",
  "name": "dot-display-dot-flow",
  "dependencies": [
    "gsap",
    "@gsap/react",
    "cn"
  ],
  "registryDependencies": [],
  "type": "registry:ui",
  "files": [
    {
      "path": "components/paceui/dot-display/dot-flow.tsx",
      "type": "registry:ui",
      "content": "import { useEffect, useRef, useState } from \"react\";\n\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\n\nimport { DotLoader } from \"@/components/paceui/dot-display/dot-loader\";\n\nexport type DotFlowProps = {\n    items: {\n        title: string;\n        frames: number[][];\n        duration?: number;\n        repeatCount?: number;\n    }[];\n    isPlaying?: boolean;\n};\n\nexport const DotFlow = ({ items, isPlaying = true }: DotFlowProps) => {\n    const containerRef = useRef<HTMLDivElement>(null);\n    const textRef = useRef<HTMLDivElement>(null);\n    const [index, setIndex] = useState(0);\n    const isTransitioning = useRef(false);\n\n    const safeIndex = items.length > 0 ? index % items.length : 0;\n    const currentItem = items[safeIndex];\n\n    const { contextSafe } = useGSAP();\n\n    useEffect(() => {\n        if (!containerRef.current || !textRef.current) return;\n\n        const newWidth = textRef.current.offsetWidth + 1;\n\n        gsap.to(containerRef.current, {\n            width: newWidth,\n            duration: 0.3,\n            ease: \"power2.out\",\n        });\n    }, [safeIndex, items]);\n\n    useEffect(() => {\n        setIndex(0);\n        isTransitioning.current = false;\n        if (containerRef.current) {\n            gsap.killTweensOf(containerRef.current);\n            gsap.set(containerRef.current, { y: 0, opacity: 1, filter: \"blur(0px)\" });\n        }\n    }, [items]);\n\n    const next = contextSafe(() => {\n        const el = containerRef.current;\n        if (!el || isTransitioning.current || items.length === 0) return;\n\n        isTransitioning.current = true;\n\n        gsap.to(el, {\n            y: 12,\n            opacity: 0,\n            filter: \"blur(4px)\",\n            duration: 0.25,\n            ease: \"power2.in\",\n            onComplete: () => {\n                setIndex((prev) => (prev + 1) % items.length);\n                gsap.fromTo(\n                    el,\n                    { y: -12, opacity: 0, filter: \"blur(4px)\" },\n                    {\n                        y: 0,\n                        opacity: 1,\n                        filter: \"blur(0px)\",\n                        duration: 0.35,\n                        ease: \"power2.out\",\n                        onComplete: () => {\n                            isTransitioning.current = false;\n                        },\n                    },\n                );\n            },\n        });\n    });\n\n    return (\n        <div className=\"bg-card/40 flex items-center gap-5 rounded-md border px-4 py-3\">\n            <DotLoader\n                key={safeIndex}\n                frames={currentItem?.frames ?? []}\n                onComplete={next}\n                className=\"shrink-0 gap-0.5\"\n                isPlaying={isPlaying}\n                repeatCount={currentItem?.repeatCount ?? 1}\n                duration={currentItem?.duration ?? 150}\n                dotClassName=\"bg-foreground/15 [&.active]:bg-foreground size-1.5 rounded-full\"\n            />\n            <div ref={containerRef} className=\"relative overflow-hidden\">\n                <div ref={textRef} className=\"inline-block font-medium whitespace-nowrap max-sm:text-sm\">\n                    {currentItem?.title ?? \"\"}\n                </div>\n            </div>\n        </div>\n    );\n};\n"
    },
    {
      "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. Animated step flow combining text status transitions with dot matrix animations. Relies on gsap, @gsap/react. Optimal for building reusable interface elements and user input flows.",
    "tags": [
      "component",
      "dot-display"
    ]
  }
}