Subscribe
Install
Copy and paste the following code into your project.
"use client";
import type React from "react";
import { useState } from "react";
import { AnimatePresence, motion } from "motion/react";
import { Loader } from "lucide-react";
import { cn } from "@/lib/utils";
function Subscribe() {
const [email, setEmail] = useState("");
const [focus, setFocus] = useState(false);
const [status, setStatus] = useState<"idle" | "loading" | "success">("idle");
const label = "[email protected]";
const letters = label.split("");
const show = focus || email;
const onSubmit = (event: React.FormEvent) => {
event.preventDefault();
setStatus("loading");
setTimeout(() => {
setStatus("success");
}, 3000);
};
return (
<form onSubmit={onSubmit} className="grow flex justify-center">
<div className="relative max-w-96 w-full bg-white">
<label htmlFor="email" className="absolute inset-0 flex items-center">
<span className="flex">
{letters.map((letter, index) => (
<motion.span
aria-hidden
className="inline-block"
key={index}
initial={false}
animate={{
x: 20,
y: show ? -40 : 0,
color: show
? "var(--color-amber-500)"
: "var(--color-neutral-400)",
opacity: status === "success" ? 0 : 1,
}}
transition={{
type: "spring",
duration: 0.4,
delay: index * 0.01,
}}
>
{letter}
</motion.span>
))}
<span className="sr-only">{label}</span>
</span>
</label>
<div
className={cn(
"h-12 w-full transition-[outline] outline-2 flex items-center gap-2 rounded-full pl-5 relative overflow-hidden",
show ? "outline-amber-500" : "outline-neutral-300",
)}
>
<input
id="email"
name="email"
type="email"
title="email"
className="outline-none grow border-none"
placeholder=""
required
autoComplete="off"
spellCheck="false"
value={email}
onChange={(e) => setEmail(e.target.value)}
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
/>
<motion.div
className="absolute"
initial={false}
animate={{
right: 4,
x: show ? "calc(0% + 0px)" : "calc(100% + 4px)",
}}
transition={{ type: "spring", bounce: 0.3 }}
>
<motion.button
layoutId="button"
type="submit"
style={{ borderRadius: 999 }}
className="px-6 h-10 bg-gradient-to-b from-amber-400 to-amber-500 hover:from-amber-500 hover:to-amber-600 font-semibold flex-shrink-0 transition-colors text-white border-none"
>
Subscribe
</motion.button>
</motion.div>
<AnimatePresence mode="popLayout">
{status === "loading" && (
<motion.div
layoutId="button"
className="absolute inset-0 z-10 bg-gradient-to-b from-amber-400 to-amber-500 flex items-center justify-center text-white"
style={{ borderRadius: 999 }}
>
<Loader size={18} className="animate-spin" />
</motion.div>
)}
{status === "success" && (
<div className="absolute inset-0 z-10 bg-gradient-to-b from-amber-400 to-amber-500 flex items-center justify-center font-semibold text-white">
<motion.span
initial={{ y: -20, filter: "blur(4px)" }}
animate={{ y: 0, filter: "blur(0px)" }}
transition={{ type: "spring" }}
>
Your email has been subscribed!
</motion.span>
</div>
)}
</AnimatePresence>
</div>
</div>
</form>
);
}
export { Subscribe };