feat: Add skeleton loaders for improved loading states in studies and participants pages

- Implemented skeleton components for studies list, study details, and participants table
- Enhanced loading experience by replacing simple "Loading..." placeholders with detailed skeleton loaders
- Created reusable skeleton components in `src/components/ui/skeleton.tsx`
- Updated studies and participants pages to use new skeleton loaders
This commit is contained in:
2025-02-12 00:03:24 -05:00
parent 6e3f2e1601
commit ec4d8db16e
6 changed files with 226 additions and 25 deletions

View File

@@ -18,6 +18,7 @@ import { ROLES } from "~/lib/permissions/constants";
import { Switch } from "~/components/ui/switch";
import { Label } from "~/components/ui/label";
import { useState } from "react";
import { TableSkeleton } from "~/components/ui/skeleton";
interface StudyParticipantsProps {
studyId: number;
@@ -38,7 +39,21 @@ export function StudyParticipants({ studyId, role }: StudyParticipantsProps) {
.includes(role.toLowerCase());
if (isLoading) {
return <div>Loading...</div>;
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>Study Participants</CardTitle>
<CardDescription>Loading participants...</CardDescription>
</div>
</div>
</CardHeader>
<CardContent>
<TableSkeleton />
</CardContent>
</Card>
);
}
return (

View File

@@ -1,4 +1,5 @@
import { cn } from "~/lib/utils"
import { Card, CardContent, CardHeader } from "~/components/ui/card"
function Skeleton({
className,
@@ -6,10 +7,132 @@ function Skeleton({
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-primary/10", className)}
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
)
}
export { Skeleton }
function TableRowSkeleton() {
return (
<div className="flex space-x-4 p-4">
<Skeleton className="h-5 w-[20%]" />
<Skeleton className="h-5 w-[15%]" />
<Skeleton className="h-5 w-[30%]" />
<Skeleton className="h-5 w-[35%]" />
</div>
)
}
function TableSkeleton() {
return (
<div className="space-y-3">
<div className="flex space-x-4 p-4 border-b">
<Skeleton className="h-4 w-[20%]" />
<Skeleton className="h-4 w-[15%]" />
<Skeleton className="h-4 w-[30%]" />
<Skeleton className="h-4 w-[35%]" />
</div>
{[...Array(5)].map((_, i) => (
<TableRowSkeleton key={i} />
))}
</div>
)
}
function CardSkeleton() {
return (
<div className="p-6 space-y-4">
<Skeleton className="h-7 w-[40%]" />
<Skeleton className="h-4 w-[60%]" />
<div className="pt-4">
<Skeleton className="h-4 w-[25%]" />
</div>
</div>
)
}
function StudyListSkeleton() {
return (
<div className="space-y-6">
{[...Array(3)].map((_, i) => (
<div key={i} className="rounded-lg border bg-card">
<CardSkeleton />
</div>
))}
</div>
)
}
function StudyDetailsSkeleton() {
return (
<div className="space-y-6">
{/* Overview Card */}
<Card>
<CardHeader>
<Skeleton className="h-7 w-[150px] mb-2" />
<Skeleton className="h-4 w-[250px]" />
</CardHeader>
<CardContent>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<Skeleton className="h-4 w-[100px] mb-1" />
<Skeleton className="h-5 w-[150px]" />
</div>
<div>
<Skeleton className="h-4 w-[100px] mb-1" />
<Skeleton className="h-5 w-[200px]" />
</div>
</div>
</CardContent>
</Card>
{/* Stats Cards */}
<div className="grid gap-4 md:grid-cols-3">
{[...Array(3)].map((_, i) => (
<Card key={i}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<Skeleton className="h-4 w-[120px]" />
<Skeleton className="h-4 w-4" />
</CardHeader>
<CardContent>
<Skeleton className="h-7 w-[60px] mb-1" />
<Skeleton className="h-4 w-[100px]" />
</CardContent>
</Card>
))}
</div>
{/* Activity Card */}
<Card>
<CardHeader>
<Skeleton className="h-7 w-[150px] mb-2" />
<Skeleton className="h-4 w-[200px]" />
</CardHeader>
<CardContent>
<div className="space-y-4">
{[...Array(3)].map((_, i) => (
<div key={i} className="flex gap-4">
<Skeleton className="h-8 w-8 rounded-full" />
<div className="flex-1 space-y-2">
<Skeleton className="h-4 w-[150px]" />
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-3 w-[100px]" />
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
)
}
export {
Skeleton,
TableRowSkeleton,
TableSkeleton,
CardSkeleton,
StudyListSkeleton,
StudyDetailsSkeleton,
}