| 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 | } |