Update agents page loading skeletons

This commit is contained in:
klopez4212
2026-06-22 16:37:32 +01:00
parent 49aa2c380f
commit 0673fd6b02
4 changed files with 178 additions and 190 deletions
+22 -16
View File
@@ -19,8 +19,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/shared/ui/dropdown-menu";
import { Card } from "@/shared/ui/card";
import { Skeleton } from "@/shared/ui/skeleton";
import { IdentityCardSkeleton } from "@/shared/ui/identity-card-skeleton";
import { CreateIdentityCard } from "./CreateIdentityCard";
import { TeamIdentityCard } from "./TeamIdentityCard";
@@ -102,20 +101,27 @@ export function TeamsSection({
{isLoading ? (
<div className={TEAM_CARD_GRID_CLASS}>
{["first", "second", "third"].map((key) => (
<Card className="aspect-[4/5] overflow-hidden p-0" key={key}>
<div className="relative h-full bg-muted/50">
<Skeleton className="absolute top-4 right-4 h-6 w-6 rounded-full" />
<Skeleton className="absolute top-[28%] left-[22%] h-16 w-16 rounded-full" />
<Skeleton className="absolute top-[22%] right-[20%] h-20 w-20 rounded-full" />
<Skeleton className="absolute top-[42%] left-[32%] h-24 w-24 rounded-full" />
<div className="absolute right-3 bottom-3 left-3 flex flex-col gap-1">
<Skeleton className="h-5 w-28" />
<Skeleton className="h-4 w-16" />
</div>
</div>
</Card>
))}
<IdentityCardSkeleton
footerSubtitleWidthClass="w-14"
footerTitleWidthClass="w-24"
kind="stack"
showAction
stackCount={1}
/>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-24"
footerTitleWidthClass="w-32"
kind="stack"
showAction
stackCount={3}
/>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-20"
footerTitleWidthClass="w-28"
kind="stack"
showAction
stackCount={5}
/>
</div>
) : null}
@@ -14,7 +14,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/shared/ui/dropdown-menu";
import { Skeleton } from "@/shared/ui/skeleton";
import { IdentityCardSkeleton } from "@/shared/ui/identity-card-skeleton";
import { AgentIdentityCard } from "./AgentIdentityCard";
import { CreateIdentityCard } from "./CreateIdentityCard";
@@ -408,19 +408,18 @@ function NewAgentCard({
function LoadingSkeleton() {
return (
<div className={AGENT_CARD_GRID_CLASS}>
{["a", "b", "c"].map((k) => (
<div
className="flex aspect-[5/7] w-full max-w-[240px] flex-col items-center justify-center rounded-lg border border-border/70 bg-card p-5"
key={k}
>
<div className="relative">
<Skeleton className="h-32 w-32 rounded-full" />
<Skeleton className="-translate-x-1/2 absolute bottom-0 left-1/2 h-8 w-24 rounded-full" />
</div>
<Skeleton className="mt-10 h-4 w-40 max-w-full" />
<Skeleton className="mt-2 h-4 w-32 max-w-full" />
</div>
))}
<IdentityCardSkeleton
footerSubtitleWidthClass="w-14"
footerTitleWidthClass="w-24"
/>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-20"
footerTitleWidthClass="w-32"
/>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-16"
footerTitleWidthClass="w-28"
/>
</div>
);
}
+40 -160
View File
@@ -1,5 +1,6 @@
import { Card } from "@/shared/ui/card";
import { Skeleton } from "@/shared/ui/skeleton";
import { IdentityCardSkeleton } from "@/shared/ui/identity-card-skeleton";
import { cn } from "@/shared/lib/cn";
import { channelChrome, topChromeInset } from "@/shared/layout/chromeLayout";
import { TopChromeInsetHeader } from "@/shared/layout/TopChromeInsetHeader";
@@ -69,119 +70,8 @@ function MessageRowsSkeleton() {
);
}
const agentLoadingGroups = [
{
badgeWidth: "w-14",
instanceWidth: "w-20",
key: "persona-one",
rows: ["one-a", "one-b"],
titleWidth: "w-32",
},
{
badgeWidth: "w-16",
instanceWidth: "w-24",
key: "persona-two",
rows: ["two-a"],
titleWidth: "w-28",
},
{
badgeWidth: "w-12",
instanceWidth: "w-20",
key: "custom-agents",
rows: ["custom-a"],
titleWidth: "w-36",
},
] as const;
function AgentRowSkeleton({ variant = 0 }: { variant?: number }) {
return (
<div className="flex items-start gap-3 px-4 py-3">
<div className="min-w-0 flex-1">
<div className="grid gap-3 lg:grid-cols-[minmax(0,1.8fr)_minmax(120px,0.8fr)_minmax(0,1.1fr)] lg:gap-4">
<div className="min-w-0">
<div className="flex items-start gap-3">
<Skeleton className="mt-0.5 h-4 w-4 shrink-0 rounded-sm" />
<Skeleton className="mt-1 h-2 w-2 shrink-0 rounded-full" />
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<Skeleton
className={cn("h-4", variant % 2 === 0 ? "w-36" : "w-28")}
/>
<Skeleton className="h-5 w-16 rounded-full" />
</div>
<div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1">
<Skeleton className="h-3 w-20" />
<Skeleton className="h-3 w-24" />
</div>
{variant === 0 ? (
<div className="mt-1.5 flex flex-wrap items-center gap-1.5">
<Skeleton className="h-5 w-20 rounded-full" />
<Skeleton className="h-5 w-24 rounded-full" />
</div>
) : null}
</div>
</div>
</div>
<div className="space-y-1 lg:pt-0.5">
<Skeleton className="h-5 w-20 rounded-full" />
<Skeleton
className={cn("h-3", variant % 2 === 0 ? "w-24" : "w-28")}
/>
</div>
<div className="space-y-1 lg:pt-0.5">
<Skeleton className="h-3 w-28" />
<div className="flex flex-wrap items-center gap-x-3 gap-y-1">
<Skeleton className="h-3 w-20" />
{variant === 0 ? <Skeleton className="h-3 w-24" /> : null}
</div>
</div>
</div>
</div>
<div className="flex shrink-0 items-start gap-2 lg:pt-0.5">
<Skeleton className="h-7 w-24 rounded-md" />
<Skeleton className="h-7 w-7 rounded-md" />
</div>
</div>
);
}
function AgentGroupSkeleton({
badgeWidth,
instanceWidth,
rows,
titleWidth,
}: {
badgeWidth: string;
instanceWidth: string;
rows: readonly string[];
titleWidth: string;
}) {
return (
<div className="overflow-hidden rounded-xl border border-border/70 bg-card/40">
<div className="flex items-center gap-2 px-3 py-2">
<div className="flex min-w-0 flex-1 items-center gap-2 py-1">
<Skeleton className="h-4 w-4 shrink-0 rounded-sm" />
<Skeleton className="h-8 w-8 shrink-0 rounded-lg" />
<div className="flex min-w-0 items-center gap-2">
<Skeleton className={cn("h-4", titleWidth)} />
<Skeleton className={cn("h-5 rounded-full", badgeWidth)} />
</div>
<Skeleton className={cn("ml-1 h-3 shrink-0", instanceWidth)} />
</div>
<Skeleton className="h-7 w-7 shrink-0 rounded-md" />
</div>
<div className="divide-y divide-border/50 border-t border-border/50">
{rows.map((row, index) => (
<AgentRowSkeleton key={row} variant={index} />
))}
</div>
</div>
);
}
const AGENTS_LOADING_GRID_CLASS =
"grid grid-cols-[repeat(auto-fill,minmax(220px,240px))] justify-start gap-3";
function AgentsLibrarySkeleton() {
return (
@@ -189,24 +79,23 @@ function AgentsLibrarySkeleton() {
<div className="flex items-center justify-between gap-3">
<div>
<Skeleton className="h-4 w-24" />
<Skeleton className="mt-2 h-4 w-80 max-w-full" />
</div>
<div className="flex items-center gap-2">
<Skeleton className="h-7 w-7 rounded-md" />
<Skeleton className="h-8 w-16 rounded-lg" />
<Skeleton className="mt-2 h-4 w-40 max-w-full" />
</div>
</div>
<div className="space-y-3">
{agentLoadingGroups.map((group) => (
<AgentGroupSkeleton
badgeWidth={group.badgeWidth}
instanceWidth={group.instanceWidth}
key={group.key}
rows={group.rows}
titleWidth={group.titleWidth}
/>
))}
<div className={AGENTS_LOADING_GRID_CLASS}>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-14"
footerTitleWidthClass="w-24"
/>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-20"
footerTitleWidthClass="w-32"
/>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-16"
footerTitleWidthClass="w-28"
/>
</div>
</section>
);
@@ -218,41 +107,32 @@ function AgentTeamsSkeleton() {
<div className="flex items-center justify-between gap-3">
<div>
<Skeleton className="h-4 w-20" />
<Skeleton className="mt-2 h-4 w-96 max-w-full" />
</div>
<div className="flex items-center gap-2">
<Skeleton className="hidden h-8 w-40 rounded-md sm:block" />
<Skeleton className="h-8 w-20 rounded-lg" />
<Skeleton className="mt-2 h-4 w-72 max-w-full" />
</div>
</div>
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
{["team-one", "team-two", "team-three"].map((key, index) => (
<Card className="p-3" key={key}>
<div className="flex items-start justify-between gap-2.5">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-4 shrink-0 rounded-sm" />
<Skeleton
className={cn("h-4", index === 0 ? "w-28" : "w-36")}
/>
{index === 1 ? (
<Skeleton className="h-4 w-8 rounded-full" />
) : null}
</div>
<div className="mt-2 flex items-center gap-2">
<div className="flex -space-x-1.5">
<Skeleton className="h-6 w-6 rounded-full border-2 border-card" />
<Skeleton className="h-6 w-6 rounded-full border-2 border-card" />
<Skeleton className="h-6 w-6 rounded-full border-2 border-card" />
</div>
<Skeleton className="h-3 w-20" />
</div>
</div>
<Skeleton className="h-6 w-6 rounded-md" />
</div>
</Card>
))}
<div className={AGENTS_LOADING_GRID_CLASS}>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-14"
footerTitleWidthClass="w-24"
kind="stack"
showAction
stackCount={1}
/>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-24"
footerTitleWidthClass="w-32"
kind="stack"
showAction
stackCount={3}
/>
<IdentityCardSkeleton
footerSubtitleWidthClass="w-20"
footerTitleWidthClass="w-28"
kind="stack"
showAction
stackCount={5}
/>
</div>
</section>
);
@@ -0,0 +1,103 @@
import { cn } from "@/shared/lib/cn";
import { Skeleton } from "@/shared/ui/skeleton";
type IdentityCardSkeletonProps = {
className?: string;
footerSubtitleWidthClass?: string;
footerTitleWidthClass?: string;
kind?: "single" | "stack";
showAction?: boolean;
stackCount?: number;
};
const MAX_STACK_ITEMS = 6;
const STACK_ITEM_KEYS = [
"first",
"second",
"third",
"fourth",
"fifth",
"sixth",
];
export function IdentityCardSkeleton({
className,
footerSubtitleWidthClass = "w-16",
footerTitleWidthClass = "w-28",
kind = "single",
showAction = false,
stackCount = 3,
}: IdentityCardSkeletonProps) {
return (
<div
className={cn(
"relative aspect-[4/5] w-full min-w-0 overflow-hidden rounded-xl border border-border/70 bg-muted/50 shadow-xs",
className,
)}
>
{showAction ? (
<Skeleton className="absolute top-3 right-3 z-30 h-7 w-7 rounded-md bg-background/70" />
) : null}
{kind === "stack" ? (
<StackSkeleton count={stackCount} />
) : (
<SingleAvatarSkeleton />
)}
<div className="absolute right-3 bottom-3 left-3 z-30 flex min-w-0 flex-col gap-1 text-left">
<Skeleton className={cn("h-4 max-w-full", footerTitleWidthClass)} />
<Skeleton className={cn("h-4 max-w-full", footerSubtitleWidthClass)} />
</div>
</div>
);
}
function SingleAvatarSkeleton() {
return (
<div className="absolute inset-x-0 top-0 bottom-12 flex items-center justify-center">
<Skeleton className="h-[152px] w-[152px] rounded-full" />
</div>
);
}
function StackSkeleton({ count }: { count: number }) {
const visibleCount = Math.max(1, Math.min(count, MAX_STACK_ITEMS));
const { overlap, size } = getStackMetrics(visibleCount);
return (
<div className="absolute inset-x-0 top-0 bottom-12 flex items-center justify-center">
<div className="flex max-w-full items-center justify-center px-2">
{STACK_ITEM_KEYS.slice(0, visibleCount).map((key, index) => (
<Skeleton
className="rounded-full border-[3px] border-background"
key={key}
style={{
height: size,
marginLeft: index > 0 ? -overlap : 0,
width: size,
zIndex: index + 1,
}}
/>
))}
</div>
</div>
);
}
function getStackMetrics(count: number) {
switch (count) {
case 1:
return { overlap: 0, size: 152 };
case 2:
return { overlap: 44, size: 124 };
case 3:
return { overlap: 46, size: 108 };
case 4:
return { overlap: 50, size: 96 };
case 5:
return { overlap: 48, size: 86 };
default:
return { overlap: 46, size: 78 };
}
}