import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { Slot } from "radix-ui" import { cn } from "../../lib/utils" const buttonVariants = cva( // Fills paint to the border box. The 1px transparent border reserves room for // the focus border, and clipping the fill to the padding box would leave a // solid button's ink 2px shorter than the outlined chips and fields it sits // beside on a toolbar. Variants with a visible border clip back to padding. "group/button box-border inline-flex shrink-0 cursor-pointer items-center justify-center rounded-md border border-transparent text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow] duration-150 ease-out outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/80 disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100", outline: // Explicit `text-foreground` on neutral surfaces. Brand bulk bar // overrides to `text-brand-foreground` so outline actions stay // WCAG-legible on `bg-brand` (see BulkSelectionBar). Transparent // fill — no `dark:bg-input/*` wash. Hover paints interactive-hover. "border-input bg-clip-padding bg-transparent text-foreground hover:bg-interactive-hover hover:text-interactive-hover-foreground aria-expanded:bg-interactive-hover aria-expanded:text-interactive-hover-foreground disabled:border-input disabled:bg-transparent disabled:text-muted-foreground disabled:opacity-100", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100", ghost: "text-foreground hover:bg-interactive-hover hover:text-interactive-hover-foreground focus-visible:bg-interactive-hover aria-expanded:bg-interactive-hover disabled:bg-transparent disabled:text-muted-foreground disabled:opacity-100", destructive: "bg-destructive/10 text-destructive-subtle-foreground hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40 disabled:bg-destructive/10 disabled:text-destructive-subtle-foreground/60 disabled:opacity-100", "destructive-solid": "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:border-destructive/40 focus-visible:ring-destructive/40 disabled:bg-destructive/50 disabled:text-destructive-foreground/80 disabled:opacity-100", "destructive-outline": "border-destructive/40 bg-clip-padding bg-transparent text-destructive-subtle-foreground hover:bg-destructive/10 hover:text-destructive-subtle-foreground aria-expanded:bg-destructive/10 disabled:border-destructive/30 disabled:bg-transparent disabled:text-destructive-subtle-foreground/60 disabled:opacity-100", "destructive-ghost": "text-destructive-subtle-foreground hover:bg-destructive/10 hover:text-destructive-subtle-foreground focus-visible:bg-destructive/10 aria-expanded:bg-destructive/10 disabled:bg-transparent disabled:text-destructive-subtle-foreground/60 disabled:opacity-100", "destructive-link": "text-destructive-subtle-foreground underline underline-offset-4 disabled:underline disabled:text-destructive-subtle-foreground/60 disabled:opacity-100", link: "text-primary underline underline-offset-4 disabled:underline disabled:text-muted-foreground disabled:opacity-100", }, size: { default: "h-8 gap-1.5 px-3 has-data-[icon=inline-end]:pe-2.5 has-data-[icon=inline-start]:ps-2.5", xs: "h-6 gap-1 px-2 text-xs has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3", sm: "h-7 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2 [&_svg:not([class*='size-'])]:size-3.5", lg: "h-9 gap-1.5 px-4 has-data-[icon=inline-end]:pe-3.5 has-data-[icon=inline-start]:ps-3.5", icon: "aspect-square size-8 shrink-0 grow-0 p-0", "icon-xs": "aspect-square size-6 shrink-0 grow-0 p-0 [&_svg:not([class*='size-'])]:size-3", "icon-sm": "aspect-square size-8 shrink-0 grow-0 p-0", "icon-lg": "aspect-square size-9 shrink-0 grow-0 p-0", }, }, defaultVariants: { variant: "default", size: "default", }, compoundVariants: [ { variant: "ghost", size: ["default", "xs", "sm", "lg"], class: "bg-interactive-hover-subtle dark:bg-interactive-hover-subtle", }, { variant: "ghost", size: ["icon", "icon-xs", "icon-sm", "icon-lg"], class: "bg-transparent dark:bg-transparent", }, { variant: "destructive-ghost", size: ["default", "xs", "sm", "lg"], class: "bg-destructive/10 dark:bg-destructive/10", }, { variant: "destructive-ghost", size: ["icon", "icon-xs", "icon-sm", "icon-lg"], class: "bg-transparent dark:bg-transparent", }, // Dense icon targets live in overflow-clipped chrome (table cells, // toolbars, rails). Outset `ring-3` gets sliced — paint inside. { size: ["icon", "icon-xs", "icon-sm", "icon-lg"], class: "focus-visible:ring-inset", }, ], }, ) const Button = React.forwardRef< HTMLButtonElement, React.ComponentProps<"button"> & VariantProps & { asChild?: boolean } >(({ className, variant = "default", size = "default", asChild = false, ...props }, ref) => { const Comp = asChild ? Slot.Root : "button" const compactTouch = size === "xs" || size === "sm" || (typeof size === "string" && size.startsWith("icon")) return ( ) }) Button.displayName = "Button" export { Button, buttonVariants }