MedASR-Bench / src /components /ui /LoadingSkeleton.tsx
ngoan
MedASR Bench: full platform + Hugging Face Docker Space packaging
d70f132
Raw
History Blame Contribute Delete
4.22 kB
import clsx from "clsx";
/* Server-component-friendly skeleton primitives for route loading.tsx files.
All shimmer comes from the `.skeleton` CSS class in globals.css (the
existing `shimmer` keyframes) — no client JS, no framer-motion, and the
global prefers-reduced-motion override collapses it to a static block. */
export interface SkeletonProps {
className?: string;
/** CSS width, e.g. "60%" or 120 (px) */
width?: string | number;
/** CSS height, e.g. 16 (px) or "2rem" */
height?: string | number;
}
/**
* Skeleton — a single shimmering placeholder block.
* Size it via width/height props or Tailwind classes (h-4 w-32 …).
*/
export function Skeleton({ className, width, height }: SkeletonProps) {
return (
<div
aria-hidden
className={clsx("skeleton", className)}
style={{ width, height }}
/>
);
}
export interface SkeletonTextProps {
className?: string;
/** number of text lines, default 3 */
lines?: number;
/** width of the trailing line, default "60%" */
lastLineWidth?: string;
}
/**
* SkeletonText — a stack of shimmering text lines; the last line is shorter
* so the block reads like a paragraph.
*/
export function SkeletonText({
className,
lines = 3,
lastLineWidth = "60%",
}: SkeletonTextProps) {
return (
<div className={clsx("flex flex-col gap-2", className)}>
{Array.from({ length: lines }).map((_, i) => (
<Skeleton
key={i}
className="h-3.5"
width={i === lines - 1 ? lastLineWidth : "100%"}
/>
))}
</div>
);
}
export interface SkeletonTableProps {
className?: string;
/** body rows, default 8 */
rows?: number;
/** columns, default 6 */
columns?: number;
}
/**
* SkeletonTable — header strip + hairline-separated rows; placeholder for
* leaderboard-style tables while route data streams in.
*/
export function SkeletonTable({
className,
rows = 8,
columns = 6,
}: SkeletonTableProps) {
return (
<div
aria-hidden
className={clsx(
"hairline overflow-hidden rounded-sm bg-surface",
className,
)}
>
{/* header */}
<div className="hairline-b flex items-center gap-4 bg-surface-2/60 px-3 py-3">
{Array.from({ length: columns }).map((_, i) => (
<Skeleton key={i} className="h-3 flex-1" />
))}
</div>
{/* rows */}
{Array.from({ length: rows }).map((_, r) => (
<div
key={r}
className="hairline-b flex items-center gap-4 px-3 py-3.5 last:border-b-0"
>
{Array.from({ length: columns }).map((_, c) => (
<Skeleton
key={c}
className="h-3 flex-1"
// vary line lengths slightly so the table doesn't look stamped
width={c === 0 ? "70%" : undefined}
/>
))}
</div>
))}
</div>
);
}
export interface SkeletonCardProps {
className?: string;
/** body text lines, default 2 */
lines?: number;
}
/**
* SkeletonCard — hairline card with a heading line + body text placeholder.
*/
export function SkeletonCard({ className, lines = 2 }: SkeletonCardProps) {
return (
<div
aria-hidden
className={clsx("hairline rounded-sm bg-surface p-5", className)}
>
<Skeleton className="mb-4 h-4" width="40%" />
<SkeletonText lines={lines} />
</div>
);
}
export interface SkeletonStatsProps {
className?: string;
/** number of stat tiles, default 4 */
count?: number;
}
/**
* SkeletonStats — grid of MetricCard-shaped stat tiles (big number + label).
* Compose grid columns via className (e.g. "grid-cols-2 md:grid-cols-4").
*/
export function SkeletonStats({ className, count = 4 }: SkeletonStatsProps) {
return (
<div
aria-hidden
className={clsx(
"hairline grid gap-px overflow-hidden rounded-sm bg-line",
className,
)}
>
{Array.from({ length: count }).map((_, i) => (
<div key={i} className="flex flex-col gap-2 bg-surface px-4 py-3">
<Skeleton className="h-3" width="55%" />
<Skeleton className="h-7" width="45%" />
</div>
))}
</div>
);
}