An in-browser wisp.place site explorer
at main 94 lines 2.5 kB view raw
1/** 2 * LoadingState component 3 * 4 * Displays loading indicators with progress stages for the resolution process. 5 */ 6 7export interface LoadingStateProps { 8 stage?: 'resolving' | 'fetching-manifest' | 'loading-site' | 'general'; 9 message?: string; 10 progress?: { 11 current: number; 12 total: number; 13 }; 14} 15 16const stageMessages: Record<string, string> = { 17 resolving: 'Resolving handle...', 18 'fetching-manifest': 'Fetching manifest...', 19 'loading-site': 'Loading site files...', 20 general: 'Loading...', 21}; 22 23export function LoadingState({ stage = 'general', message, progress }: LoadingStateProps) { 24 const displayMessage = message || stageMessages[stage]; 25 26 return ( 27 <div className="flex flex-col items-center justify-center py-12"> 28 {/* Spinner */} 29 <div className="relative mb-4"> 30 <div className="w-12 h-12 border-4 border-sky-200 border-t-sky-600 rounded-full animate-spin" /> 31 </div> 32 33 {/* Message */} 34 <p className="text-gray-600 text-sm font-medium mb-2">{displayMessage}</p> 35 36 {/* Progress indicator (optional) */} 37 {progress && ( 38 <div className="flex items-center gap-2 text-xs text-gray-500"> 39 <span>{progress.current}</span> 40 <span className="text-gray-300">/</span> 41 <span>{progress.total}</span> 42 <span className="ml-1">files</span> 43 </div> 44 )} 45 </div> 46 ); 47} 48 49/** 50 * Compact inline loading indicator 51 */ 52export interface InlineLoadingProps { 53 message?: string; 54 size?: 'sm' | 'md' | 'lg'; 55} 56 57export function InlineLoading({ message, size = 'sm' }: InlineLoadingProps) { 58 const sizeClasses = { 59 sm: 'w-4 h-4 border-2', 60 md: 'w-6 h-6 border-2', 61 lg: 'w-8 h-8 border-3', 62 }; 63 64 return ( 65 <div className="flex items-center gap-2"> 66 <div className={`${sizeClasses[size]} border-gray-300 border-t-gray-600 rounded-full animate-spin`} /> 67 {message && <span className="text-gray-600 text-sm">{message}</span>} 68 </div> 69 ); 70} 71 72/** 73 * Skeleton screen for content placeholder 74 */ 75export interface SkeletonProps { 76 className?: string; 77 lines?: number; 78} 79 80export function Skeleton({ className = '', lines = 3 }: SkeletonProps) { 81 return ( 82 <div className={`space-y-2 ${className}`}> 83 {Array.from({ length: lines }).map((_, i) => ( 84 <div 85 key={i} 86 className="h-4 bg-gray-200 rounded animate-pulse" 87 style={{ 88 width: i === lines - 1 ? '60%' : '100%', 89 }} 90 /> 91 ))} 92 </div> 93 ); 94}