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 (
);
}
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 (
{Array.from({ length: lines }).map((_, i) => (
))}
);
}
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 (
{/* header */}
{Array.from({ length: columns }).map((_, i) => (
))}
{/* rows */}
{Array.from({ length: rows }).map((_, r) => (
{Array.from({ length: columns }).map((_, c) => (
))}
))}
);
}
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 (
);
}
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 (
{Array.from({ length: count }).map((_, i) => (
))}
);
}