{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ripple-button",
  "type": "registry:ui",
  "title": "Ripple Button",
  "description": "A clickable button featuring a ripple animation effect on click.",
  "dependencies": [
    "motion",
    "class-variance-authority"
  ],
  "files": [
    {
      "path": "registry/buttons/ripple/index.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { type HTMLMotionProps, motion, type Transition } from 'motion/react';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst buttonVariants = cva(\n  \"relative overflow-hidden cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n        destructive:\n          'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',\n        outline:\n          'border bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n        ghost:\n          'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n      },\n      size: {\n        default: 'h-10 px-4 py-2 has-[>svg]:px-3',\n        sm: 'h-9 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',\n        lg: 'h-11 px-8 has-[>svg]:px-6',\n        icon: 'size-10',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\nconst rippleVariants = cva('absolute rounded-full size-5 pointer-events-none', {\n  variants: {\n    variant: {\n      default: 'bg-primary-foreground',\n      destructive: 'bg-destructive',\n      outline: 'bg-input',\n      secondary: 'bg-secondary',\n      ghost: 'bg-accent',\n    },\n  },\n  defaultVariants: {\n    variant: 'default',\n  },\n});\n\ntype Ripple = {\n  id: number;\n  x: number;\n  y: number;\n};\n\ntype RippleButtonProps = HTMLMotionProps<'button'> & {\n  children: React.ReactNode;\n  rippleClassName?: string;\n  scale?: number;\n  transition?: Transition;\n} & VariantProps<typeof buttonVariants>;\n\nfunction RippleButton({\n  ref,\n  children,\n  onClick,\n  className,\n  rippleClassName,\n  variant,\n  size,\n  scale = 10,\n  transition = { duration: 0.6, ease: 'easeOut' },\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  return (\n    <motion.button\n      ref={buttonRef}\n      data-slot=\"ripple-button\"\n      onClick={handleClick}\n      whileTap={{ scale: 0.95 }}\n      whileHover={{ scale: 1.05 }}\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    >\n      {children}\n      {ripples.map((ripple) => (\n        <motion.span\n          key={ripple.id}\n          initial={{ scale: 0, opacity: 0.5 }}\n          animate={{ scale, opacity: 0 }}\n          transition={transition}\n          className={cn(\n            rippleVariants({ variant, className: rippleClassName }),\n          )}\n          style={{\n            top: ripple.y - 10,\n            left: ripple.x - 10,\n          }}\n        />\n      ))}\n    </motion.button>\n  );\n}\n\nexport { RippleButton, type RippleButtonProps };\n",
      "type": "registry:ui",
      "target": "components/animate-ui/buttons/ripple.tsx"
    }
  ]
}