@jpgilldev / steid

3.2 KBRaw
1use topcoat::{
2 Result,
3 view::{Attributes, View, class, component, view},
4};
5
6/// The classes for the [`card`] container.
7///
8/// The card is a column of sections separated by a uniform gap. It carries
9/// vertical padding only; each section brings its own horizontal padding, so
10/// full-bleed content such as an image can span the card's width. The card
11/// casts the theme's raised-surface shadow and sets its own background and
12/// text color, so it reads as a card on any ancestor.
13const CARD: &str = "flex flex-col gap-5 rounded-xl border border-border bg-background py-6 \
14 text-foreground shadow-sm";
15
16/// A card component: a bordered, raised surface grouping related content.
17///
18/// A card stacks sections vertically: typically a [`card_header`], then a
19/// [`card_content`], closed by a [`card_footer`]. Any section can be omitted.
20/// The `attrs` (such as `class` or event handlers) are forwarded to the
21/// underlying `<div>`; a `class` among them is appended to the computed
22/// classes. Child nodes become the card's sections.
23///
24/// ```ignore
25/// view! {
26/// card(
27/// attrs: attributes! { class="max-w-sm" },
28/// card_header(
29/// card_title("Delete workspace")
30/// card_description("This cannot be undone.")
31/// )
32/// card_footer(
33/// attrs: attributes! { class="justify-end" },
34/// button(variant: ButtonVariant::Destructive, "Delete")
35/// )
36/// )
37/// }
38/// ```
39#[component]
40pub async fn card(#[default] mut attrs: Attributes, #[default] child: View) -> Result {
41 view! { <div class=(class!(CARD, attrs.remove("class"))) (attrs)>(child)</div> }
42}
43
44/// The opening section of a [`card`], stacking a [`card_title`] and an
45/// optional [`card_description`].
46#[component]
47pub async fn card_header(#[default] mut attrs: Attributes, #[default] child: View) -> Result {
48 view! {
49 <div
50 class=(class!("flex flex-col gap-1.5 px-6", attrs.remove("class")))
51 (attrs)
52 >
53 (child)
54 </div>
55 }
56}
57
58/// The heading of a [`card`], rendered as an `<h3>`.
59#[component]
60pub async fn card_title(#[default] mut attrs: Attributes, #[default] child: View) -> Result {
61 view! {
62 <h3 class=(class!("leading-none font-semibold", attrs.remove("class"))) (attrs)>
63 (child)
64 </h3>
65 }
66}
67
68/// The supporting text under a [`card_title`].
69#[component]
70pub async fn card_description(#[default] mut attrs: Attributes, #[default] child: View) -> Result {
71 view! {
72 <p
73 class=(class!("text-sm text-muted-foreground", attrs.remove("class")))
74 (attrs)
75 >
76 (child)
77 </p>
78 }
79}
80
81/// The main body of a [`card`].
82#[component]
83pub async fn card_content(#[default] mut attrs: Attributes, #[default] child: View) -> Result {
84 view! { <div class=(class!("px-6", attrs.remove("class"))) (attrs)>(child)</div> }
85}
86
87/// The closing section of a [`card`], a horizontal row for actions.
88#[component]
89pub async fn card_footer(#[default] mut attrs: Attributes, #[default] child: View) -> Result {
90 view! {
91 <div
92 class=(class!("flex items-center gap-2 px-6", attrs.remove("class")))
93 (attrs)
94 >
95 (child)
96 </div>
97 }
98}