Command Palette

Search for a command to run...

54
Components
PreviousNext

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

PropTypeDefaultDescription
variant"loading" | "success" | "error"undefinedThe current state
childrenReactNodeRequiredButton content for normal state
iconReactNodeundefinedIcon for normal state
text{ loading?: string; success?: string; error?: string }See belowCustom text
transitionTransitionSpring transitionMotion transition settings

Default Text

  • loading: "Loading..."
  • success: "Success"
  • error: "Error"

Credits

Inspired by the Family Button component.