/** * @factory/ui — Button (thin Radix Slot + clsx wrapper). * * All colors come from design tokens (`var(--…)`); no hard-coded hex. * `asChild` delegates rendering to a single child (Radix Slot). */ import * as React from "react"; import * as Slot from "@radix-ui/react-slot"; import { clsx } from "clsx"; export type ButtonVariant = "primary" | "secondary" | "ghost" | "destructive" | "danger"; export type ButtonSize = "sm" | "md" | "lg"; const VARIANT: Record = { primary: "bg-primary text-primary-foreground hover:opacity-90", secondary: "bg-secondary text-foreground hover:bg-accent", ghost: "bg-transparent text-foreground hover:bg-accent", destructive: "bg-destructive text-destructive-foreground hover:opacity-90", danger: "bg-transparent text-destructive hover:bg-destructive/10", }; const SIZE: Record = { sm: "h-7 px-2.5 text-xs", md: "h-9 px-3.5 text-sm", lg: "h-11 px-5 text-base", }; export interface ButtonProps extends React.ButtonHTMLAttributes { variant?: ButtonVariant; size?: ButtonSize; /** Render as the single child element (Radix Slot). */ asChild?: boolean; } export const Button = React.forwardRef( ( { variant = "primary", size = "md", asChild = false, className, type = "button", ...props }, ref, ) => { const classes = clsx( "inline-flex items-center justify-center gap-2 rounded-md font-medium", "transition-colors select-none cursor-pointer", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", "disabled:pointer-events-none disabled:opacity-50", VARIANT[variant], SIZE[size], className, ); if (asChild) { return ( {/* Slot renders its single child instead of a