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 incidents | 1 | import type { ComponentProps } from "react"; |
| 2 | ||
| 3 | import { 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`. */ | |
| 11 | export 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 | */ | |
| 25 | export 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 | */ | |
| 39 | export 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 | } |