Skip to content

g1t/apps/web/app/components/workspace-skeletons.tsx

98 lines3,681 bytesCodeBlame
1/**
2 * The shapes of a workspace's tabs while they load: the same layout as
3 * the real thing, so nothing moves when it arrives (components/ui/skeleton.tsx).
4 */
5import type { WorkspaceTabKey } from "../lib/workspace-nav";
6import { Skeleton, SkeletonRows, SkeletonText } from "./ui/skeleton";
7
8/** A project card, as the Overview and the Projects grid show them. */
9export function ProjectCardSkeleton() {
10 return (
11 <li aria-hidden="true" className="flex flex-col rounded-2xl border border-line bg-surface p-5">
12 <div className="flex items-start gap-3">
13 <Skeleton className="size-9 shrink-0 rounded-lg" />
14 <div className="grow space-y-2 pt-1">
15 <Skeleton className="h-3.5 w-32" />
16 <Skeleton className="h-3 w-48" />
17 </div>
18 </div>
19 <div className="mt-8 flex gap-4 border-t border-line pt-3">
20 <Skeleton className="h-3 w-12" />
21 <Skeleton className="h-3 w-8" />
22 <Skeleton className="h-3 w-8" />
23 </div>
24 </li>
25 );
26}
27
28/** The Projects page's rows. */
29export function ProjectRowsSkeleton({ rows = 8 }: { rows?: number }) {
30 return (
31 <div aria-hidden="true" className="overflow-hidden rounded-xl border border-line bg-surface">
32 {Array.from({ length: rows }, (_, index) => (
33 <div key={index} className="flex items-center gap-3 border-b border-line px-4 py-3.5 last:border-b-0">
34 <Skeleton className="size-8 shrink-0 rounded-md" />
35 <div className="grow space-y-2">
36 <Skeleton className="h-3.5" style={{ width: `${30 + ((index * 13) % 25)}%` }} />
37 <Skeleton className="h-3" style={{ width: `${45 + ((index * 7) % 30)}%` }} />
38 </div>
39 <Skeleton className="hidden h-3 w-16 sm:block" />
40 <Skeleton className="size-8 shrink-0 rounded-md" />
41 </div>
42 ))}
43 </div>
44 );
45}
46
47/** The Projects page: its search and filters, then rows. */
48export function ProjectsSkeleton() {
49 return (
50 <div aria-busy="true" aria-label="Loading projects" className="space-y-4">
51 <div className="flex flex-wrap gap-2">
52 <Skeleton className="h-9 w-full rounded-md sm:w-72" />
53 <Skeleton className="h-9 w-28 rounded-md" />
54 <Skeleton className="h-9 w-28 rounded-md" />
55 <Skeleton className="h-9 w-36 rounded-md" />
56 </div>
57 <ProjectRowsSkeleton />
58 </div>
59 );
60}
61
62/** The Overview: pinned cards, then recent ones beside the aside. */
63export function OverviewSkeleton() {
64 return (
65 <div aria-busy="true" aria-label="Loading" className="grid gap-10 lg:grid-cols-[1fr_18rem]">
66 <div className="space-y-10">
67 <div>
68 <Skeleton className="h-3.5 w-20" />
69 <ul className="mt-3 grid gap-4 sm:grid-cols-2">
70 {Array.from({ length: 4 }, (_, index) => (
71 <ProjectCardSkeleton key={index} />
72 ))}
73 </ul>
74 </div>
75 <div>
76 <Skeleton className="h-3.5 w-28" />
77 <SkeletonRows rows={4} className="mt-3 rounded-xl border border-line bg-surface" />
78 </div>
79 </div>
80 <div className="space-y-4">
81 <Skeleton className="h-40 rounded-xl" />
82 <SkeletonText lines={4} />
83 </div>
84 </div>
85 );
86}
87
88/** Whichever tab is on its way. */
89export function WorkspaceTabSkeleton({ tab }: { tab: WorkspaceTabKey }) {
90 if (tab === "overview") return <OverviewSkeleton />;
91 if (tab === "projects") return <ProjectsSkeleton />;
92 return (
93 <div aria-busy="true" aria-label="Loading" className="space-y-4">
94 <Skeleton className="h-3.5 w-2/3 max-w-xl" />
95 <SkeletonRows rows={6} className="rounded-xl border border-line bg-surface" />
96 </div>
97 );
98}