ui: 骨架屏替代全部页面级加载 spinner
- 新增 Skeleton 组件库:Skeleton、SkeletonCircle 基础元素 + 5 个业务骨架 (SwipeDeck、ProfileCard、RecordItem、BlindboxRoom、BlindboxList、RoomCard) - 替换 room、profile、blindbox 列表、blindbox 房间、invite 5 个页面的加载态 - 替换 profile 历史记录 / 收藏列表的内联加载 spinner - 更新 project-conventions.mdc:新增 Loading States 规范, 要求页面级和列表级加载必须使用骨架屏
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"use client";
|
||||
|
||||
interface SkeletonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Skeleton({ className = "" }: SkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
className={`animate-pulse rounded-lg bg-elevated ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SkeletonCircle({ className = "" }: SkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
className={`animate-pulse rounded-full bg-elevated ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function RoomCardSkeleton() {
|
||||
return (
|
||||
<div className="flex w-full items-center gap-3 rounded-2xl bg-surface p-4 ring-1 ring-border">
|
||||
<Skeleton className="h-11 w-11 shrink-0 rounded-xl" />
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Skeleton className="h-4 w-24" />
|
||||
<Skeleton className="h-3 w-36" />
|
||||
</div>
|
||||
<Skeleton className="h-3 w-10" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfileCardSkeleton() {
|
||||
return (
|
||||
<div className="rounded-2xl bg-surface p-4 ring-1 ring-border">
|
||||
<div className="flex items-center gap-4">
|
||||
<Skeleton className="h-14 w-14 rounded-2xl" />
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Skeleton className="h-5 w-28" />
|
||||
<Skeleton className="h-3 w-40" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RecordItemSkeleton() {
|
||||
return (
|
||||
<div className="flex gap-3 rounded-xl bg-elevated p-2.5">
|
||||
<Skeleton className="h-12 w-12 shrink-0 rounded-lg" />
|
||||
<div className="flex flex-1 flex-col justify-center gap-2">
|
||||
<Skeleton className="h-4 w-28" />
|
||||
<Skeleton className="h-3 w-36" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SwipeDeckSkeleton() {
|
||||
return (
|
||||
<div className="flex h-dvh flex-col bg-background">
|
||||
<nav className="flex h-14 items-center px-4">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Skeleton className="h-8 w-8 rounded-full" />
|
||||
<Skeleton className="h-7 w-28 rounded-full" />
|
||||
<Skeleton className="h-7 w-16 rounded-full" />
|
||||
</div>
|
||||
</nav>
|
||||
<div className="flex flex-1 items-center justify-center px-4">
|
||||
<div className="h-[60vh] w-full max-w-sm">
|
||||
<div className="h-full overflow-hidden rounded-2xl bg-surface ring-1 ring-border">
|
||||
<Skeleton className="h-[58%] w-full rounded-none" />
|
||||
<div className="flex flex-col gap-3 px-5 py-4">
|
||||
<Skeleton className="h-5 w-40" />
|
||||
<div className="flex gap-3">
|
||||
<Skeleton className="h-4 w-12" />
|
||||
<Skeleton className="h-4 w-16" />
|
||||
<Skeleton className="h-4 w-10" />
|
||||
</div>
|
||||
<Skeleton className="h-3 w-full" />
|
||||
<Skeleton className="h-3 w-32" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4 pb-5">
|
||||
<SkeletonCircle className="h-13 w-13" />
|
||||
<SkeletonCircle className="h-13 w-13" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BlindboxRoomSkeleton() {
|
||||
return (
|
||||
<div className="flex min-h-dvh flex-col items-center bg-background px-5 py-6">
|
||||
<div className="flex w-full max-w-sm items-center gap-3">
|
||||
<Skeleton className="h-8 w-8 rounded-full" />
|
||||
<div className="flex flex-1 flex-col gap-1.5">
|
||||
<Skeleton className="h-4 w-28" />
|
||||
<Skeleton className="h-2.5 w-16" />
|
||||
</div>
|
||||
<div className="flex -space-x-1.5">
|
||||
<SkeletonCircle className="h-7 w-7 ring-2 ring-background" />
|
||||
<SkeletonCircle className="h-7 w-7 ring-2 ring-background" />
|
||||
</div>
|
||||
<Skeleton className="h-8 w-8 rounded-full" />
|
||||
</div>
|
||||
<Skeleton className="mt-10 h-36 w-36 rounded-2xl" />
|
||||
<Skeleton className="mt-6 h-5 w-32" />
|
||||
<Skeleton className="mt-2 h-3 w-48" />
|
||||
<div className="mt-8 w-full max-w-sm space-y-3">
|
||||
<Skeleton className="h-12 w-full rounded-xl" />
|
||||
<Skeleton className="h-12 w-full rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BlindboxListSkeleton() {
|
||||
return (
|
||||
<div className="mt-6 flex w-full max-w-sm flex-col gap-3">
|
||||
<div className="flex gap-2">
|
||||
<Skeleton className="h-10 flex-1 rounded-xl" />
|
||||
<Skeleton className="h-10 w-20 rounded-xl" />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Skeleton className="h-10 flex-1 rounded-xl" />
|
||||
<Skeleton className="h-10 w-20 rounded-xl" />
|
||||
</div>
|
||||
<div className="mt-3 flex flex-col gap-3">
|
||||
<RoomCardSkeleton />
|
||||
<RoomCardSkeleton />
|
||||
<RoomCardSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user