g1t/apps/web/app/components/ui/skeleton.tsx

51 lines2,019 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1import type { ComponentProps } from "react";
2
3import { cn } from "../../lib/cn";
4
5// The shape of something still loading: a quiet raised block that pulses
6// (still for people who ask for less motion). Give it the size of what it
7// stands for, so nothing moves when the real thing arrives. Hidden from
8// screen readers; the region it sits in says it is busy (`aria-busy`).
9
10/** One block. Size it with classes: `h-3 w-24`, `size-8 rounded-full`. */
11export function Skeleton({ className, ...props }: ComponentProps<"span">) {
12 return (
13 <span
14 aria-hidden="true"
15 className={cn("block rounded bg-raised motion-safe:animate-pulse", className)}
16 {...props}
17 />
18 );
19}
20
21/**
22 * Lines of text: `lines` bars `h-3` apart by `gap`, the last one shorter,
23 * as a paragraph or a list's titles would sit.
24 */
25export function SkeletonText({ lines = 3, className, lineClassName }: { lines?: number; className?: string; lineClassName?: string }) {
26 return (
27 <span aria-hidden="true" className={cn("flex flex-col gap-2", className)}>
28 {Array.from({ length: lines }, (_, index) => (
29 <Skeleton key={index} className={cn("h-3", index === lines - 1 && lines > 1 ? "w-3/5" : "w-full", lineClassName)} />
30 ))}
31 </span>
32 );
33}
34
35/**
36 * Rows of a list, each `rowClassName` tall (`h-10` by default) with a
37 * leading dot and a title, as the app's lists are laid out.
38 */
39export function SkeletonRows({ rows = 3, className, rowClassName }: { rows?: number; className?: string; rowClassName?: string }) {
40 return (
41 <div aria-hidden="true" className={cn("divide-y divide-line", className)}>
42 {Array.from({ length: rows }, (_, index) => (
43 <div key={index} className={cn("flex h-10 items-center gap-3 px-3", rowClassName)}>
44 <Skeleton className="size-4 shrink-0 rounded-full" />
45 <Skeleton className="h-3 grow" style={{ maxWidth: `${70 - ((index * 17) % 30)}%` }} />
46 <Skeleton className="h-3 w-12 shrink-0" />
47 </div>
48 ))}
49 </div>
50 );
51}