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