import type { ElementType, MouseEventHandler } from 'react'; import { Link } from 'react-router-dom'; import type { ComponentSize } from './types'; type ButtonType = 'solid' | 'outlined' | 'noborder'; type ButtonVariant = 'primary' | 'secondary' | 'important'; type NativeButtonType = 'button' | 'submit' | 'reset'; type ButtonProps = { label?: string; type: ButtonType; variant?: ButtonVariant; size?: ComponentSize; width?: ComponentSize; to?: string; htmlType?: NativeButtonType; onClick?: MouseEventHandler; disabled?: boolean; icon?: ElementType; ariaLabel?: string; className?: string; }; const SIZE_CLASS: Record = { sm: 'h-8 px-3 text-xs', md: 'h-10 px-4 text-sm', lg: 'h-12 px-5 text-base', full: 'h-10 px-4 text-sm', }; const ICON_ONLY_SIZE_CLASS: Record = { sm: 'h-8 w-8 !p-0', md: 'h-10 w-10 !p-0', lg: 'h-12 w-12 !p-0', full: 'h-10 w-10 !p-0', }; const ICON_CLASS: Record = { sm: 'h-4 w-4', md: 'h-4 w-4', lg: 'h-5 w-5', full: 'h-4 w-4', }; const ICON_ONLY_CLASS: Record = { sm: 'h-4 w-4', md: 'h-5 w-5', lg: 'h-6 w-6', full: 'h-5 w-5', }; const WIDTH_CLASS: Record = { sm: 'max-w-xs', md: '', lg: 'max-w-md', full: 'w-full max-w-none', }; const TYPE_CLASS: Record = { solid: 'btn-solid', outlined: 'btn-outlined', noborder: 'btn-noborder', }; const VARIANT_CLASS: Record = { primary: 'btn-primary', secondary: 'btn-secondary', important: 'btn-important', }; function resolveVariant(type: ButtonType, variant?: ButtonVariant): ButtonVariant { if (variant) { return variant; } return type === 'solid' ? 'primary' : 'secondary'; } export function Button({ label, type, variant, size = 'md', width = 'md', to, htmlType = 'button', onClick, disabled = false, icon: Icon, ariaLabel, className = '', }: Readonly) { const isIconOnly = Icon != null && !label; const resolvedVariant = resolveVariant(type, variant); const composedClassName = [ TYPE_CLASS[type], VARIANT_CLASS[resolvedVariant], isIconOnly ? ICON_ONLY_SIZE_CLASS[size] : SIZE_CLASS[size], WIDTH_CLASS[width], Icon && label ? 'gap-1.5' : '', disabled ? 'pointer-events-none cursor-not-allowed opacity-45 saturate-50' : '', className, ] .join(' ') .trim(); const computedAriaLabel = ariaLabel ?? label; const iconClass = `${isIconOnly ? ICON_ONLY_CLASS[size] : ICON_CLASS[size]} shrink-0`; const content = ( <> {Icon ?