"use client" import { cva, type VariantProps } from 'class-variance-authority'; import { Loader2 } from 'lucide-react'; import * as React from 'react'; import { Slot } from '@radix-ui/react-slot'; import { cn } from '../../../lib/utils'; import { Link } from '../../navigation/link'; const buttonVariants = cva( "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-[var(--radius)] text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2", sm: "h-8 rounded-[var(--radius)] px-3 text-sm", xs: "h-7 rounded px-2 py-1 text-xs", lg: "h-11 rounded-[var(--radius)] px-8", huge: "h-14 rounded-[var(--radius)] px-10 text-lg", icon: "h-9 w-9", }, // Most variants ship a baked-in `shadow`/`shadow-sm` for a lifted look. // `elevated={false}` flattens that (e.g. controls grouped inside a card — // a table's pagination — where a per-button shadow fights the panel). // `shadow-none` wins over the variant's shadow: both set `--tw-shadow` // and this class is appended after the variant in the utility order. elevated: { true: "", false: "shadow-none", }, }, defaultVariants: { variant: "default", size: "default", elevated: true, }, } ) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean loading?: boolean } // Filter out SVG icons from children when loading function filterIcons(children: React.ReactNode): React.ReactNode { return React.Children.map(children, (child) => { if (!React.isValidElement(child)) return child; const type = child.type; // Skip native svg or ForwardRef components (Lucide icons) if (type === 'svg' || (typeof type === 'object' && type !== null)) { return null; } return child; }); } const Button = React.forwardRef( ({ className, variant, size, elevated, asChild = false, loading = false, onClick, children, disabled, ...props }, ref) => { const handleClick = (e: React.MouseEvent) => { if (!loading) { onClick?.(e); } }; // When asChild is true, Slot expects exactly ONE child element // Don't render loading spinner with asChild to avoid "Children.only" error if (asChild) { return ( {children} ) } return ( ) } ) Button.displayName = "Button" /** * ButtonLink — anchor styled as a Button. * * Renders the framework-agnostic `` underneath, so SPA navigation, * cmd-click, and the Next.js adapter (if mounted) all work out of the box. * Drop-in replacement for the previous native-`` version. */ /** * ButtonLink — anchor styled as a Button. * * **Disabled / loading.** An `` has no native `disabled`, so we emulate it * the shadcn-recommended way: `aria-disabled` + `tabIndex={-1}` + * `pointer-events-none opacity-50`, and we cancel the click. `loading` adds the * same inert state plus a spinner — keeping parity with `