{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "primitives-buttons-ripple",
  "title": "Ripple Button",
  "description": "A button that animates on tap with a ripple effect.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@unlumen-ui/lib-get-strict-context",
    "@unlumen-ui/primitives-animate-slot"
  ],
  "files": [
    {
      "path": "registry/primitives/buttons/ripple/index.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion, type HTMLMotionProps } from \"motion/react\";\n\nimport { getStrictContext } from \"@/lib/get-strict-context\";\nimport { Slot, type WithAsChild } from \"@/components/unlumen-ui/primitives/animate/slot\";\n\ntype Ripple = {\n  id: number;\n  x: number;\n  y: number;\n};\n\ntype RippleButtonContextType = {\n  ripples: Ripple[];\n  setRipples: (ripples: Ripple[]) => void;\n};\n\nconst [RippleButtonProvider, useRippleButton] =\n  getStrictContext<RippleButtonContextType>(\"RippleButtonContext\");\n\ntype RippleButtonProps = WithAsChild<\n  HTMLMotionProps<\"button\"> & {\n    hoverScale?: number;\n    tapScale?: number;\n  }\n>;\n\nfunction RippleButton({\n  ref,\n  onClick,\n  hoverScale = 1.05,\n  tapScale = 0.95,\n  asChild = false,\n  style,\n  ...props\n}: RippleButtonProps) {\n  const [ripples, setRipples] = React.useState<Ripple[]>([]);\n  const buttonRef = React.useRef<HTMLButtonElement>(null);\n  React.useImperativeHandle(ref, () => buttonRef.current as HTMLButtonElement);\n\n  const createRipple = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      const button = buttonRef.current;\n      if (!button) return;\n\n      const rect = button.getBoundingClientRect();\n      const x = event.clientX - rect.left;\n      const y = event.clientY - rect.top;\n\n      const newRipple: Ripple = {\n        id: Date.now(),\n        x,\n        y,\n      };\n\n      setRipples((prev) => [...prev, newRipple]);\n\n      setTimeout(() => {\n        setRipples((prev) => prev.filter((r) => r.id !== newRipple.id));\n      }, 600);\n    },\n    [],\n  );\n\n  const handleClick = React.useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      createRipple(event);\n      if (onClick) {\n        onClick(event);\n      }\n    },\n    [createRipple, onClick],\n  );\n\n  const Component = asChild ? Slot : motion.button;\n\n  return (\n    <RippleButtonProvider value={{ ripples, setRipples }}>\n      <Component\n        ref={buttonRef}\n        data-slot=\"ripple-button\"\n        onClick={handleClick}\n        whileTap={{ scale: tapScale }}\n        whileHover={{ scale: hoverScale }}\n        style={{\n          position: \"relative\",\n          overflow: \"hidden\",\n          ...style,\n        }}\n        {...props}\n      />\n    </RippleButtonProvider>\n  );\n}\n\ntype RippleButtonRipplesProps = WithAsChild<\n  HTMLMotionProps<\"span\"> & {\n    color?: string;\n    scale?: number;\n  }\n>;\n\nfunction RippleButtonRipples({\n  color = \"var(--ripple-button-ripple-color)\",\n  scale = 10,\n  transition = { duration: 0.6, ease: \"easeOut\" },\n  asChild = false,\n  style,\n  ...props\n}: RippleButtonRipplesProps) {\n  const { ripples } = useRippleButton();\n\n  const Component = asChild ? Slot : motion.span;\n\n  return ripples.map((ripple) => (\n    <Component\n      key={ripple.id}\n      initial={{ scale: 0, opacity: 0.5 }}\n      animate={{ scale, opacity: 0 }}\n      transition={transition}\n      style={{\n        position: \"absolute\",\n        borderRadius: \"50%\",\n        pointerEvents: \"none\",\n        width: \"20px\",\n        height: \"20px\",\n        backgroundColor: color,\n        top: ripple.y - 10,\n        left: ripple.x - 10,\n        ...style,\n      }}\n      {...props}\n    />\n  ));\n}\n\nexport {\n  RippleButton,\n  RippleButtonRipples,\n  type RippleButtonProps,\n  type RippleButtonRipplesProps,\n};\n",
      "type": "registry:ui",
      "target": "components/unlumen-ui/primitives/buttons/ripple.tsx"
    }
  ],
  "type": "registry:ui"
}