mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 12:31:26 -05:00
115 lines
3.0 KiB
TypeScript
115 lines
3.0 KiB
TypeScript
import { ReactNode } from 'react';
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
|
import { Loader2, AlertCircle } from 'lucide-react';
|
|
|
|
interface LoadingGateProps {
|
|
/** Whether data is still loading */
|
|
isLoading: boolean;
|
|
|
|
/** Optional error to display */
|
|
error?: Error | null;
|
|
|
|
/** Content to render when loaded */
|
|
children: ReactNode;
|
|
|
|
/** Loading variant */
|
|
variant?: 'skeleton' | 'spinner' | 'card';
|
|
|
|
/** Number of skeleton items (for skeleton variant) */
|
|
skeletonCount?: number;
|
|
|
|
/** Custom loading message */
|
|
loadingMessage?: string;
|
|
|
|
/** Custom error message */
|
|
errorMessage?: string;
|
|
}
|
|
|
|
/**
|
|
* Reusable loading and error state wrapper
|
|
*
|
|
* Handles common loading patterns:
|
|
* - Skeleton loaders
|
|
* - Spinner with message
|
|
* - Card-based loading states
|
|
* - Error display
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <LoadingGate isLoading={loading} error={error} variant="skeleton" skeletonCount={3}>
|
|
* <YourContent />
|
|
* </LoadingGate>
|
|
* ```
|
|
*/
|
|
export function LoadingGate({
|
|
isLoading,
|
|
error,
|
|
children,
|
|
variant = 'skeleton',
|
|
skeletonCount = 3,
|
|
loadingMessage = 'Loading...',
|
|
errorMessage,
|
|
}: LoadingGateProps) {
|
|
// Error state
|
|
if (error) {
|
|
return (
|
|
<Alert variant="destructive">
|
|
<AlertCircle className="h-4 w-4" />
|
|
<AlertTitle>Error</AlertTitle>
|
|
<AlertDescription>
|
|
{errorMessage || error.message || 'An unexpected error occurred'}
|
|
</AlertDescription>
|
|
</Alert>
|
|
);
|
|
}
|
|
|
|
// Loading state
|
|
if (isLoading) {
|
|
switch (variant) {
|
|
case 'spinner':
|
|
return (
|
|
<div className="flex flex-col items-center justify-center py-12 gap-4">
|
|
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
|
<p className="text-sm text-muted-foreground">{loadingMessage}</p>
|
|
</div>
|
|
);
|
|
|
|
case 'card':
|
|
return (
|
|
<Card>
|
|
<CardContent className="p-6 space-y-3">
|
|
{Array.from({ length: skeletonCount }).map((_, i) => (
|
|
<div key={i} className="flex items-center gap-4 p-3 border rounded-lg">
|
|
<Skeleton className="h-10 w-10 rounded-full" />
|
|
<div className="flex-1 space-y-2">
|
|
<Skeleton className="h-4 w-2/3" />
|
|
<Skeleton className="h-3 w-1/2" />
|
|
</div>
|
|
<Skeleton className="h-8 w-24" />
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
|
|
case 'skeleton':
|
|
default:
|
|
return (
|
|
<div className="space-y-3">
|
|
{Array.from({ length: skeletonCount }).map((_, i) => (
|
|
<div key={i} className="space-y-2">
|
|
<Skeleton className="h-4 w-3/4" />
|
|
<Skeleton className="h-3 w-1/2" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
// Loaded state
|
|
return <>{children}</>;
|
|
}
|