Files
thrilltrack-explorer/src-old/components/common/LoadingGate.tsx

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