ironhtml_bootstrap/
cards.rs1use crate::Color;
22use ironhtml::typed::Element;
23use ironhtml_elements::{Div, Img, A, H5, P};
24
25extern crate alloc;
26use alloc::format;
27
28#[must_use]
47pub fn card<F>(f: F) -> Element<Div>
48where
49 F: FnOnce(Element<Div>) -> Element<Div>,
50{
51 Element::<Div>::new()
52 .class("card")
53 .child::<Div, _>(|body| f(body.class("card-body")))
54}
55
56#[must_use]
58pub fn card_width<F>(width: &str, f: F) -> Element<Div>
59where
60 F: FnOnce(Element<Div>) -> Element<Div>,
61{
62 let style = format!("width: {width};");
63 Element::<Div>::new()
64 .class("card")
65 .attr("style", &style)
66 .child::<Div, _>(|body| f(body.class("card-body")))
67}
68
69#[must_use]
82pub fn card_simple(title: &str, text: &str, link_text: &str, link_href: &str) -> Element<Div> {
83 Element::<Div>::new()
84 .class("card")
85 .attr("style", "width: 18rem;")
86 .child::<Div, _>(|body| {
87 body.class("card-body")
88 .child::<H5, _>(|h| h.class("card-title").text(title))
89 .child::<P, _>(|p| p.class("card-text").text(text))
90 .child::<A, _>(|a| {
91 a.attr("href", link_href)
92 .class("btn btn-primary")
93 .text(link_text)
94 })
95 })
96}
97
98#[must_use]
111pub fn card_with_image(img_src: &str, img_alt: &str, title: &str, text: &str) -> Element<Div> {
112 Element::<Div>::new()
113 .class("card")
114 .attr("style", "width: 18rem;")
115 .child::<Img, _>(|img| {
116 img.attr("src", img_src)
117 .class("card-img-top")
118 .attr("alt", img_alt)
119 })
120 .child::<Div, _>(|body| {
121 body.class("card-body")
122 .child::<H5, _>(|h| h.class("card-title").text(title))
123 .child::<P, _>(|p| p.class("card-text").text(text))
124 })
125}
126
127#[must_use]
138pub fn card_with_header_footer<F>(header: &str, footer: &str, f: F) -> Element<Div>
139where
140 F: FnOnce(Element<Div>) -> Element<Div>,
141{
142 Element::<Div>::new()
143 .class("card")
144 .child::<Div, _>(|h| h.class("card-header").text(header))
145 .child::<Div, _>(|body| f(body.class("card-body")))
146 .child::<Div, _>(|foot| foot.class("card-footer text-body-secondary").text(footer))
147}
148
149#[must_use]
151pub fn card_colored<F>(color: Color, f: F) -> Element<Div>
152where
153 F: FnOnce(Element<Div>) -> Element<Div>,
154{
155 let class = format!("card text-bg-{}", color.as_str());
156 Element::<Div>::new()
157 .class(&class)
158 .child::<Div, _>(|body| f(body.class("card-body")))
159}
160
161#[must_use]
163pub fn card_border<F>(color: Color, f: F) -> Element<Div>
164where
165 F: FnOnce(Element<Div>) -> Element<Div>,
166{
167 let class = format!("card border-{}", color.as_str());
168 Element::<Div>::new()
169 .class(&class)
170 .child::<Div, _>(|body| f(body.class("card-body")))
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_basic_card() {
179 let c = card(|b| b.text("Hello"));
180 let html = c.render();
181 assert!(html.contains(r#"class="card"#));
182 assert!(html.contains(r#"class="card-body"#));
183 assert!(html.contains("Hello"));
184 }
185
186 #[test]
187 fn test_card_simple() {
188 let c = card_simple("Title", "Text content", "Click", "#");
189 let html = c.render();
190 assert!(html.contains("card-title"));
191 assert!(html.contains("Title"));
192 assert!(html.contains("card-text"));
193 assert!(html.contains("btn btn-primary"));
194 }
195
196 #[test]
197 fn test_card_with_image() {
198 let c = card_with_image("/img.jpg", "Alt text", "Title", "Description");
199 let html = c.render();
200 assert!(html.contains("card-img-top"));
201 assert!(html.contains(r#"src="/img.jpg"#));
202 assert!(html.contains(r#"alt="Alt text"#));
203 }
204
205 #[test]
206 fn test_card_with_header_footer() {
207 let c = card_with_header_footer("Header", "Footer", |b| b.text("Body"));
208 let html = c.render();
209 assert!(html.contains("card-header"));
210 assert!(html.contains("card-footer"));
211 assert!(html.contains("Header"));
212 assert!(html.contains("Footer"));
213 }
214}