mirror of
https://github.com/soconnor0919/hristudio.git
synced 2025-12-12 07:04:44 -05:00
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:
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user