src/components/skeleton.rs
//! Layout-preserving loading placeholders.
//!
//! A skeleton is only honest when it has the SHAPE of what arrives: same
//! table, same columns, so the page does not jump when data lands. Text-only
//! `Loading` stays for small inline waits — skeletons are for the
//! table-shaped ones, where a bare "loading…" collapses the layout twice.
//!
//! Screen readers get the sentence, not the shimmer: the boxes are
//! `aria-hidden` and a single visually-hidden live region speaks instead.
use leptos::prelude::*;
#[component]
pub fn TableSkeleton(
/// How many placeholder rows to draw — pick the page's usual density, not
/// a number that implies a count the server has not reported.
#[prop(default = 5)]
rows: usize,
#[prop(default = 4)] cols: usize,
#[prop(into)] what: String,
) -> impl IntoView {
view! {
<p class="vh" role="status">{format!("loading {what}…")}</p>
<div class="tablewrap" aria-hidden="true">
<table class="skeleton-table">
<tbody>
{(0..rows).map(|r| view! {
<tr>
{(0..cols).map(|c| {
// Vary the bar widths a little so the block
// reads as text-to-come, not as a grid.
let w = 45 + ((r * 7 + c * 23) % 45);
view! {
<td><span class="skeleton-bar"
style=format!("width:{w}%")></span></td>
}
}).collect_view()}
</tr>
}).collect_view()}
</tbody>
</table>
</div>
}
}