{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"button","type":"registry:ui","title":"Button","description":"shadcn-style button with six variants, a tactile press, icon slots and a loading spinner that slides the label aside.","dependencies":["lucide-react"],"registryDependencies":["utils"],"files":[{"path":"registry/ui/button.tsx","type":"registry:ui","content":"\"use client\";\n\nimport type { ButtonHTMLAttributes, ReactElement, ReactNode } from \"react\";\nimport { Loader2 } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ButtonVariant = \"default\" | \"secondary\" | \"outline\" | \"ghost\" | \"destructive\" | \"link\";\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\" | \"icon\";\n\nconst variants: Record<ButtonVariant, string> = {\n  default:\n    \"bg-primary text-primary-foreground shadow-sm shadow-primary/20 hover:bg-primary/90 hover:shadow-md hover:shadow-primary/25\",\n  secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n  outline: \"border border-border bg-background text-foreground shadow-xs hover:bg-muted\",\n  ghost: \"text-foreground hover:bg-muted\",\n  destructive: \"bg-destructive text-white shadow-sm shadow-destructive/20 hover:bg-destructive/90 focus-visible:ring-destructive/50\",\n  link: \"text-primary underline-offset-4 hover:underline active:scale-100\",\n};\n\nconst sizes: Record<ButtonSize, string> = {\n  sm: \"h-8 rounded-md px-3 text-xs [--btn-gap:0.375rem] [&_svg]:size-3.5\",\n  md: \"h-10 rounded-lg px-4 text-sm [--btn-gap:0.5rem] [&_svg]:size-4\",\n  lg: \"h-12 rounded-xl px-6 text-base [--btn-gap:0.625rem] [&_svg]:size-5\",\n  icon: \"size-10 rounded-lg [--btn-gap:0px] [&_svg]:size-4\",\n};\n\n/** Class string for styling other elements (e.g. a router Link) exactly like a Button. */\nexport function buttonVariants({\n  variant = \"default\",\n  size = \"md\",\n  className,\n}: { variant?: ButtonVariant; size?: ButtonSize; className?: string } = {}) {\n  return cn(\n    \"relative inline-flex shrink-0 gap-(--btn-gap) select-none items-center justify-center whitespace-nowrap font-medium outline-none\",\n    \"transition-[background-color,box-shadow,color,scale,opacity] duration-150 active:scale-[0.96] motion-reduce:transition-none\",\n    \"focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-busy:cursor-progress aria-busy:disabled:opacity-80\",\n    \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n    variants[variant],\n    sizes[size],\n    className,\n  );\n}\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n  variant?: ButtonVariant;\n  size?: ButtonSize;\n  /** Shows a spinner, disables the button and sets aria-busy. */\n  loading?: boolean;\n  /** Icon before the label. Replaced by the spinner while loading. */\n  startIcon?: ReactNode;\n  /** Icon after the label. */\n  endIcon?: ReactNode;\n  /**\n   * Render a different element (e.g. a link) with the button's classes and content:\n   * `render={(p) => <Link href=\"/pricing\" {...p} />}`.\n   */\n  render?: (props: { className: string; children: ReactNode }) => ReactElement;\n}\n\nexport function Button({\n  variant = \"default\",\n  size = \"md\",\n  loading = false,\n  startIcon,\n  endIcon,\n  render,\n  disabled,\n  className,\n  children,\n  type = \"button\",\n  ...props\n}: ButtonProps) {\n  const content = (\n    <>\n      {/* Leading slot animates its width open when the spinner appears, so the label slides instead of jumping. */}\n      <span\n        aria-hidden\n        className={cn(\n          \"grid transition-[grid-template-columns,margin,opacity] duration-200 ease-out motion-reduce:transition-none\",\n          startIcon || loading ? \"grid-cols-[1fr] opacity-100\" : \"-me-(--btn-gap) grid-cols-[0fr] opacity-0\",\n        )}\n      >\n        <span className=\"grid min-w-0 place-items-center overflow-hidden\">\n          {loading ? <Loader2 className=\"animate-spin\" /> : startIcon}\n        </span>\n      </span>\n      {/* Icon buttons swap their icon for the spinner instead of growing. */}\n      {size === \"icon\" && loading ? null : children}\n      {endIcon && (\n        <span aria-hidden className=\"grid place-items-center\">\n          {endIcon}\n        </span>\n      )}\n    </>\n  );\n\n  const cls = buttonVariants({ variant, size, className });\n  if (render) return render({ className: cls, children: content });\n\n  return (\n    <button {...props} type={type} disabled={disabled || loading} aria-busy={loading || undefined} className={cls}>\n      {content}\n    </button>\n  );\n}\n"}]}