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