Files
pn-new-crm/src/components/shared/loading-skeleton.tsx

113 lines
3.2 KiB
TypeScript
Raw Normal View History

import { cn } from '@/lib/utils';
import { Skeleton } from '@/components/ui/skeleton';
interface LoadingSkeletonProps {
className?: string;
}
/**
* Table skeleton mimics a data table with header + rows.
*/
export function TableSkeleton({ rows = 6, columns = 5 }: { rows?: number; columns?: number }) {
return (
<div className="w-full space-y-0 border border-border rounded-lg overflow-hidden">
{/* Header row */}
<div className="flex gap-4 px-4 py-3 bg-muted border-b border-border">
{Array.from({ length: columns }).map((_, i) => (
<Skeleton key={i} className={cn('h-4', i === 0 ? 'w-1/4' : 'flex-1')} />
))}
</div>
{/* Data rows */}
{Array.from({ length: rows }).map((_, rowIdx) => (
<div
key={rowIdx}
className={cn(
'flex gap-4 px-4 py-3.5 border-b border-border last:border-0',
rowIdx % 2 === 0 ? 'bg-background' : 'bg-muted/20',
)}
>
{Array.from({ length: columns }).map((_, colIdx) => (
<Skeleton
key={colIdx}
className={cn('h-4', colIdx === 0 ? 'w-1/4' : 'flex-1')}
/>
))}
</div>
))}
</div>
);
}
/**
* Card skeleton mimics a content card.
*/
export function CardSkeleton({ className }: LoadingSkeletonProps) {
return (
<div className={cn('border border-border rounded-lg p-5 space-y-3 bg-background', className)}>
<div className="flex items-center justify-between">
<Skeleton className="h-5 w-1/3" />
<Skeleton className="h-5 w-16 rounded-full" />
</div>
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-4/5" />
<div className="pt-2 flex gap-2">
<Skeleton className="h-8 w-20 rounded-md" />
<Skeleton className="h-8 w-20 rounded-md" />
</div>
</div>
);
}
/**
* Form skeleton mimics a form with labeled inputs.
*/
export function FormSkeleton({ fields = 4 }: { fields?: number }) {
return (
<div className="space-y-5">
{Array.from({ length: fields }).map((_, i) => (
<div key={i} className="space-y-1.5">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-9 w-full rounded-md" />
</div>
))}
<div className="flex gap-3 pt-2">
<Skeleton className="h-9 w-24 rounded-md" />
<Skeleton className="h-9 w-20 rounded-md" />
</div>
</div>
);
}
/**
* Grid skeleton a responsive card grid.
*/
export function GridSkeleton({ cards = 6 }: { cards?: number }) {
return (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: cards }).map((_, i) => (
<CardSkeleton key={i} />
))}
</div>
);
}
/**
* Page-level loading skeleton header + content area.
*/
export function PageSkeleton() {
return (
<div className="space-y-6">
{/* Page header */}
<div className="flex items-center justify-between">
<div className="space-y-1.5">
<Skeleton className="h-7 w-48" />
<Skeleton className="h-4 w-72" />
</div>
<Skeleton className="h-9 w-28 rounded-md" />
</div>
{/* Content */}
<TableSkeleton />
</div>
);
}