{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"shadcnblocks/async-button","title":"Async Button - Async status button","description":"A button that shows idle, loading spinner, success, and error states, with an optional progress bar.","dependencies":["lucide-react","motion","cn"],"registryDependencies":["button"],"files":[{"path":"components/shadcnblocks/async-button.tsx","content":"\"use client\";\n\nimport { Check, Loader2, X } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { cn } from \"cn\";\n\nimport { buttonVariants } from \"@/components/ui/button\";\ntype AsyncButtonVariant = \"default\" | \"secondary\" | \"outline\" | \"ghost\";\ntype AsyncButtonStatus = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nexport interface AsyncButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  variant?: AsyncButtonVariant;\n  status?: AsyncButtonStatus;\n  progress?: number;\n  resetDelay?: number;\n  onReset?: () => void;\n  successIcon?: React.ReactNode;\n  errorIcon?: React.ReactNode;\n}\n\nexport const AsyncButton = ({\n  children,\n  variant = \"default\",\n  status = \"idle\",\n  progress,\n  resetDelay = 2000,\n  onReset,\n  successIcon = <Check className=\"size-4\" />,\n  errorIcon = <X className=\"size-4\" />,\n  className,\n  disabled,\n  ...props\n}: AsyncButtonProps) => {\n  const isLoading = status === \"loading\";\n  const isResult = status === \"success\" || status === \"error\";\n\n  React.useEffect(() => {\n    if (!isResult) return;\n    const timer = window.setTimeout(() => onReset?.(), resetDelay);\n    return () => clearTimeout(timer);\n  }, [isResult, resetDelay, onReset]);\n\n  const showProgress = isLoading && progress !== undefined;\n\n  return (\n    <button\n      type=\"button\"\n      disabled={disabled || isLoading || isResult}\n      className={cn(\n        buttonVariants({ variant, size: \"default\" }),\n        \"relative overflow-hidden\",\n        className,\n      )}\n      {...props}\n    >\n      {/* Progress bar */}\n      {showProgress && (\n        <motion.span\n          className=\"absolute inset-x-0 bottom-0 h-0.5 origin-left bg-primary-foreground/50\"\n          initial={{ scaleX: 0 }}\n          animate={{ scaleX: progress / 100 }}\n          transition={{ duration: 0.3, ease: \"easeOut\" }}\n        />\n      )}\n\n      {/* Content */}\n      <span\n        className={cn(\n          \"flex items-center gap-2 transition-opacity duration-150\",\n          (isLoading || isResult) && \"opacity-0\",\n        )}\n      >\n        {children}\n      </span>\n\n      {/* Loading state */}\n      {isLoading && (\n        <span className=\"absolute inset-0 flex items-center justify-center\">\n          <Loader2 className=\"size-4 animate-spin\" />\n        </span>\n      )}\n\n      {/* Success state */}\n      {status === \"success\" && (\n        <motion.span\n          className=\"absolute inset-0 flex items-center justify-center text-primary-foreground\"\n          initial={{ scale: 0.5, opacity: 0 }}\n          animate={{ scale: 1, opacity: 1 }}\n          transition={{ type: \"spring\", stiffness: 500, damping: 30 }}\n        >\n          {successIcon}\n        </motion.span>\n      )}\n\n      {/* Error state */}\n      {status === \"error\" && (\n        <motion.span\n          className=\"absolute inset-0 flex items-center justify-center text-destructive\"\n          initial={{ scale: 0.5, opacity: 0 }}\n          animate={{ scale: 1, opacity: 1 }}\n          transition={{ type: \"spring\", stiffness: 500, damping: 30 }}\n        >\n          {errorIcon}\n        </motion.span>\n      )}\n    </button>\n  );\n};\n","type":"registry:component"}],"type":"registry:component","meta":{"license":"https://www.shadcnblocks.com/license"}}