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