feat(21-02): add Skeleton component and skeleton loading states for dashboard + inbox

Create reusable Skeleton component with animate-pulse styling. Replace
plain "Loading..." text with structured skeleton placeholders matching
card layouts in InitiativeList and inbox page.
This commit is contained in:
Lukas May
2026-02-05 08:56:35 +01:00
parent 81814ac213
commit 11fa5f4be9
3 changed files with 55 additions and 5 deletions

View File

@@ -1,5 +1,7 @@
import { AlertCircle, Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/Skeleton";
import { InitiativeCard } from "@/components/InitiativeCard";
import { trpc } from "@/lib/trpc";
@@ -28,8 +30,19 @@ export function InitiativeList({
// Loading state
if (initiativesQuery.isLoading) {
return (
<div className="flex items-center justify-center py-12 text-muted-foreground">
Loading initiatives...
<div className="space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<Card key={i} className="p-4">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<Skeleton className="h-5 w-48" />
<div className="flex flex-1 items-center gap-4">
<Skeleton className="h-5 w-16" />
<Skeleton className="h-2 w-32" />
<Skeleton className="h-4 w-24" />
</div>
</div>
</Card>
))}
</div>
);
}

View File

@@ -0,0 +1,11 @@
import { cn } from "@/lib/utils";
interface SkeletonProps {
className?: string;
}
export function Skeleton({ className }: SkeletonProps) {
return (
<div className={cn("animate-pulse rounded-md bg-muted", className)} />
);
}

View File

@@ -1,7 +1,9 @@
import { useState } from "react";
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, Link } from "@tanstack/react-router";
import { AlertCircle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/Skeleton";
import { trpc } from "@/lib/trpc";
import { InboxList } from "@/components/InboxList";
import { QuestionForm } from "@/components/QuestionForm";
@@ -89,8 +91,32 @@ function InboxPage() {
// Loading state
if (agentsQuery.isLoading && messagesQuery.isLoading) {
return (
<div className="flex items-center justify-center py-12 text-muted-foreground">
Loading inbox...
<div className="space-y-4">
{/* Skeleton header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Skeleton className="h-6 w-28" />
<Skeleton className="h-5 w-8 rounded-full" />
</div>
<Skeleton className="h-8 w-20" />
</div>
{/* Skeleton message rows */}
<div className="space-y-2">
{Array.from({ length: 4 }).map((_, i) => (
<Card key={i} className="p-4">
<div className="flex items-start justify-between gap-4">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<Skeleton className="h-3 w-3 rounded-full" />
<Skeleton className="h-4 w-32" />
</div>
<Skeleton className="mt-2 ml-5 h-3 w-full" />
</div>
<Skeleton className="h-3 w-16" />
</div>
</Card>
))}
</div>
</div>
);
}