mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Update agents page loading skeletons
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user