Skip to content

Button (components/ui/button.tsx)

Overview

A versatile button component that provides customizable styling, interaction states, and loading functionality.

Features

  • Multiple variants (default, outline, ghost, link)
  • Size options (sm, md, lg)
  • Loading state with spinner
  • Icon support (leading/trailing)
  • Keyboard navigation
  • Disabled state handling
  • Focus management
  • Touch feedback
  • Ripple effect
  • Accessibility support

Dependencies

import * as React from 'react'
import { forwardRef } from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { Spinner } from './spinner'
import { cn } from '@/lib/utils'

Props

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: ButtonVariant;
  size?: ButtonSize;
  loading?: boolean;
  disabled?: boolean;
  leadingIcon?: ReactNode;
  trailingIcon?: ReactNode;
  className?: string;
  children: ReactNode;
  onClick?: () => void;
}

Implementation

State Management

interface ButtonState {
  isPressed: boolean;
  isFocused: boolean;
  ripples: Array<{
    id: number;
    x: number;
    y: number;
  }>;
}

const [buttonState, setButtonState] = useState<ButtonState>({
  isPressed: false,
  isFocused: false,
  ripples: []
});

Methods

const handlePressStart = (event: React.PointerEvent) => {
  setButtonState(prev => ({
    ...prev,
    isPressed: true
  }));

  const rect = event.currentTarget.getBoundingClientRect();
  const ripple = {
    id: Date.now(),
    x: event.clientX - rect.left,
    y: event.clientY - rect.top
  };

  setButtonState(prev => ({
    ...prev,
    ripples: [...prev.ripples, ripple]
  }));
};

const handlePressEnd = () => {
  setButtonState(prev => ({
    ...prev,
    isPressed: false
  }));
};

Unique Functionality

  • Variant styling system
  • Ripple effect animation
  • Size management
  • Loading state handling
  • Icon positioning
  • Focus state management

HTML Structure

<button
  className={cn(
    "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
    getVariantStyles(variant),
    getSizeStyles(size),
    className
  )}
  disabled={disabled || loading}
  ref={ref}
  {...props}
>
  {loading && <Spinner className="mr-2 h-4 w-4 animate-spin" />}
  {leadingIcon && <span className="mr-2">{leadingIcon}</span>}
  {children}
  {trailingIcon && <span className="ml-2">{trailingIcon}</span>}
</button>

API Integration

No direct API integration required.

Components Used

  • Spinner
  • Ripple

Notes

  • Implements keyboard navigation
  • Handles loading states
  • Manages focus states
  • Provides touch feedback
  • Supports accessibility features
  • Clean up ripple effects on unmount