Ripple Button
Preview
Code
"use client";
import { motion, AnimatePresence } from "framer-motion";
import React, { useState, type FC, type ReactNode } from "react";
import { cn } from "@/lib/utils";
interface Ripple {
x: number;
y: number;
id: number;
}
interface RippleButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
rippleColor?: string;
rippleDuration?: number;
}
const RippleButton: FC<RippleButtonProps> = ({
children,
className,
rippleColor = "rgba(255, 255, 255, 0.7)",
rippleDuration = 0.7,
...props
}) => {
const [ripples, setRipples] = useState<Ripple[]>([]);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const button = event.currentTarget;
const rect = button.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const newRipple = { x, y, id: Date.now() };
setRipples([...ripples, newRipple]);
setTimeout(() => {
setRipples((state) => state.filter((r) => r.id !== newRipple.id));
}, rippleDuration * 1000);
props.onClick?.(event);
};
return (
<button
{...props}
className={cn(
"relative inline-flex items-center justify-center px-6 py-3 rounded-lg overflow-hidden",
"bg-purple-600 text-white hover:bg-purple-700 transition-colors",
className
)}
onClick={handleClick}
>
{children}
<AnimatePresence>
{ripples.map(({ x, y, id }) => (
<motion.span
key={id}
initial={{ opacity: 1, scale: 0 }}
animate={{ opacity: 0, scale: 10 }}
exit={{ opacity: 0 }}
transition={{ duration: rippleDuration }}
style={{
position: "absolute",
left: x,
top: y,
transform: "translate(-50%, -50%)",
borderRadius: "50%",
width: "20px",
height: "20px",
backgroundColor: rippleColor,
pointerEvents: "none",
}}
/>
))}
</AnimatePresence>
</button>
);
};
export default RippleButton;