{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "status-button",
  "title": "Status Button",
  "author": "jyotirmoydas <jyotirmoydas12345@gmail.com>",
  "description": "A stateful button with animated transitions between normal, loading, success, and error states.",
  "dependencies": ["cn", "motion", "react-icons", "class-variance-authority"],
  "files": [
    {
      "path": "src/registry/status-button/status-button.tsx",
      "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"cn\";\nimport {\n  AnimatePresence,\n  type HTMLMotionProps,\n  motion,\n  MotionConfig,\n  type MotionProps,\n  type Transition,\n} from \"motion/react\";\nimport { TbAlertTriangleFilled, TbCircleCheckFilled } from \"react-icons/tb\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type StatusButtonVariant = \"loading\" | \"error\" | \"success\";\nexport type StatusButtonColor =\n  | \"default\"\n  | \"primary\"\n  | \"secondary\"\n  | \"success\"\n  | \"warning\"\n  | \"danger\";\nexport type StatusButtonRadius = \"none\" | \"sm\" | \"md\" | \"lg\" | \"full\";\n\nexport interface StatusButtonProps extends VariantProps<typeof buttonStyles> {\n  /** The current state variant of the button */\n  variant?: StatusButtonVariant;\n  /** The button content for normal state */\n  children: React.ReactNode;\n  /** Icon to display in normal state */\n  icon?: React.ReactNode;\n  /** Custom spring transition settings */\n  transition?: Transition;\n  /** Additional CSS classes */\n  className?: string;\n  /** Custom text for each state */\n  text?: {\n    error?: string;\n    loading?: string;\n    success?: string;\n  };\n  /** Custom colors for each state (overrides color preset) */\n  colors?: {\n    default?: string;\n    loading?: string;\n    success?: string;\n    error?: string;\n  };\n}\n\n// =============================================================================\n// Animation Configs\n// =============================================================================\n\nconst textAnimation: MotionProps = {\n  variants: {\n    initial: { opacity: 0, x: 40, filter: \"blur(4px)\" },\n    animate: { opacity: 1, x: 0, filter: \"blur(0px)\" },\n    exit: { opacity: 0, x: -40, filter: \"blur(4px)\" },\n  },\n  initial: \"initial\",\n  animate: \"animate\",\n  exit: \"exit\",\n};\n\nconst iconAnimation: MotionProps = {\n  variants: {\n    initial: { opacity: 0, scale: 0, filter: \"blur(4px)\" },\n    animate: { opacity: 1, scale: 1, filter: \"blur(0px)\" },\n    exit: { opacity: 0, scale: 0, filter: \"blur(4px)\" },\n  },\n  initial: \"initial\",\n  animate: \"animate\",\n  exit: \"exit\",\n};\n\n// =============================================================================\n// Styles\n// =============================================================================\n\nconst buttonStyles = cva(\n  \"relative flex h-14 cursor-pointer items-center justify-center gap-3 overflow-hidden px-6 text-center text-xl font-bold text-nowrap transition-colors duration-500 select-none\",\n  {\n    variants: {\n      color: {\n        default: \"\",\n        primary: \"\",\n        secondary: \"\",\n        success: \"\",\n        warning: \"\",\n        danger: \"\",\n      },\n      radius: {\n        none: \"rounded-none\",\n        sm: \"rounded-lg\",\n        md: \"rounded-xl\",\n        lg: \"rounded-2xl\",\n        full: \"rounded-full\",\n      },\n    },\n    defaultVariants: {\n      color: \"default\",\n      radius: \"full\",\n    },\n  }\n);\n\n// Color mappings for each state\nconst colorStyles: Record<\n  StatusButtonColor,\n  Record<\"default\" | \"loading\" | \"success\" | \"error\", string>\n> = {\n  default: {\n    default: \"bg-gray-100 text-black/80\",\n    loading: \"bg-sky-100 text-sky-500\",\n    success: \"bg-green-100 text-green-500\",\n    error: \"bg-rose-100 text-rose-500\",\n  },\n  primary: {\n    default: \"bg-blue-100 text-blue-600\",\n    loading: \"bg-blue-100 text-blue-500\",\n    success: \"bg-green-100 text-green-500\",\n    error: \"bg-rose-100 text-rose-500\",\n  },\n  secondary: {\n    default: \"bg-purple-100 text-purple-600\",\n    loading: \"bg-purple-100 text-purple-500\",\n    success: \"bg-green-100 text-green-500\",\n    error: \"bg-rose-100 text-rose-500\",\n  },\n  success: {\n    default: \"bg-green-100 text-green-600\",\n    loading: \"bg-green-100 text-green-500\",\n    success: \"bg-green-100 text-green-500\",\n    error: \"bg-rose-100 text-rose-500\",\n  },\n  warning: {\n    default: \"bg-amber-100 text-amber-600\",\n    loading: \"bg-amber-100 text-amber-500\",\n    success: \"bg-green-100 text-green-500\",\n    error: \"bg-rose-100 text-rose-500\",\n  },\n  danger: {\n    default: \"bg-rose-100 text-rose-600\",\n    loading: \"bg-rose-100 text-rose-500\",\n    success: \"bg-green-100 text-green-500\",\n    error: \"bg-rose-100 text-rose-500\",\n  },\n};\n\n// Spinner color for each color preset\nconst spinnerColors: Record<StatusButtonColor, string> = {\n  default: \"stroke-sky-400\",\n  primary: \"stroke-blue-400\",\n  secondary: \"stroke-purple-400\",\n  success: \"stroke-green-400\",\n  warning: \"stroke-amber-400\",\n  danger: \"stroke-rose-400\",\n};\n\n// =============================================================================\n// Component\n// =============================================================================\n\n/**\n * StatusButton - An animated button with state transitions\n *\n * @example\n * ```tsx\n * <StatusButton variant=\"loading\" icon={<Icon />} color=\"primary\" radius=\"lg\">\n *   Submit\n * </StatusButton>\n * ```\n */\nfunction StatusButton({\n  variant,\n  text: _text,\n  children,\n  icon,\n  className,\n  color = \"default\",\n  radius = \"full\",\n  colors,\n  transition = { type: \"spring\", duration: 0.6, bounce: 0.4 },\n  style,\n  ...rest\n}: StatusButtonProps & HTMLMotionProps<\"button\">) {\n  const text = {\n    success: \"Success\",\n    loading: \"Loading...\",\n    error: \"Error\",\n    ..._text,\n  };\n\n  const currentState = variant ?? \"default\";\n  const colorPreset = color ?? \"default\";\n\n  // Get color class - use custom colors if provided, otherwise use preset\n  const getColorClass = () => {\n    if (colors?.[currentState]) {\n      return colors[currentState];\n    }\n    return colorStyles[colorPreset][currentState];\n  };\n\n  function renderIcon() {\n    if (variant === \"loading\") {\n      return (\n        <div>\n          <svg\n            className=\"size-6 animate-spin\"\n            width=\"32\"\n            height=\"32\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            style={{\n              animationDuration: \"1s\",\n              animationTimingFunction: \"linear\",\n            }}\n          >\n            <circle\n              cx=\"12\"\n              cy=\"12\"\n              r=\"10\"\n              className=\"stroke-black/10\"\n              strokeWidth=\"4\"\n              fill=\"none\"\n            />\n            <circle\n              cx=\"12\"\n              cy=\"12\"\n              r=\"10\"\n              className={spinnerColors[colorPreset]}\n              strokeWidth=\"4\"\n              fill=\"none\"\n              strokeDasharray=\"62.33185307179586\"\n              strokeDashoffset=\"43.66456772333291\"\n              strokeLinecap=\"round\"\n            />\n          </svg>\n        </div>\n      );\n    } else if (variant === \"error\") {\n      return (\n        <motion.div\n          animate={{\n            x: [0, 6, -6, 0, 6, -6, 0, 6, -6, 0],\n            transition: {\n              duration: 0.4,\n              repeat: Infinity,\n              repeatType: \"loop\",\n              repeatDelay: 1.2,\n            },\n          }}\n        >\n          <TbAlertTriangleFilled className=\"size-6\" />\n        </motion.div>\n      );\n    } else if (variant === \"success\") {\n      return <TbCircleCheckFilled size={24} />;\n    } else {\n      return icon;\n    }\n  }\n\n  function renderText() {\n    if (variant === \"loading\") return text.loading;\n    if (variant === \"error\") return text.error;\n    if (variant === \"success\") return text.success;\n    return children;\n  }\n\n  return (\n    <MotionConfig transition={transition}>\n      <motion.button\n        {...rest}\n        className={cn(buttonStyles({ radius }), getColorClass(), className)}\n        style={style}\n        layout\n      >\n        <AnimatePresence mode=\"popLayout\" initial={false}>\n          {(variant || icon) && (\n            <motion.div\n              key={`${variant}-icon`}\n              {...iconAnimation}\n              layout=\"position\"\n            >\n              {renderIcon()}\n            </motion.div>\n          )}\n          <motion.div\n            key={`${variant}-text`}\n            {...textAnimation}\n            layout=\"position\"\n          >\n            {renderText()}\n          </motion.div>\n        </AnimatePresence>\n      </motion.button>\n    </MotionConfig>\n  );\n}\n\nexport { StatusButton };\n",
      "type": "registry:component"
    }
  ],
  "docs": "https://jyotimoydas.in/components/status-button",
  "type": "registry:component"
}
