{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ripple",
  "title": "Ripple",
  "description": "A ripple effect animation on click.",
  "dependencies": [
    "motion",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "registry/wise-ui/components/ripple.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, AnimatePresence } from \"motion/react\"\nimport { cn } from \"@/registry/wise-ui/lib/utils\"\n\ninterface RippleProps {\n  color?: string\n  duration?: number\n  ringCount?: number\n  mode?: \"click\" | \"pulse\"\n  children: React.ReactNode\n  className?: string\n}\n\ninterface RippleEntry {\n  x: number\n  y: number\n  id: number\n}\n\nlet nextId = 0\n\nfunction Ripple({\n  color = \"currentColor\",\n  duration = 600,\n  ringCount = 3,\n  mode = \"click\",\n  children,\n  className,\n}: RippleProps) {\n  const [ripples, setRipples] = React.useState<RippleEntry[]>([])\n  const containerRef = React.useRef<HTMLDivElement>(null)\n\n  function handleClick(e: React.MouseEvent<HTMLDivElement>) {\n    if (mode !== \"click\") return\n    const rect = containerRef.current!.getBoundingClientRect()\n    const x = e.clientX - rect.left\n    const y = e.clientY - rect.top\n    setRipples((prev) => [...prev, { x, y, id: nextId++ }])\n  }\n\n  function removeRipple(id: number) {\n    setRipples((prev) => prev.filter((r) => r.id !== id))\n  }\n\n  const durationS = duration / 1000\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(\"relative overflow-hidden\", className)}\n      onClick={handleClick}\n    >\n      {children}\n\n      {/* Click ripples */}\n      <AnimatePresence>\n        {ripples.map((ripple) => (\n          <div\n            key={ripple.id}\n            className=\"pointer-events-none absolute inset-0\"\n          >\n            {Array.from({ length: ringCount }, (_, i) => (\n              <motion.div\n                key={i}\n                className=\"absolute rounded-full\"\n                style={{\n                  left: ripple.x,\n                  top: ripple.y,\n                  width: 20,\n                  height: 20,\n                  x: \"-50%\",\n                  y: \"-50%\",\n                  border: `1.5px solid ${color}`,\n                }}\n                initial={{ scale: 0, opacity: 0.6 }}\n                animate={{ scale: 2.5, opacity: 0 }}\n                transition={{\n                  duration: durationS,\n                  delay: i * (durationS * 0.2),\n                  ease: \"easeOut\",\n                }}\n                onAnimationComplete={() => {\n                  if (i === ringCount - 1) removeRipple(ripple.id)\n                }}\n              />\n            ))}\n          </div>\n        ))}\n      </AnimatePresence>\n\n      {/* Pulse mode */}\n      {mode === \"pulse\" && (\n        <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n          {Array.from({ length: ringCount }, (_, i) => (\n            <motion.div\n              key={i}\n              className=\"absolute rounded-full\"\n              style={{\n                width: 20,\n                height: 20,\n                border: `1.5px solid ${color}`,\n              }}\n              animate={{ scale: [0, 2.5], opacity: [0.6, 0] }}\n              transition={{\n                duration: durationS,\n                delay: i * (durationS / ringCount),\n                repeat: Infinity,\n                ease: \"easeOut\",\n              }}\n            />\n          ))}\n        </div>\n      )}\n    </div>\n  )\n}\nRipple.displayName = \"Ripple\"\n\nexport { Ripple }\nexport type { RippleProps }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}