{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "title": "Button",
  "description": "CoreCN Button primitive built on shadcn/ui.",
  "registryDependencies": [
    "spinner"
  ],
  "files": [
    {
      "path": "registry/corecn/ui/button.tsx",
      "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Spinner } from \"@/registry/corecn/ui/spinner\"\n\nconst buttonVariants = cva(\n  \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n        outline:\n          \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost:\n          \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n        icon: \"size-9\",\n        \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\": \"size-8\",\n        \"icon-lg\": \"size-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\ntype ButtonOwnProps = {\n  asChild?: boolean\n  loading?: boolean\n  loadingIcon?: React.ReactNode\n  loadingText?: string\n  icon?: React.ReactNode\n  iconPlacement?: \"start\" | \"end\"\n}\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> &\n  ButtonOwnProps\n\ntype IconOnlyNameProps =\n  | { \"aria-label\": string; \"aria-labelledby\"?: never }\n  | { \"aria-labelledby\": string; \"aria-label\"?: never }\n\ntype ButtonIconOnlyProps = Omit<ButtonProps, \"icon\" | \"children\" | \"loadingText\"> &\n  IconOnlyNameProps & {\n    icon: React.ReactNode\n    children?: undefined\n    loadingText?: undefined\n  }\n\nfunction DefaultLoadingIcon() {\n  return (\n    <Spinner aria-hidden=\"true\" aria-label={undefined} role=\"presentation\" />\n  )\n}\n\nfunction ButtonAdornment({\n  placement,\n  children,\n}: {\n  placement: \"start\" | \"end\"\n  children: React.ReactNode\n}) {\n  return (\n    <span\n      data-slot=\"button-icon\"\n      data-icon={placement === \"end\" ? \"inline-end\" : \"inline-start\"}\n      aria-hidden=\"true\"\n      className=\"inline-flex shrink-0 items-center justify-center\"\n    >\n      {children}\n    </span>\n  )\n}\n\n/**\n * Button with optional `icon` and `loading` UI.\n *\n * - `disabled` wins over `loading`: native disabled, no `aria-busy`, no spinner.\n * - `loadingIcon` / `loadingText` have no effect unless `loading` is true.\n * - While loading: `aria-busy`, `aria-disabled`, `pointer-events-none`, and\n *   `type=\"button\"` so a submit control cannot post the form. Focus stays in\n *   tab order. `onClick` is not intercepted here (this file stays a Server\n *   Component); gate the handler with `loading` at the call site.\n * - No `aria-live`: `aria-busy` is enough. A live region would re-announce the\n *   control whenever loading toggles.\n * - `asChild`: icon/spinner/`loadingText` are injected into the slotted child's\n *   children via `Slot.Slottable` so Radix still receives a single host element.\n * - Icon-only (`icon` with no `children` / `loadingText`) requires `aria-label`\n *   or `aria-labelledby`.\n */\nfunction Button(props: ButtonIconOnlyProps): React.JSX.Element\nfunction Button(props: ButtonProps): React.JSX.Element\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  asChild = false,\n  loading = false,\n  loadingIcon,\n  loadingText,\n  icon,\n  iconPlacement = \"start\",\n  disabled,\n  children,\n  type,\n  \"aria-busy\": ariaBusy,\n  \"aria-disabled\": ariaDisabled,\n  ...props\n}: ButtonProps) {\n  const Comp = asChild ? Slot.Root : \"button\"\n  const isDisabled = Boolean(disabled)\n  const isLoading = Boolean(loading) && !isDisabled\n  const adornment = isLoading ? (loadingIcon ?? <DefaultLoadingIcon />) : icon\n  const placement = icon ? iconPlacement : \"start\"\n  const replaceSlottedLabel = isLoading && loadingText != null\n  const label = replaceSlottedLabel ? loadingText : children\n  const slottedChild =\n    asChild && replaceSlottedLabel && React.isValidElement(children)\n      ? React.cloneElement(\n          children as React.ReactElement<{ children?: React.ReactNode }>,\n          undefined,\n          loadingText\n        )\n      : children\n  const injectSlotContent = asChild && (Boolean(adornment) || replaceSlottedLabel)\n\n  const content = (\n    <>\n      {adornment && placement === \"start\" ? (\n        <ButtonAdornment placement=\"start\">{adornment}</ButtonAdornment>\n      ) : null}\n      {asChild ? <Slot.Slottable>{slottedChild}</Slot.Slottable> : label}\n      {adornment && placement === \"end\" ? (\n        <ButtonAdornment placement=\"end\">{adornment}</ButtonAdornment>\n      ) : null}\n    </>\n  )\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      data-loading={isLoading ? \"true\" : undefined}\n      data-icon-placement={adornment ? placement : undefined}\n      className={cn(\n        buttonVariants({ variant, size, className }),\n        isLoading && \"pointer-events-none\"\n      )}\n      disabled={isDisabled || undefined}\n      aria-busy={isLoading ? true : ariaBusy}\n      aria-disabled={isLoading ? true : ariaDisabled}\n      type={asChild ? type : isLoading ? \"button\" : type}\n      {...props}\n    >\n      {asChild && !injectSlotContent ? children : content}\n    </Comp>\n  )\n}\n\nexport { Button, buttonVariants }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}