An interactive button component designed for payment or transaction interfaces with animated success states and tactile feedback.
"use client";
import { type Variants, AnimatePresence, motion } from "motion/react";
import { Fingerprint, X } from "lucide-react";
import { useState } from "react";
// ease-out-quint for entering elements
const easeOutQuint: [number, number, number, number] = [0.23, 1, 0.32, 1];
const modalVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.06,
delayChildren: 0.08,
},
},
exit: {
opacity: 0,
transition: { duration: 0.15, ease: [0.25, 0.46, 0.45, 0.94] as [number, number, number, number] },
},
};
const itemVariants: Variants = {
hidden: { opacity: 0, y: -8, filter: "blur(4px)" },
visible: {
opacity: 1,
y: 0,
filter: "blur(0px)",
transition: { duration: 0.35, ease: easeOutQuint },
},
exit: {
opacity: 0,
y: -4,
transition: { duration: 0.12 },
},
};
export default function ReceiveConfirmationModal() {
const [isExpanded, setIsExpanded] = useState(false);
const handleReceiveClick = () => setIsExpanded(true);
const handleClose = () => setIsExpanded(false);
const handleCancel = () => setIsExpanded(false);
const handleConfirmReceive = () => {
setIsExpanded(false);
};
return (
<div className="relative flex justify-center items-center">
<AnimatePresence mode="popLayout">
{!isExpanded ? (
<motion.button
key="receive-button"
layoutId="receive-button"
onClick={handleReceiveClick}
className="bg-white hover:bg-neutral-200 flex justify-center items-center cursor-pointer text-neutral-900 font-medium rounded-3xl text-lg w-[300px] h-11 shadow-lg transition-colors"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.25, ease: easeOutQuint }}
whileTap={{ scale: 0.97 }}
>
Receive
</motion.button>
) : (
<motion.div
key="modal"
className="relative bg-linear-to-b from-neutral-900 to-neutral-950 border border-neutral-800 rounded-3xl p-5 w-[430px] shadow-2xl backdrop-blur-xl flex flex-col gap-5"
variants={modalVariants}
initial="hidden"
animate="visible"
exit="exit"
layout
>
{/* Header */}
<motion.div
className="flex flex-row items-center justify-between"
variants={itemVariants}
>
<div className="flex items-center gap-3">
<motion.div
initial={{ scale: 0.8, rotate: -10 }}
animate={{ scale: 1, rotate: 0 }}
transition={{ type: "spring", stiffness: 300, damping: 20, delay: 0.1 }}
className="text-neutral-300 bg-neutral-700/50 h-10 w-10 rounded-full p-2 flex justify-center items-center"
>
<Fingerprint size={24} />
</motion.div>
<h2 className="text-neutral-100 text-lg font-medium tracking-wide">
Confirm
</h2>
</div>
<motion.button
onClick={handleClose}
className="text-neutral-500 hover:text-neutral-300 transition-colors cursor-pointer"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
variants={itemVariants}
>
<X size={22} />
</motion.button>
</motion.div>
{/* Message */}
<motion.p
className="text-neutral-400 text-start leading-relaxed"
variants={itemVariants}
>
Are you sure you want to receive{" "}
<span className="text-neutral-100 font-semibold">
a hell load of money?
</span>
</motion.p>
{/* Buttons */}
<motion.div className="flex gap-4" variants={itemVariants}>
<motion.button
onClick={handleCancel}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
className="flex-1 bg-neutral-800 hover:bg-neutral-700 cursor-pointer h-11 flex justify-center items-center text-neutral-300 font-medium rounded-full border border-neutral-700 transition-colors"
>
Cancel
</motion.button>
<motion.button
layoutId="receive-button"
onClick={handleConfirmReceive}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
className="flex-1 bg-white hover:bg-neutral-200 h-11 flex justify-center items-center cursor-pointer text-neutral-900 font-semibold rounded-full shadow-lg transition-colors"
>
Receive
</motion.button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}