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 { AlertCircle, Plus } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { Skeleton } from "@/components/Skeleton";
|
||||||
import { InitiativeCard } from "@/components/InitiativeCard";
|
import { InitiativeCard } from "@/components/InitiativeCard";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
|
|
||||||
@@ -28,8 +30,19 @@ export function InitiativeList({
|
|||||||
// Loading state
|
// Loading state
|
||||||
if (initiativesQuery.isLoading) {
|
if (initiativesQuery.isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12 text-muted-foreground">
|
<div className="space-y-3">
|
||||||
Loading initiatives...
|
{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>
|
</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 { useState } from "react";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
import { AlertCircle } from "lucide-react";
|
import { AlertCircle } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { Skeleton } from "@/components/Skeleton";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
import { InboxList } from "@/components/InboxList";
|
import { InboxList } from "@/components/InboxList";
|
||||||
import { QuestionForm } from "@/components/QuestionForm";
|
import { QuestionForm } from "@/components/QuestionForm";
|
||||||
@@ -89,8 +91,32 @@ function InboxPage() {
|
|||||||
// Loading state
|
// Loading state
|
||||||
if (agentsQuery.isLoading && messagesQuery.isLoading) {
|
if (agentsQuery.isLoading && messagesQuery.isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12 text-muted-foreground">
|
<div className="space-y-4">
|
||||||
Loading inbox...
|
{/* 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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user