Status Button
ericts-ui/status-buttonFreeComponent
A shadcn Button-based action that transitions through idle, loading, and success states.
Live preview
live · rendered by the registry
Rendered by ericts-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.ericts.com/r/status-button.jsonSource
1"use client";23import * as React from "react";4import { AnimatePresence, motion, useReducedMotion } from "motion/react";56import { Button } from "@/components/ui/button";7import { cn } from "@/lib/utils";89type ButtonProps = React.ComponentPropsWithoutRef<typeof Button>;10type ButtonClickEvent = Parameters<NonNullable<ButtonProps["onClick"]>>[0];11type ButtonState = "idle" | "loading" | "success";1213export type StatusButtonProps = Omit<ButtonProps, "children" | "onClick"> & {14 idleLabel?: React.ReactNode;15 loadingLabel?: React.ReactNode;16 successLabel?: React.ReactNode;17 loadingDuration?: number;18 successDuration?: number;19 onClick?: (event: ButtonClickEvent) => void | Promise<void>;20};2122function Spinner({ className }: { className?: string }) {23 return (24 <span25 aria-hidden="true"26 className={cn(27 "size-4 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70",28 className29 )}30 />31 );32}3334export function StatusButton({35 idleLabel = "Send me a login link",36 loadingLabel = <Spinner />,37 successLabel = "Login link sent!",38 loadingDuration = 1750,39 successDuration = 1750,40 disabled,41 className,42 onClick,43 type = "button",44 ...props45}: StatusButtonProps) {46 const [buttonState, setButtonState] = React.useState<ButtonState>("idle");47 const shouldReduceMotion = useReducedMotion();48 const timers = React.useRef<ReturnType<typeof setTimeout>[]>([]);49 const isMountedRef = React.useRef(true);5051 React.useEffect(() => {52 isMountedRef.current = true;53 return () => {54 isMountedRef.current = false;55 timers.current.forEach(clearTimeout);56 };57 }, []);5859 const clearTimers = React.useCallback(() => {60 timers.current.forEach(clearTimeout);61 timers.current = [];62 }, []);6364 const handleClick = React.useCallback(65 async (event: ButtonClickEvent) => {66 if (buttonState !== "idle") return;6768 setButtonState("loading");69 clearTimers();7071 try {72 await onClick?.(event);73 } catch {74 if (!isMountedRef.current) return;75 setButtonState("idle");76 return;77 }7879 if (!isMountedRef.current) return;8081 timers.current = [82 setTimeout(() => {83 setButtonState("success");84 }, loadingDuration),85 setTimeout(() => {86 setButtonState("idle");87 }, loadingDuration + successDuration),88 ];89 },90 [buttonState, clearTimers, loadingDuration, onClick, successDuration]91 );9293// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from ericts-ui
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Adaptive Draweradaptive-drawer | ericts-ui | Components | Free | 2 deps | |
| Check Animationcheck-animation | ericts-ui | Components | Free | no deps · 2 files | |
| Context Cursorcontext-cursor | ericts-ui | Components | Free | 1 dep | |
| Copy Buttoncopy-button | ericts-ui | Components | Free | 2 deps | |
| Expandable Dialogexpandable-modal | ericts-ui | Components | Free | 1 dep | |
| Expandable Tabsexpandable-tabs | ericts-ui | Components | Free | 1 dep | |
| Expandable Toolbarexpandable-toolbar | ericts-ui | Components | Free | 1 dep | |
| Expanding Panelexpanding-panel | ericts-ui | Components | Free | 2 deps |
