An in-browser wisp.place site explorer
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}