Skip to main content

dioxus_bootstrap_css/
card.rs

1use dioxus::prelude::*;
2
3/// Bootstrap Card component with optional header, body, and footer slots.
4///
5/// # Bootstrap HTML → Dioxus
6///
7/// ```html
8/// <!-- Bootstrap HTML -->
9/// <div class="card">
10///   <div class="card-header">Title</div>
11///   <div class="card-body"><p>Content</p></div>
12///   <div class="card-footer">Footer</div>
13/// </div>
14/// ```
15///
16/// ```rust,no_run
17/// # use dioxus::prelude::*;
18/// # use dioxus_bootstrap_css::prelude::*;
19/// # fn _doctest() -> Element {
20/// // Dioxus equivalent
21/// rsx! {
22///     Card {
23///         header: rsx! { "Card Title" },
24///         body: rsx! { p { "Card content goes here." } },
25///         footer: rsx! { "Last updated 3 mins ago" },
26///     }
27///     // Body-only card
28///     Card { body: rsx! { "Simple card" } }
29///     // Card with custom header styling (e.g., flex layout with action buttons)
30///     Card {
31///         class: "mb-3",
32///         header_class: "d-flex justify-content-between align-items-center py-2",
33///         body_class: "py-2",
34///         header: rsx! {
35///             span { class: "small", "Server" }
36///             button { class: "btn btn-sm btn-outline-secondary py-0 px-1",
37///                 i { class: "bi bi-arrow-clockwise small" }
38///             }
39///         },
40///         body: rsx! { p { "Stats here" } },
41///     }
42///     // Custom layout (children go inside card, outside body)
43///     Card { class: "text-center",
44///         img { class: "card-img-top", src: "/photo.jpg" }
45///         div { class: "card-body", h5 { "Title" } p { "Text" } }
46///     }
47/// }
48/// # }
49/// ```
50#[derive(Clone, PartialEq, Props)]
51pub struct CardProps {
52    /// Card header content.
53    #[props(default)]
54    pub header: Option<Element>,
55    /// Card body content.
56    #[props(default)]
57    pub body: Option<Element>,
58    /// Card footer content.
59    #[props(default)]
60    pub footer: Option<Element>,
61    /// Additional CSS classes for the card container.
62    #[props(default)]
63    pub class: String,
64    /// Additional CSS classes for the card-header div.
65    #[props(default)]
66    pub header_class: String,
67    /// Additional CSS classes for the card body.
68    #[props(default)]
69    pub body_class: String,
70    /// Additional CSS classes for the card-footer div.
71    #[props(default)]
72    pub footer_class: String,
73    /// Any additional HTML attributes.
74    #[props(extends = GlobalAttributes)]
75    attributes: Vec<Attribute>,
76    /// Child elements (rendered inside card, outside body — for custom layouts).
77    #[props(default)]
78    pub children: Element,
79}
80
81#[component]
82pub fn Card(props: CardProps) -> Element {
83    let full_class = if props.class.is_empty() {
84        "card".to_string()
85    } else {
86        format!("card {}", props.class)
87    };
88
89    let header_class = if props.header_class.is_empty() {
90        "card-header".to_string()
91    } else {
92        format!("card-header {}", props.header_class)
93    };
94
95    let body_class = if props.body_class.is_empty() {
96        "card-body".to_string()
97    } else {
98        format!("card-body {}", props.body_class)
99    };
100
101    let footer_class = if props.footer_class.is_empty() {
102        "card-footer".to_string()
103    } else {
104        format!("card-footer {}", props.footer_class)
105    };
106
107    rsx! {
108        div { class: "{full_class}",
109            ..props.attributes,
110            if let Some(header) = props.header {
111                div { class: "{header_class}", {header} }
112            }
113            if let Some(body) = props.body {
114                div { class: "{body_class}", {body} }
115            }
116            {props.children}
117            if let Some(footer) = props.footer {
118                div { class: "{footer_class}", {footer} }
119            }
120        }
121    }
122}