{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ripple-button",
  "type": "registry:ui",
  "title": "Ripple Button",
  "description": "An animated button with ripple useful for user engagement.",
  "files": [
    {
      "path": "registry/magicui/ripple-button.tsx",
      "content": "\"use client\"\n\nimport React, {\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n  type MouseEvent,\n} from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface RippleButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  rippleColor?: string\n  duration?: string\n}\n\ntype RippleItem = {\n  x: number\n  y: number\n  size: number\n  key: number\n}\n\nfunction ButtonRipple({\n  ripple,\n  duration,\n  rippleColor,\n  onDone,\n}: {\n  ripple: RippleItem\n  duration: string\n  rippleColor: string\n  onDone: (key: number) => void\n}) {\n  useEffect(() => {\n    const timeout = setTimeout(\n      () => {\n        onDone(ripple.key)\n      },\n      parseInt(duration, 10)\n    )\n\n    return () => {\n      clearTimeout(timeout)\n    }\n  }, [duration, onDone, ripple.key])\n\n  return (\n    <span\n      className=\"animate-rippling bg-background absolute rounded-full opacity-30\"\n      style={\n        {\n          width: `${ripple.size}px`,\n          height: `${ripple.size}px`,\n          top: `${ripple.y}px`,\n          left: `${ripple.x}px`,\n          backgroundColor: rippleColor,\n          transform: `scale(0)`,\n          \"--duration\": duration,\n        } as React.CSSProperties\n      }\n    />\n  )\n}\n\nexport const RippleButton = React.forwardRef<\n  HTMLButtonElement,\n  RippleButtonProps\n>(\n  (\n    {\n      className,\n      children,\n      rippleColor = \"#ffffff\",\n      duration = \"600ms\",\n      onClick,\n      ...props\n    },\n    ref\n  ) => {\n    const [buttonRipples, setButtonRipples] = useState<Array<RippleItem>>([])\n    const nextKeyRef = useRef(0)\n\n    const removeRipple = useCallback((key: number) => {\n      setButtonRipples((prevRipples) =>\n        prevRipples.filter((ripple) => ripple.key !== key)\n      )\n    }, [])\n\n    const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n      createRipple(event)\n      onClick?.(event)\n    }\n\n    const createRipple = (event: MouseEvent<HTMLButtonElement>) => {\n      const button = event.currentTarget\n      const rect = button.getBoundingClientRect()\n      const size = Math.max(rect.width, rect.height)\n      const x = event.clientX - rect.left - size / 2\n      const y = event.clientY - rect.top - size / 2\n\n      nextKeyRef.current += 1\n      const newRipple = { x, y, size, key: nextKeyRef.current }\n      setButtonRipples((prevRipples) => [...prevRipples, newRipple])\n    }\n\n    return (\n      <button\n        className={cn(\n          \"bg-background text-primary relative flex cursor-pointer items-center justify-center overflow-hidden rounded-lg border-2 px-4 py-2 text-center\",\n          className\n        )}\n        onClick={handleClick}\n        ref={ref}\n        {...props}\n      >\n        <div className=\"relative z-10\">{children}</div>\n        <span className=\"pointer-events-none absolute inset-0\">\n          {buttonRipples.map((ripple) => (\n            <ButtonRipple\n              key={ripple.key}\n              duration={duration}\n              onDone={removeRipple}\n              ripple={ripple}\n              rippleColor={rippleColor}\n            />\n          ))}\n        </span>\n      </button>\n    )\n  }\n)\n\nRippleButton.displayName = \"RippleButton\"\n",
      "type": "registry:ui"
    }
  ],
  "cssVars": {
    "theme": {
      "animate-rippling": "rippling var(--duration) ease-out"
    }
  },
  "css": {
    "@keyframes rippling": {
      "0%": {
        "opacity": "1"
      },
      "100%": {
        "transform": "scale(2)",
        "opacity": "0"
      }
    }
  }
}