# All files / src/components/ui Button.tsx

100% Statements 5/5
100% Branches 7/7
100% Functions 1/1
100% Lines 5/5

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57

    3x

    3x

    3x

    96x

    3x

     import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';

    type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';

    interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
      variant?: ButtonVariant;
      loading?: boolean;
      children: ReactNode;
      size?: 'sm' | 'md' | 'lg';
    }

    const variantClasses: Record<ButtonVariant, string> = {
      primary: 'bg-accent text-white hover:bg-accent-hover shadow-button hover:shadow-button-hover hover:-translate-y-0.5',
      secondary: 'bg-surface-secondary text-text-primary hover:bg-gray-200',
      ghost: 'bg-transparent text-text-secondary hover:text-text-primary hover:bg-gray-100',
      danger: 'bg-red text-white hover:bg-red-hover',
      link: 'bg-transparent text-accent hover:underline p-0 shadow-none',
    };

    const sizeClasses: Record<string, string> = {
      sm: 'px-3 py-1.5 text-xs',
      md: 'px-5 py-2 text-sm',
      lg: 'px-7 py-2.5 text-base',
    };

    const Button = forwardRef<HTMLButtonElement, ButtonProps>(
      ({ variant = 'primary', size = 'md', loading, disabled, className = '', children, ...props }, ref) => {
        return (
          <button
            ref={ref}
            disabled={disabled || loading}
            className={`
              inline-flex items-center justify-center gap-1.5
              font-medium rounded-button transition-button
              disabled:opacity-50 disabled:cursor-not-allowed
              ${variantClasses[variant]}
              ${sizeClasses[size]}
              ${className}
            `}
            {...props}
          >
            {loading && (
              <svg className="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
                <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 12h4z" />
              </svg>
            )}
            {children}
          </button>
        );
      }
    );

    Button.displayName = 'Button';
    export { Button };
    export type { ButtonProps };