92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
import { ReactNode, ButtonHTMLAttributes } from 'react';
|
|
import { getButtonClasses, defaultLayoutConfig, type LayoutConfig } from '~/lib/layout-utils';
|
|
|
|
interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {
|
|
children: ReactNode;
|
|
className?: string;
|
|
config?: Partial<LayoutConfig>;
|
|
variant?: 'primary' | 'secondary' | 'danger' | 'outline' | 'ghost';
|
|
size?: 'sm' | 'md' | 'lg';
|
|
fullWidth?: boolean;
|
|
loading?: boolean;
|
|
icon?: ReactNode;
|
|
iconPosition?: 'start' | 'end';
|
|
}
|
|
|
|
export function Button({
|
|
children,
|
|
className = '',
|
|
config = {},
|
|
variant = 'primary',
|
|
size = 'md',
|
|
fullWidth = false,
|
|
loading = false,
|
|
icon,
|
|
iconPosition = 'start',
|
|
disabled,
|
|
...props
|
|
}: ButtonProps) {
|
|
const layoutConfig = { ...defaultLayoutConfig, ...config };
|
|
|
|
const baseClasses = getButtonClasses(variant as any, size);
|
|
|
|
const variantClasses = {
|
|
primary: 'bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-500 disabled:bg-blue-300',
|
|
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900 focus:ring-gray-500 disabled:bg-gray-100',
|
|
danger: 'bg-red-600 hover:bg-red-700 text-white focus:ring-red-500 disabled:bg-red-300',
|
|
outline: 'border border-gray-300 bg-white hover:bg-gray-50 text-gray-700 focus:ring-blue-500 disabled:bg-gray-50',
|
|
ghost: 'bg-transparent hover:bg-gray-100 text-gray-700 focus:ring-gray-500 disabled:text-gray-400',
|
|
};
|
|
|
|
const fullWidthClass = fullWidth ? 'w-full' : '';
|
|
const disabledClass = (disabled || loading) ? 'cursor-not-allowed opacity-50' : '';
|
|
|
|
const iconSpacing = layoutConfig.direction === 'rtl' ? 'space-x-reverse' : '';
|
|
const iconOrderClass = iconPosition === 'end' ? 'flex-row-reverse' : '';
|
|
|
|
return (
|
|
<button
|
|
className={`${baseClasses} ${variantClasses[variant]} ${fullWidthClass} ${disabledClass} ${iconSpacing} ${iconOrderClass} ${className}`}
|
|
disabled={disabled || loading}
|
|
dir={layoutConfig.direction}
|
|
{...props}
|
|
>
|
|
{loading && (
|
|
<svg
|
|
className="animate-spin -ml-1 mr-3 h-5 w-5 text-current"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
)}
|
|
|
|
{icon && iconPosition === 'start' && !loading && (
|
|
<span className={layoutConfig.direction === 'rtl' ? 'ml-2' : 'mr-2'}>
|
|
{icon}
|
|
</span>
|
|
)}
|
|
|
|
<span>{children}</span>
|
|
|
|
{icon && iconPosition === 'end' && !loading && (
|
|
<span className={layoutConfig.direction === 'rtl' ? 'mr-2' : 'ml-2'}>
|
|
{icon}
|
|
</span>
|
|
)}
|
|
</button>
|
|
);
|
|
} |