114 lines
4.3 KiB
TypeScript
114 lines
4.3 KiB
TypeScript
"use client"
|
|
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { Card } from "@/components/ui/card";
|
|
import { Loader2 } from "lucide-react";
|
|
|
|
interface PageLoadingProps {
|
|
title?: string;
|
|
subtitle?: string;
|
|
itemsCount?: number;
|
|
layout?: 'list' | 'grid' | 'table';
|
|
}
|
|
|
|
export default function PageLoading({
|
|
title = "Loading data...",
|
|
subtitle,
|
|
itemsCount = 5,
|
|
layout = 'list'
|
|
}: PageLoadingProps) {
|
|
return (
|
|
<div className="space-y-6 animate-in fade-in duration-300">
|
|
{/* Header skeleton */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<Skeleton className="h-8 w-72 mb-2" />
|
|
{subtitle && <Skeleton className="h-4 w-96" />}
|
|
</div>
|
|
<Skeleton className="h-10 w-28" />
|
|
</div>
|
|
|
|
{/* Main content skeleton */}
|
|
<Card className="relative overflow-hidden">
|
|
<div className="absolute inset-0 flex items-center justify-center z-10 pointer-events-none">
|
|
<div className="flex flex-col items-center justify-center bg-background/80 backdrop-blur-[2px] p-6 rounded-lg shadow-sm">
|
|
<Loader2 className="h-10 w-10 animate-spin text-primary mb-3" />
|
|
<p className="text-lg font-medium">{title}</p>
|
|
{subtitle && <p className="text-sm text-muted-foreground">{subtitle}</p>}
|
|
</div>
|
|
</div>
|
|
|
|
{layout === 'list' && (
|
|
<div className="p-6 space-y-4 opacity-30">
|
|
{[...Array(itemsCount)].map((_, i) => (
|
|
<div key={i} className="flex items-center gap-4 p-3 border-b last:border-0">
|
|
<Skeleton className="h-12 w-12 rounded-md" />
|
|
<div className="space-y-2 flex-1">
|
|
<Skeleton className="h-4 w-full max-w-[280px]" />
|
|
<Skeleton className="h-4 w-20" />
|
|
</div>
|
|
<div className="ml-auto text-right flex gap-2">
|
|
<Skeleton className="h-9 w-9 rounded-md" />
|
|
<Skeleton className="h-9 w-9 rounded-md" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{layout === 'grid' && (
|
|
<div className="p-6 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 opacity-30">
|
|
{[...Array(itemsCount)].map((_, i) => (
|
|
<Card key={i} className="p-4">
|
|
<Skeleton className="h-32 w-full rounded-md mb-3" />
|
|
<Skeleton className="h-5 w-3/4 mb-2" />
|
|
<Skeleton className="h-4 w-1/2 mb-3" />
|
|
<div className="flex justify-between items-center mt-2">
|
|
<Skeleton className="h-6 w-16" />
|
|
<Skeleton className="h-8 w-8 rounded-full" />
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{layout === 'table' && (
|
|
<div className="w-full opacity-30">
|
|
<div className="border-b px-6 py-4">
|
|
<div className="flex gap-4">
|
|
<Skeleton className="h-4 w-32" />
|
|
<Skeleton className="h-4 w-32" />
|
|
<Skeleton className="h-4 w-32" />
|
|
<Skeleton className="h-4 w-32" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="divide-y">
|
|
{[...Array(itemsCount)].map((_, i) => (
|
|
<div key={i} className="px-6 py-4">
|
|
<div className="flex justify-between items-center">
|
|
<div className="space-y-2 flex-1">
|
|
<Skeleton className="h-4 w-40" />
|
|
<Skeleton className="h-4 w-24" />
|
|
</div>
|
|
<div className="space-y-2 flex-1">
|
|
<Skeleton className="h-4 w-32" />
|
|
<Skeleton className="h-4 w-20" />
|
|
</div>
|
|
<div className="space-y-2 flex-1">
|
|
<Skeleton className="h-4 w-28" />
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Skeleton className="h-8 w-8 rounded-md" />
|
|
<Skeleton className="h-8 w-8 rounded-md" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|