use topcoat::{
Result,
icon::{IconData, icon as icon_view},
view::{Attributes, StaticClass, View, class, component, view},
};
const STAT: StaticClass = class!(
"flex flex-col gap-3 rounded-xl border border-border bg-card p-5 text-card-foreground",
"shadow-xs outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring",
);
const STAT_LINK: StaticClass = class!("hover:border-foreground/20 hover:bg-muted/40");
const STAT_LABEL: StaticClass = class!(
"flex items-center gap-2 text-sm font-medium text-muted-foreground",
"[&>svg]:size-4 [&>svg]:shrink-0",
);
const STAT_VALUE: StaticClass =
class!("text-3xl leading-none font-semibold tracking-tight text-foreground tabular-nums");
const STAT_DETAIL: StaticClass = class!("text-sm text-muted-foreground");
#[component]
pub async fn stat(
#[into]
label: String,
#[into]
value: String,
#[into]
#[default]
detail: String,
#[into]
#[default]
icon: Option<IconData>,
#[into]
#[default]
href: Option<String>,
#[default]
mut attrs: Attributes,
) -> Result<impl View> {
let body = view! {
<p class=(STAT_LABEL)>
if let Some(glyph) = icon {
icon_view(
data: glyph,
attrs: topcoat::view::attributes! { aria-hidden="true" }
)
}
(label)
</p>
<p class=(STAT_VALUE)>(value)</p>
if !detail.is_empty() {
<p class=(STAT_DETAIL)>(detail)</p>
}
};
Ok(view! {
if let Some(href) = href {
<a
href=(href)
class=(class!(STAT, STAT_LINK, attrs.remove("class")))
(attrs)
>
(body)
</a>
} else {
<div class=(class!(STAT, attrs.remove("class"))) (attrs)>(body)</div>
}
})
}