ripple-effect-button

PreviousNext

A Button component with ripple effect animation on hover.

Docs
tailwind-admincomponent

Preview

Loading preview…
app/components/animatedComponents/buttons/ripple-effect/RippleEffectButton.tsx
'use client'

import * as React from 'react'

import { motion, type HTMLMotionProps, type Transition } from 'motion/react'
import type { VariantProps } from 'class-variance-authority'

import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'

interface Ripple {
  id: number
  x: number
  y: number
}

interface RippleEffectButtonProps extends HTMLMotionProps<'button'>, VariantProps<typeof buttonVariants> {  
  scale?: number
  transition?: Transition
}

function RippleEffectButton({
  ref,
  onClick,
  className,
  variant,
  size,
  scale = 10,
  transition = { duration: 0.6, ease: 'easeOut' },
  ...props
}: RippleEffectButtonProps) {
  const [ripples, setRipples] = React.useState<Ripple[]>([])
  const buttonRef = React.useRef<HTMLButtonElement>(null)

  React.useImperativeHandle(ref, () => buttonRef.current as HTMLButtonElement)

  const createRipple = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
    const button = buttonRef.current

    if (!button) return

    const rect = button.getBoundingClientRect()
    const x = event.clientX - rect.left
    const y = event.clientY - rect.top

    const newRipple: Ripple = {
      id: Date.now(),
      x,
      y
    }

    setRipples(prev => [...prev, newRipple])

    setTimeout(() => {
      setRipples(prev => prev.filter(r => r.id !== newRipple.id))
    }, 600)
  }, [])

  const handleClick = React.useCallback(
    (event: React.MouseEvent<HTMLButtonElement>) => {
      createRipple(event)

      if (onClick) {
        onClick(event)
      }
    },
    [createRipple, onClick]
  )

  return (
    <motion.button
      ref={buttonRef}
      data-slot='ripple-button'
      onClick={handleClick}
      className={cn(buttonVariants({ variant, size }), 'relative overflow-hidden', className)}
      {...props}
    >
      Ripple Effect
      {ripples.map(ripple => (
        <motion.span
          key={ripple.id}
          initial={{ scale: 0, opacity: 0.7 }}
          animate={{ scale, opacity: 0 }}
          transition={transition}
          className='pointer-events-none absolute size-5 rounded-full bg-current'
          style={{
            top: ripple.y - 12,
            left: ripple.x - 12
          }}
        />
      ))}
    </motion.button>
  )
}

export { RippleEffectButton, type RippleEffectButtonProps }

Installation

npx shadcn@latest add @tailwind-admin/ripple-effect-button

Usage

import { RippleEffectButton } from "@/components/ripple-effect-button"
<RippleEffectButton />