Skip to main content

tablo_ui/components/primitives/
card.rs

1// SYNC: topcoat-ui-registry@0.10.0 sha256:0d0599fb85b42d341aea108fadb25937afdd47b0521d1246dc2bf3bda42f1367 — do not hand-edit. Sync via `cargo xtask sync-topcoat-ui` (ADR-0007).
2use topcoat::{
3    Result,
4    view::{Attributes, Child, StaticClass, View, class, component, view},
5};
6
7/// Classes for a card with vertically stacked sections. Each section supplies its own
8/// horizontal padding so other content can span the full width.
9const CARD: StaticClass = class!(
10    "flex flex-col gap-5 rounded-xl border border-border bg-card py-6 \
11     text-card-foreground shadow-sm",
12);
13
14/// A bordered panel that groups related content.
15///
16/// Pass sections as children. Use a header, body, or footer as needed. `attrs` are
17/// forwarded to the `<div>`, with extra classes added to its classes.
18///
19/// ```ignore
20/// view! {
21///     card(
22///         attrs: attributes! { class="max-w-sm" },
23///         card_header(
24///             card_title("Delete workspace")
25///             card_description("This cannot be undone.")
26///         )
27///         card_footer(
28///             attrs: attributes! { class="justify-end" },
29///             button(variant: ButtonVariant::Destructive, "Delete")
30///         )
31///     )
32/// }
33/// ```
34#[component]
35pub async fn card(
36    #[default] mut attrs: Attributes,
37    #[default] child: Child<'_>,
38) -> Result<impl View> {
39    Ok(view! { <div class=(class!(CARD, attrs.remove("class"))) (attrs)>(child)</div> })
40}
41
42/// The opening section of a [`card`], stacking a [`card_title`] and an
43/// optional [`card_description`].
44#[component]
45pub async fn card_header(
46    #[default] mut attrs: Attributes,
47    #[default] child: Child<'_>,
48) -> Result<impl View> {
49    Ok(view! {
50        <div
51            class=(class!("flex flex-col gap-1.5 px-6", attrs.remove("class")))
52            (attrs)
53        >
54            (child)
55        </div>
56    })
57}
58
59/// The heading of a [`card`], rendered as an `<h3>`.
60#[component]
61pub async fn card_title(
62    #[default] mut attrs: Attributes,
63    #[default] child: Child<'_>,
64) -> Result<impl View> {
65    Ok(view! {
66        <h3
67            class=(class!("text-base leading-none font-semibold", attrs.remove("class")))
68            (attrs)
69        >
70            (child)
71        </h3>
72    })
73}
74
75/// The supporting text under a [`card_title`].
76#[component]
77pub async fn card_description(
78    #[default] mut attrs: Attributes,
79    #[default] child: Child<'_>,
80) -> Result<impl View> {
81    Ok(view! {
82        <p
83            class=(class!("text-sm text-muted-foreground", attrs.remove("class")))
84            (attrs)
85        >
86            (child)
87        </p>
88    })
89}
90
91/// The main body of a [`card`].
92#[component]
93pub async fn card_content(
94    #[default] mut attrs: Attributes,
95    #[default] child: Child<'_>,
96) -> Result<impl View> {
97    Ok(view! { <div class=(class!("px-6", attrs.remove("class"))) (attrs)>(child)</div> })
98}
99
100/// The closing section of a [`card`], a horizontal row for actions.
101#[component]
102pub async fn card_footer(
103    #[default] mut attrs: Attributes,
104    #[default] child: Child<'_>,
105) -> Result<impl View> {
106    Ok(view! {
107        <div
108            class=(class!("flex items-center gap-2 px-6", attrs.remove("class")))
109            (attrs)
110        >
111            (child)
112        </div>
113    })
114}