hasari-api / packages /ui /src /components /Button.tsx
erdoganpeker's picture
v0.3.0 — multimodal vehicle damage MVP
e327f0d
import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';
import { cn } from '../utils/cn';
import { Spinner } from './Spinner';
export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
export type ButtonSize = 'sm' | 'md' | 'lg';
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
loading?: boolean;
leftIcon?: ReactNode;
rightIcon?: ReactNode;
fullWidth?: boolean;
}
const VARIANT_STYLES: Record<ButtonVariant, string> = {
primary:
'bg-brand-600 text-white hover:bg-brand-700 active:bg-brand-800 focus-visible:ring-brand-500 disabled:bg-brand-300',
secondary:
'bg-white text-slate-900 border border-slate-300 hover:bg-slate-50 active:bg-slate-100 focus-visible:ring-brand-500 disabled:bg-slate-50 disabled:text-slate-400',
ghost:
'bg-transparent text-slate-700 hover:bg-slate-100 active:bg-slate-200 focus-visible:ring-brand-500 disabled:text-slate-400',
danger:
'bg-red-600 text-white hover:bg-red-700 active:bg-red-800 focus-visible:ring-red-500 disabled:bg-red-300',
};
const SIZE_STYLES: Record<ButtonSize, string> = {
sm: 'h-8 px-3 text-xs gap-1.5',
md: 'h-10 px-4 text-sm gap-2',
lg: 'h-12 px-6 text-base gap-2',
};
export const Button = forwardRef<HTMLButtonElement, Props>(function Button(
{
variant = 'primary',
size = 'md',
loading = false,
leftIcon,
rightIcon,
fullWidth,
className,
disabled,
children,
type = 'button',
...rest
},
ref,
) {
const isDisabled = disabled || loading;
return (
<button
ref={ref}
type={type}
disabled={isDisabled}
aria-busy={loading || undefined}
className={cn(
'inline-flex items-center justify-center rounded-md font-semibold transition-colors',
'focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
'disabled:cursor-not-allowed',
VARIANT_STYLES[variant],
SIZE_STYLES[size],
fullWidth && 'w-full',
className,
)}
{...rest}
>
{loading ? (
<Spinner size={size === 'lg' ? 'md' : 'sm'} />
) : (
leftIcon && <span aria-hidden>{leftIcon}</span>
)}
{children}
{!loading && rightIcon && <span aria-hidden>{rightIcon}</span>}
</button>
);
});