Button Stateful Button
beui/button-statefulFreeComponent
Idle → loading → success / error with blur-swap slots and morphing width.
Install it
npx shadcn@latest add https://beui.dev/r/button-stateful.jsonSource
1"use client";2// beui.dev/components/motion/button34import { Check, Loader2, X } from "lucide-react";5import {6 AnimatePresence,7 motion,8 useReducedMotion,9 type Variants,10} from "motion/react";11import {12 forwardRef,13 type ReactNode,14 useLayoutEffect,15 useRef,16 useState,17} from "react";18import { EASE_OUT, SPRING_SWAP } from "@/lib/ease";19import { Button, type ButtonProps } from "./base";2021export type ButtonState = "idle" | "loading" | "success" | "error";2223export interface StatefulButtonProps extends Omit<ButtonProps, "children"> {24 state?: ButtonState;25 children: ReactNode;26 loadingText?: ReactNode;27 successText?: ReactNode;28 errorText?: ReactNode;29 icon?: ReactNode;30}3132const CASCADE_STAGGER = 0.025;33const ROLL_BLUR = "blur(6px)";3435const CASCADE_LETTER_VARIANTS: Variants = {36 initial: { opacity: 0, y: "105%", filter: ROLL_BLUR },37 animate: (delay: number = 0) => ({38 opacity: 1,39 y: "0%",40 filter: "blur(0px)",41 transition: { ...SPRING_SWAP, delay },42 }),43 exit: (delay: number = 0) => ({44 opacity: 0,45 y: "-105%",46 filter: ROLL_BLUR,47 transition: { duration: 0.16, ease: EASE_OUT, delay: delay * 0.5 },48 }),49};5051const ICON_VARIANTS: Variants = {52 // Width collapses too, so the icon adds/removes its own space smoothly53 // instead of popping the row width in a single frame.54 initial: { opacity: 0, width: 0, scale: 0.7, filter: ROLL_BLUR },55 animate: {56 opacity: 1,57 width: "1.5rem",58 scale: 1,59 filter: "blur(0px)",60 transition: SPRING_SWAP,61 },62 exit: {63 opacity: 0,64 width: 0,65 scale: 0.7,66 filter: ROLL_BLUR,67 transition: { duration: 0.16, ease: EASE_OUT },68 },69};7071function IconSlot({ keyId, children }: { keyId: string; children: ReactNode }) {72 const reduce = useReducedMotion();73 return (74 <motion.span75 key={keyId}76 variants={ICON_VARIANTS}77 initial={reduce ? { opacity: 0 } : "initial"}78 animate={reduce ? { opacity: 1 } : "animate"}79 exit={reduce ? { opacity: 0 } : "exit"}80 transition={reduce ? { duration: 0.15 } : undefined}81 className="inline-grid shrink-0 place-items-center overflow-hidden"82 >83 {children}84 </motion.span>85 );86}8788function TextSlot({89 value,90 children,91}: {92 value: string;93 children: ReactNode;94}) {95 const reduce = useReducedMotion();96 const measureRef = useRef<HTMLSpanElement>(null);97 const [width, setWidth] = useState<number>();98 const label = typeof children === "string" ? children : null;99// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Action Swap Bluraction-swap-blur | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Cascadeaction-swap-cascade | beui | Components | Free | 3 deps · 4 files | |
| Action Swap Rollaction-swap-roll | beui | Components | Free | 3 deps · 4 files | |
| Agent Activityagent-activity | beui | Components | Free | 4 deps · 9 files | |
| Agent Loading States Agent Progressagent-progress | beui | Components | Free | 3 deps · 3 files | |
| AI Sidebarai-sidebar | beui | Components | Free | 4 deps · 5 files | |
| Animated Badgeanimated-badge | beui | Components | Free | 4 deps · 3 files | |
| Number Animation Animated Numberanimated-number | beui | Components | Free | 3 deps · 3 files |
