{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ripple-button",
  "type": "registry:ui",
  "title": "RippleButton",
  "description": "A pointer-aware ripple button with touch-safe feedback, customizable motion, and reduced-motion support.",
  "files": [
    {
      "path": "registry/sonaui/ripple-button/ripple-button.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { type ReactNode, useCallback, useRef, useState } from \"react\";\n\nimport { cn } from \"@/lib/sona-utils\";\n\nexport interface RippleButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  /** The content to be displayed inside the button. */\n  children: ReactNode;\n  /**\n   * The scale amount for the ripple effect.\n   * @default 25\n   */\n  scaleAmount?: number;\n  /** Additional CSS classes for the button. */\n  className?: string;\n  /**\n   * Duration of the ripple animation in seconds.\n   * @default 0.5\n   */\n  duration?: number;\n  /** Additional CSS classes for the ripple effect. */\n  rippleStyle?: string;\n}\n\ninterface RippleProps {\n  x: number;\n  y: number;\n  key: number;\n}\n\nconst RIPPLE_SIZE = 10;\n\nexport default function RippleButton({\n  children,\n  className,\n  scaleAmount = 25,\n  duration = 0.5,\n  rippleStyle,\n  disabled,\n  onPointerEnter,\n  onPointerDown,\n  onPointerUp,\n  onPointerLeave,\n  onPointerCancel,\n  ...props\n}: RippleButtonProps) {\n  const [ripple, setRipple] = useState<RippleProps | null>(null);\n  const [isHover, setIsHover] = useState(false);\n  const shouldReduceMotion = useReducedMotion();\n  const rippleKey = useRef(0);\n\n  const showRipple = useCallback(\n    (event: React.PointerEvent<HTMLButtonElement>) => {\n      const rect = event.currentTarget.getBoundingClientRect();\n      rippleKey.current += 1;\n      setRipple({\n        x: event.clientX - rect.left,\n        y: event.clientY - rect.top,\n        key: rippleKey.current,\n      });\n      setIsHover(true);\n    },\n    [],\n  );\n\n  return (\n    <button\n      className={cn(\n        \"relative overflow-hidden rounded-full border border-border bg-background px-6 py-3 leading-[16px] transition-[transform,background-color,border-color] duration-200 ease-out hover:cursor-pointer active:scale-[0.97] motion-reduce:active:scale-100 disabled:pointer-events-none disabled:opacity-50\",\n        className,\n      )}\n      disabled={disabled}\n      onPointerEnter={(e) => {\n        onPointerEnter?.(e);\n        if (disabled) return;\n        // Touch pointers fire enter on tap — let pointerdown handle those.\n        if (e.pointerType === \"mouse\") showRipple(e);\n      }}\n      onPointerDown={(e) => {\n        onPointerDown?.(e);\n        if (disabled) return;\n        if (e.pointerType !== \"mouse\") showRipple(e);\n      }}\n      onPointerUp={(e) => {\n        onPointerUp?.(e);\n        if (e.pointerType !== \"mouse\") setIsHover(false);\n      }}\n      onPointerLeave={(e) => {\n        onPointerLeave?.(e);\n        setIsHover(false);\n      }}\n      onPointerCancel={(e) => {\n        onPointerCancel?.(e);\n        setIsHover(false);\n      }}\n      {...props}\n    >\n      {ripple && (\n        <motion.span\n          key={ripple.key}\n          className={cn(\n            \"pointer-events-none absolute rounded-full bg-foreground\",\n            rippleStyle,\n          )}\n          style={{\n            left: ripple.x - RIPPLE_SIZE / 2,\n            top: ripple.y - RIPPLE_SIZE / 2,\n            width: RIPPLE_SIZE,\n            height: RIPPLE_SIZE,\n          }}\n          initial={{ scale: 0 }}\n          animate={isHover ? { scale: scaleAmount } : { scale: 0 }}\n          transition={\n            shouldReduceMotion\n              ? { duration: 0 }\n              : {\n                  duration: isHover ? duration : 0.4,\n                  ease: [0.23, 1, 0.32, 1],\n                }\n          }\n        />\n      )}\n      {children}\n    </button>\n  );\n}\n\nexport interface RippleButtonTextProps\n  extends React.HTMLAttributes<HTMLSpanElement> {\n  text: string;\n  className?: string;\n}\n\nexport function RippleButtonText({ text, className }: RippleButtonTextProps) {\n  return (\n    <span\n      className={cn(\n        \"text-background dark:text-foreground mix-blend-difference\",\n        className,\n      )}\n    >\n      {text}\n    </span>\n  );\n}\n",
      "target": "components/ui/ripple-button/ripple-button.tsx"
    }
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@sona-ui/sona-utils"
  ]
}