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 };