Status Button
A stateful button with animated transitions between normal, loading, success, and error states.
Loading...
"use client";
import { motion } from "motion/react";
import { useCallback, useEffect, useState } from "react";
import { TbPlus } from "react-icons/tb";
import { cn } from "@/lib/utils";
import {
StatusButton,
type StatusButtonVariant,
} from "@/components/jyotirmoydas/status-button";
// =============================================================================
// Demo Data
// =============================================================================
interface TabItem {
name: string;
value: StatusButtonVariant | undefined;
}
const TABS: TabItem[] = [
{ name: "Normal", value: undefined },
{ name: "Loading", value: "loading" },
{ name: "Success", value: "success" },
{ name: "Error", value: "error" },
];
// =============================================================================
// Demo Component
// =============================================================================
export function StatusButtonDemo() {
const [tab, setTab] = useState(TABS[0]);
const handleNext = useCallback(() => {
setTab((prev) => {
const currentIndex = TABS.findIndex((t) => t.value === prev.value);
const nextIndex = (currentIndex + 1) % TABS.length;
return TABS[nextIndex];
});
}, []);
useEffect(() => {
const interval = setInterval(handleNext, 5000);
return () => clearInterval(interval);
}, [handleNext]);
return (
<div className="flex flex-col items-center gap-6 py-8">
<StatusButton
icon={<TbPlus strokeWidth={3.2} className="size-5" />}
variant={tab.value}
onClick={handleNext}
text={{
loading: "Processing...",
success: "Complete",
error: "Failed",
}}
>
Submit
</StatusButton>
<div className="flex items-center justify-center gap-3 text-black/60">
{TABS.map((i) => (
<button
onClick={() => setTab(i)}
className={cn(
"relative flex flex-1 cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-full bg-gray-100 px-4 py-2 text-sm font-semibold transition-colors",
tab.value === i.value && "bg-gray-200"
)}
key={i.name}
>
{tab.value === i.value && (
<motion.div
className="absolute inset-0 bg-white/15 mix-blend-difference"
initial={{ clipPath: "inset(0 100% 0 0)" }}
animate={{ clipPath: "inset(0 0% 0 0)" }}
transition={{ duration: 5 }}
/>
)}
<span>{i.name}</span>
</button>
))}
</div>
</div>
);
}Features
- Four animated states: Normal, Loading, Success, and Error
- Smooth blur transitions: Text and icons blur in/out during state changes
- Spring animations: Natural, bouncy transitions using Motion for React
- Customizable text: Override default text for each state
- Icon support: Show custom icons in normal state
Installation
bunx --bun shadcn@latest add "https://jyotimoydas.in/r/status-button.json"
Usage
import { StatusButton } from "@/registry/status-button";
import { TbSend } from "react-icons/tb";
export function Example() {
const [status, setStatus] = useState<"loading" | "success" | "error">();
const handleClick = async () => {
setStatus("loading");
try {
await someApiCall();
setStatus("success");
} catch {
setStatus("error");
}
};
return (
<StatusButton
variant={status}
icon={<TbSend />}
text={{
loading: "Sending...",
success: "Sent!",
error: "Failed",
}}
onClick={handleClick}
>
Send Message
</StatusButton>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "loading" | "success" | "error" | undefined | The current state |
children | ReactNode | Required | Button content for normal state |
icon | ReactNode | undefined | Icon for normal state |
text | { loading?: string; success?: string; error?: string } | See below | Custom text |
transition | Transition | Spring transition | Motion transition settings |
Default Text
loading: "Loading..."success: "Success"error: "Error"
Credits
Inspired by the Family Button component.