1import React from "react";
2
3type SkeletonVariant = "text" | "circle" | "rect" | "card";
4
5interface SkeletonProps {
6 variant?: SkeletonVariant;
7 width?: string | number;
8 height?: string | number;
9 lines?: number;
10 className?: string;
11 /** What is loading, for screen readers */
12 label?: string;
13}
14
15/* Placeholders are empty sunken wells — the shape of what will fill them —
16 with a light sweep travelling across, rather than a blink. */
17const base = "skeleton-sheen relative overflow-hidden rounded-[8px]";
18const well: React.CSSProperties = { background: "var(--well)", boxShadow: "var(--well-shadow)" };
19
20export function Skeleton({
21 variant = "text",
22 width,
23 height,
24 lines = 1,
25 className = "",
26 label = "Loading",
27}: SkeletonProps) {
28 const a11y = { role: "status", "aria-busy": true, "aria-label": label } as const;
29
30 if (variant === "circle") {
31 return (
32 <div
33 {...a11y}
34 className={[base, "rounded-full", className].join(" ")}
35 style={{ ...well, width: width ?? 40, height: height ?? 40 }}
36 />
37 );
38 }
39
40 if (variant === "card") {
41 return (
42 <div
43 {...a11y}
44 className={["rounded-[20px] p-1.5", className].join(" ")}
45 style={{ background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" }}
46 >
47 <div className="bg-white rounded-[15px] p-1.5" style={{ boxShadow: "var(--panel-face-shadow)" }}>
48 <div className={[base, "w-full rounded-[11px]"].join(" ")} style={{ ...well, height: height ?? 160 }} />
49 <div className="px-3.5 pt-4 pb-3.5 flex flex-col gap-2.5">
50 <div className={[base, "h-3.5 w-3/5"].join(" ")} style={well} />
51 <div className={[base, "h-2.5 w-full"].join(" ")} style={well} />
52 <div className={[base, "h-2.5 w-4/5"].join(" ")} style={well} />
53 </div>
54 </div>
55 </div>
56 );
57 }
58
59 if (variant === "rect") {
60 return (
61 <div
62 {...a11y}
63 className={[base, "rounded-[12px]", className].join(" ")}
64 style={{ ...well, width: width ?? "100%", height: height ?? 80 }}
65 />
66 );
67 }
68
69 return (
70 <div {...a11y} className={["flex flex-col gap-2", className].join(" ")}>
71 {Array.from({ length: lines }).map((_, i) => (
72 <div
73 key={i}
74 className={[base, "h-2.5 rounded-full"].join(" ")}
75 style={{
76 ...well,
77 width: width ?? (i === lines - 1 && lines > 1 ? "60%" : "100%"),
78 }}
79 />
80 ))}
81 </div>
82 );
83}