import React, { forwardRef, ButtonHTMLAttributes } from 'react';
import { clsx } from 'clsx';
export interface ButtonProps extends ButtonHTMLAttributes {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'success' | 'warning';
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
isLoading?: boolean;
isFullWidth?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
loadingText?: string;
}
const Button = forwardRef(({
variant = 'primary',
size = 'md',
isLoading = false,
isFullWidth = false,
leftIcon,
rightIcon,
loadingText,
className,
children,
disabled,
...props
}, ref) => {
const baseClasses = [
'inline-flex items-center justify-center font-medium',
'transition-all duration-200 ease-in-out',
'focus:outline-none focus:ring-2 focus:ring-offset-2',
'disabled:opacity-50 disabled:cursor-not-allowed',
'border rounded-lg'
];
const variantClasses = {
primary: [
'bg-[var(--color-primary)] text-[var(--text-inverse)] border-[var(--color-primary)]',
'hover:bg-[var(--color-primary-dark)] hover:border-[var(--color-primary-dark)]',
'focus:ring-[var(--color-primary)]/20',
'active:bg-[var(--color-primary-dark)]'
],
secondary: [
'bg-[var(--color-secondary)] text-[var(--text-inverse)] border-[var(--color-secondary)]',
'hover:bg-[var(--color-secondary-dark)] hover:border-[var(--color-secondary-dark)]',
'focus:ring-[var(--color-secondary)]/20',
'active:bg-[var(--color-secondary-dark)]'
],
outline: [
'bg-transparent text-[var(--color-primary)] border-[var(--color-primary)]',
'hover:bg-[var(--color-primary)] hover:text-[var(--text-inverse)]',
'focus:ring-[var(--color-primary)]/20',
'active:bg-[var(--color-primary-dark)] active:border-[var(--color-primary-dark)]'
],
ghost: [
'bg-transparent text-[var(--color-primary)] border-transparent',
'hover:bg-[var(--color-primary)]/10 hover:text-[var(--color-primary-dark)]',
'focus:ring-[var(--color-primary)]/20',
'active:bg-[var(--color-primary)]/20'
],
danger: [
'bg-[var(--color-error)] text-[var(--text-inverse)] border-[var(--color-error)]',
'hover:bg-[var(--color-error-dark)] hover:border-[var(--color-error-dark)]',
'focus:ring-[var(--color-error)]/20',
'active:bg-[var(--color-error-dark)]'
],
success: [
'bg-[var(--color-success)] text-[var(--text-inverse)] border-[var(--color-success)]',
'hover:bg-[var(--color-success-dark)] hover:border-[var(--color-success-dark)]',
'focus:ring-[var(--color-success)]/20',
'active:bg-[var(--color-success-dark)]'
],
warning: [
'bg-[var(--color-warning)] text-[var(--text-inverse)] border-[var(--color-warning)]',
'hover:bg-[var(--color-warning-dark)] hover:border-[var(--color-warning-dark)]',
'focus:ring-[var(--color-warning)]/20',
'active:bg-[var(--color-warning-dark)]'
]
};
const sizeClasses = {
xs: 'px-2 py-1 text-xs gap-1 min-h-6',
sm: 'px-3 py-1.5 text-sm gap-1.5 min-h-8 sm:min-h-10', // Better touch target on mobile
md: 'px-4 py-2 text-sm gap-2 min-h-10 sm:min-h-12',
lg: 'px-6 py-2.5 text-base gap-2 min-h-12 sm:min-h-14',
xl: 'px-8 py-3 text-lg gap-3 min-h-14 sm:min-h-16'
};
const loadingSpinner = (
);
const classes = clsx(
baseClasses,
variantClasses[variant],
sizeClasses[size],
{
'w-full': isFullWidth,
'cursor-wait': isLoading
},
className
);
return (
);
});
Button.displayName = 'Button';
export default Button;