"use client";
import type { CSSProperties, ReactNode } from "react";
import { cx, sortCx } from "@/utils/cx";
/**
* Skeleton — placeholders in the shape of the content that is on its way.
*
*
*
*
*
* Blocks are tinted from the text colour at 8%, so they show on any surface
* in both themes (a fixed grey disappears on dark cards). A sheen of light
* sweeps each one, drawn in layers like Spinner and Progress: a crisp 2px
* line leads, a tight glow and a wide bloom trail it. It mirrors in RTL and
* gives way to a gentle opacity pulse under reduced motion. The sheen lives
* in globals.css (`.bui-skeleton`).
*
* With children, a Skeleton sizes itself to them: it lays the real content
* out invisibly, then, once `isLoaded`, cross-fades the block away while the
* content sharpens in from a 4px blur.
*
* Skeletons are hidden from assistive technology; mark the region that is
* loading with `aria-busy`.
*/
export type SkeletonShape = "rect" | "text" | "circle";
const styles = sortCx({
block: "bui-skeleton block bg-text-primary/8",
shape: {
rect: "rounded-xl",
text: "rounded-md",
circle: "rounded-full",
},
});
const FADE = "transition-[opacity,filter] duration-300 ease-out motion-reduce:transition-none";
export interface SkeletonProps {
/** `rect` (radius 12, default), `text` (radius 6), or `circle`. */
shape?: SkeletonShape;
/** With children: show them once true, blurring them in. */
isLoaded?: boolean;
/** Size and placement, e.g. `h-4 w-40` or `size-10`. */
className?: string;
style?: CSSProperties;
/** Real content to measure the placeholder from and reveal when loaded. */
children?: ReactNode;
}
export function Skeleton({ shape = "rect", isLoaded = false, className, style, children }: SkeletonProps) {
if (children === undefined) {
return ;
}
return (
{children}
);
}
export interface SkeletonTextProps {
/** Number of lines. Default 3; the last one runs 60% wide. */
lines?: number;
/** Line height, matched to the text it stands in for. Default `h-3.5` (Body 1). */
lineClassName?: string;
className?: string;
}
export function SkeletonText({ lines = 3, lineClassName = "h-3.5", className }: SkeletonTextProps) {
return (
{Array.from({ length: lines }, (_, index) => (
1 ? "w-3/5" : "w-full")}
/>
))}
);
}