{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ripple",
  "dependencies": [
    "class-variance-authority",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/watermelon-ui/ripple.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { isMotionComponent, motion, type HTMLMotionProps } from 'motion/react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\n\nconst rippleButtonVariants = {\n  default: '[--ripple-button-ripple-color:var(--primary-foreground)]',\n  accent: '[--ripple-button-ripple-color:var(--accent-foreground)]',\n  destructive: '[--ripple-button-ripple-color:var(--destructive-foreground)]',\n  outline: '[--ripple-button-ripple-color:var(--foreground)]',\n  secondary: '[--ripple-button-ripple-color:var(--secondary-foreground)]',\n  ghost: '[--ripple-button-ripple-color:var(--foreground)]',\n  link: '[--ripple-button-ripple-color:var(--primary-foreground)]',\n};\n\ntype RippleButtonProps = RippleButtonPrimitiveProps &\n  VariantProps<typeof buttonVariants>;\n\nfunction RippleButton({\n  className,\n  variant,\n  size,\n  ...props\n}: RippleButtonProps) {\n  return (\n    <RippleButtonPrimitive\n      className={cn(\n        buttonVariants({ variant, size, className }),\n        rippleButtonVariants[variant as keyof typeof rippleButtonVariants],\n      )}\n      {...props}\n    />\n  );\n}\n\ntype RippleButtonRipplesProps = RippleButtonRipplesPrimitiveProps;\n\nfunction RippleButtonRipples(props: RippleButtonRipplesProps) {\n  return <RippleButtonRipplesPrimitive {...props} />;\n}\n\nexport {\n  RippleButton,\n  RippleButtonRipples,\n  type RippleButtonProps,\n  type RippleButtonRipplesProps,\n};\n\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[box-shadow,_color,_background-color,_border-color,_outline-color,_text-decoration-color,_fill,_stroke] 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:\n          'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',\n        accent: 'bg-accent text-accent-foreground shadow-xs hover:bg-accent/90',\n        destructive:\n          'bg-destructive text-white shadow-xs 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 shadow-xs 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 shadow-xs hover:bg-secondary/80',\n        ghost:\n          'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default: 'h-9 px-4 py-2 has-[>svg]:px-3',\n        sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',\n        lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',\n        icon: 'size-9',\n        'icon-sm': 'size-8 rounded-md',\n        'icon-lg': 'size-10 rounded-md',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\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 RippleButtonPrimitiveProps = WithAsChild<\n  HTMLMotionProps<'button'> & {\n    hoverScale?: number;\n    tapScale?: number;\n  }\n>;\n\nfunction RippleButtonPrimitive({\n  ref,\n  onClick,\n  hoverScale = 1.05,\n  tapScale = 0.95,\n  asChild = false,\n  style,\n  ...props\n}: RippleButtonPrimitiveProps) {\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 RippleButtonRipplesPrimitiveProps = WithAsChild<\n  HTMLMotionProps<'span'> & {\n    color?: string;\n    scale?: number;\n  }\n>;\n\nfunction RippleButtonRipplesPrimitive({\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}: RippleButtonRipplesPrimitiveProps) {\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\n\nfunction getStrictContext<T>(\n  name?: string,\n): readonly [\n  ({\n    value,\n    children,\n  }: {\n    value: T;\n    children?: React.ReactNode;\n  }) => React.JSX.Element,\n  () => T,\n] {\n  const Context = React.createContext<T | undefined>(undefined);\n\n  const Provider = ({\n    value,\n    children,\n  }: {\n    value: T;\n    children?: React.ReactNode;\n  }) => <Context.Provider value={value}>{children}</Context.Provider>;\n\n  const useSafeContext = () => {\n    const ctx = React.useContext(Context);\n    if (ctx === undefined) {\n      throw new Error(`useContext must be used within ${name ?? 'a Provider'}`);\n    }\n    return ctx;\n  };\n\n  return [Provider, useSafeContext] as const;\n}\n\ntype AnyProps = Record<string, unknown>;\n\ntype DOMMotionProps<T extends HTMLElement = HTMLElement> = Omit<\n  HTMLMotionProps<keyof HTMLElementTagNameMap>,\n  'ref'\n> & { ref?: React.Ref<T> };\n\ntype WithAsChild<Base extends object> =\n  | (Base & { asChild: true; children: React.ReactElement })\n  | (Base & { asChild?: false | undefined });\n\ntype SlotProps<T extends HTMLElement = HTMLElement> = {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  children?: any;\n} & DOMMotionProps<T>;\n\nfunction mergeRefs<T>(\n  ...refs: (React.Ref<T> | undefined)[]\n): React.RefCallback<T> {\n  return (node) => {\n    refs.forEach((ref) => {\n      if (!ref) return;\n      if (typeof ref === 'function') {\n        ref(node);\n      } else {\n        (ref as React.RefObject<T | null>).current = node;\n      }\n    });\n  };\n}\n\nfunction mergeProps<T extends HTMLElement>(\n  childProps: AnyProps,\n  slotProps: DOMMotionProps<T>,\n): AnyProps {\n  const merged: AnyProps = { ...childProps, ...slotProps };\n\n  if (childProps.className || slotProps.className) {\n    merged.className = cn(\n      childProps.className as string,\n      slotProps.className as string,\n    );\n  }\n\n  if (childProps.style || slotProps.style) {\n    merged.style = {\n      ...(childProps.style as React.CSSProperties),\n      ...(slotProps.style as React.CSSProperties),\n    };\n  }\n\n  return merged;\n}\n\nfunction Slot<T extends HTMLElement = HTMLElement>({\n  children,\n  ref,\n  ...props\n}: SlotProps<T>) {\n  const isAlreadyMotion =\n    typeof children.type === 'object' &&\n    children.type !== null &&\n    isMotionComponent(children.type);\n\n  const Base = React.useMemo(\n    () =>\n      isAlreadyMotion\n        ? (children.type as React.ElementType)\n        : motion.create(children.type as React.ElementType),\n    [isAlreadyMotion, children.type],\n  );\n\n  if (!React.isValidElement(children)) return null;\n\n  const { ref: childRef, ...childProps } = children.props as AnyProps;\n\n  const mergedProps = mergeProps(childProps, props);\n\n  return (\n    // eslint-disable-next-line react-hooks/static-components\n    <Base {...mergedProps} ref={mergeRefs(childRef as React.Ref<T>, ref)} />\n  );\n}\n\nexport {\n  Slot,\n  type SlotProps,\n  type WithAsChild,\n  type DOMMotionProps,\n  type AnyProps,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/ripple.tsx"
    }
  ],
  "type": "registry:ui"
}
