All files / src/components/ui LoadingSpinner.tsx

0% Statements 0/52
0% Branches 0/1
0% Functions 0/1
0% Lines 0/52

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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107                                                                                                                                                                                                                     
import { cn } from '../../lib/utils';
import { Loader2 } from 'lucide-react';
 
interface LoadingSpinnerProps {
  /** Size of the spinner */
  size?: 'sm' | 'md' | 'lg';
  /** Optional text to display below the spinner */
  text?: string;
  /** Additional class names */
  className?: string;
}
 
const sizeClasses = {
  sm: 'w-4 h-4',
  md: 'w-6 h-6',
  lg: 'w-8 h-8',
};
 
/**
 * Small inline loading spinner for buttons and inline contexts.
 */
export function LoadingSpinner({ size = 'md', text, className }: LoadingSpinnerProps) {
  return (
    <div className={cn('flex items-center gap-2', className)}>
      <Loader2 className={cn('animate-spin text-gray-400 dark:text-gray-500', sizeClasses[size])} />
      {text && <span className="text-sm text-gray-500 dark:text-gray-400">{text}</span>}
    </div>
  );
}
 
interface LoadingOverlayProps {
  /** Whether the overlay is visible */
  visible: boolean;
  /** Optional text to display */
  text?: string;
  /** Additional class names */
  className?: string;
}
 
/**
 * Full-screen loading overlay for blocking interactions during loading.
 */
export function LoadingOverlay({ visible, text = 'Loading...', className }: LoadingOverlayProps) {
  if (!visible) return null;
 
  return (
    <div
      className={cn(
        'absolute inset-0 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm',
        'flex items-center justify-center z-50',
        className
      )}
    >
      <div className="flex flex-col items-center gap-3">
        <Loader2 className="w-8 h-8 animate-spin text-orange-500" />
        <span className="text-sm text-gray-600 dark:text-gray-300">{text}</span>
      </div>
    </div>
  );
}
 
interface LoadingDotsProps {
  /** Additional class names */
  className?: string;
}
 
/**
 * Animated dots for "thinking" states.
 */
export function LoadingDots({ className }: LoadingDotsProps) {
  return (
    <div className={cn('flex items-center gap-1', className)}>
      <span
        className="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce"
        style={{ animationDelay: '0ms' }}
      />
      <span
        className="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce"
        style={{ animationDelay: '150ms' }}
      />
      <span
        className="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce"
        style={{ animationDelay: '300ms' }}
      />
    </div>
  );
}
 
interface InlineLoadingProps {
  /** Loading text */
  text?: string;
  /** Additional class names */
  className?: string;
}
 
/**
 * Compact inline loading indicator with text.
 */
export function InlineLoading({ text = 'Loading...', className }: InlineLoadingProps) {
  return (
    <div className={cn('flex items-center gap-2 px-4 py-3 text-gray-500 dark:text-gray-400', className)}>
      <LoadingDots />
      <span className="text-sm">{text}</span>
    </div>
  );
}