import React, { forwardRef, ButtonHTMLAttributes, ReactNode } from 'react';
import { cn } from '../../lib/utils';
import { getViewport, getTouchTargetSize } from '../../lib/responsive';
// Button variants
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
// Button sizes
type ButtonSize = 'sm' | 'md' | 'lg';
// Button props interface
interface ButtonProps extends ButtonHTMLAttributes {
variant?: ButtonVariant;
size?: ButtonSize;
loading?: boolean;
icon?: ReactNode;
iconPosition?: 'left' | 'right';
fullWidth?: boolean;
responsiveSize?: {
mobile?: ButtonSize;
tablet?: ButtonSize;
desktop?: ButtonSize;
};
touchTarget?: boolean;
}
// Helper function to get variant styles
const getVariantStyles = (variant: ButtonVariant, disabled?: boolean) => {
const baseStyles = 'transition-all duration-200 ease-in-out font-medium rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2';
if (disabled) {
return `${baseStyles} bg-gray-300 text-gray-500 cursor-not-allowed opacity-60`;
}
switch (variant) {
case 'primary':
return `${baseStyles} bg-primary hover:bg-primary-dark text-white focus:ring-primary`;
case 'secondary':
return `${baseStyles} bg-secondary hover:bg-secondary-light text-white focus:ring-secondary`;
case 'outline':
return `${baseStyles} bg-transparent border-2 border-primary text-primary hover:bg-primary-light hover:border-primary-dark focus:ring-primary`;
case 'ghost':
return `${baseStyles} bg-transparent text-primary hover:bg-primary-light focus:ring-primary`;
default:
return `${baseStyles} bg-primary hover:bg-primary-dark text-white`;
}
};
// Helper function to get size styles
const getSizeStyles = (size: ButtonSize) => {
switch (size) {
case 'sm':
return 'px-3 py-1.5 text-sm';
case 'md':
return 'px-4 py-2 text-base';
case 'lg':
return 'px-6 py-3 text-lg';
default:
return 'px-4 py-2 text-base';
}
};
// Helper function to get icon spacing
const getIconSpacing = (size: ButtonSize, iconPosition: 'left' | 'right') => {
const spacing = {
sm: iconPosition === 'left' ? 'mr-1.5' : 'ml-1.5',
md: iconPosition === 'left' ? 'mr-2' : 'ml-2',
lg: iconPosition === 'left' ? 'mr-2.5' : 'ml-2.5',
};
return spacing[size];
};
// Loading spinner component
const LoadingSpinner = ({ size }: { size: ButtonSize }) => {
const sizeClasses = {
sm: 'w-4 h-4',
md: 'w-5 h-5',
lg: 'w-6 h-6',
};
return (
);
};
// Main Button component
export const Button = forwardRef(
(
{
variant = 'primary',
size = 'md',
loading = false,
icon,
iconPosition = 'left',
fullWidth = false,
disabled,
className = '',
children,
type = 'button',
responsiveSize,
touchTarget = true,
...props
},
ref
) => {
const isDisabled = disabled || loading;
// Get responsive size if provided
const getResponsiveSize = () => {
if (!responsiveSize) return size;
if (typeof window === 'undefined') return size;
const viewport = getViewport();
if (viewport.isMobile && responsiveSize.mobile) {
return responsiveSize.mobile;
}
if (viewport.isTablet && responsiveSize.tablet) {
return responsiveSize.tablet;
}
if (viewport.isDesktop && responsiveSize.desktop) {
return responsiveSize.desktop;
}
return size;
};
const responsiveSizeValue = getResponsiveSize();
// Get touch target size - fixed for hydration
const getTouchTargetClasses = () => {
if (!touchTarget) return '';
// Always return the same classes to avoid hydration mismatch
// The touch target is a design requirement that should be consistent
return `min-h-[44px] min-w-[44px]`;
};
return (
);
}
);
Button.displayName = 'Button';
// Export types for external use
export type { ButtonProps, ButtonVariant, ButtonSize };