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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
11
packages/web/src/components/Skeleton.tsx
Normal file
11
packages/web/src/components/Skeleton.tsx
Normal 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)} />
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user