ironhtml_bootstrap/alerts.rs
1//! Bootstrap alert components.
2//!
3//! Provides type-safe Bootstrap alerts matching the
4//! [Bootstrap alerts documentation](https://getbootstrap.com/docs/5.3/components/alerts/).
5//!
6//! ## Example
7//!
8//! ```rust
9//! use ironhtml_bootstrap::{alerts::*, Color};
10//!
11//! // Simple alert
12//! let warning = alert(Color::Warning, "This is a warning!");
13//! assert!(warning.render().contains(r#"class="alert alert-warning"#));
14//! assert!(warning.render().contains(r#"role="alert"#));
15//!
16//! // Dismissible alert
17//! let dismissible = alert_dismissible(Color::Danger, "Error occurred!");
18//! assert!(dismissible.render().contains("alert-dismissible"));
19//! assert!(dismissible.render().contains("btn-close"));
20//! ```
21
22use crate::Color;
23use ironhtml::typed::Element;
24use ironhtml_elements::{Button, Div};
25
26extern crate alloc;
27use alloc::format;
28
29/// Create a simple Bootstrap alert.
30///
31/// Generates:
32/// ```html
33/// <div class="alert alert-{color}" role="alert">{text}</div>
34/// ```
35///
36/// ## Example
37///
38/// ```rust
39/// use ironhtml_bootstrap::{alerts::alert, Color};
40///
41/// let a = alert(Color::Primary, "A simple primary alert");
42/// assert!(a.render().contains(r#"alert-primary"#));
43/// ```
44#[must_use]
45pub fn alert(color: Color, text: &str) -> Element<Div> {
46 let class = format!("alert alert-{}", color.as_str());
47 Element::<Div>::new()
48 .class(&class)
49 .attr("role", "alert")
50 .text(text)
51}
52
53/// Create an alert with custom content.
54///
55/// Allows adding child elements like links, headings, etc.
56///
57/// ## Example
58///
59/// ```rust
60/// use ironhtml_bootstrap::{alerts::alert_with, Color};
61/// use ironhtml_elements::{A, Strong};
62///
63/// let a = alert_with(Color::Info, |div| {
64/// div.child::<Strong, _>(|s| s.text("Note: "))
65/// .text("Check ")
66/// .child::<A, _>(|a| a.class("alert-link").attr("href", "#").text("this link"))
67/// });
68/// assert!(a.render().contains("alert-info"));
69/// assert!(a.render().contains("alert-link"));
70/// ```
71#[must_use]
72pub fn alert_with<F>(color: Color, f: F) -> Element<Div>
73where
74 F: FnOnce(Element<Div>) -> Element<Div>,
75{
76 let class = format!("alert alert-{}", color.as_str());
77 f(Element::<Div>::new().class(&class).attr("role", "alert"))
78}
79
80/// Create a dismissible Bootstrap alert.
81///
82/// Generates:
83/// ```html
84/// <div class="alert alert-{color} alert-dismissible fade show" role="alert">
85/// {text}
86/// <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
87/// </div>
88/// ```
89///
90/// ## Example
91///
92/// ```rust
93/// use ironhtml_bootstrap::{alerts::alert_dismissible, Color};
94///
95/// let a = alert_dismissible(Color::Warning, "Holy guacamole!");
96/// let html = a.render();
97/// assert!(html.contains("alert-dismissible"));
98/// assert!(html.contains("fade show"));
99/// assert!(html.contains("btn-close"));
100/// assert!(html.contains(r#"data-bs-dismiss="alert"#));
101/// ```
102#[must_use]
103pub fn alert_dismissible(color: Color, text: &str) -> Element<Div> {
104 let class = format!("alert alert-{} alert-dismissible fade show", color.as_str());
105 Element::<Div>::new()
106 .class(&class)
107 .attr("role", "alert")
108 .text(text)
109 .child::<Button, _>(|btn| {
110 btn.attr("type", "button")
111 .class("btn-close")
112 .attr("data-bs-dismiss", "alert")
113 .attr("aria-label", "Close")
114 })
115}
116
117/// Create an alert with heading (from Bootstrap docs).
118///
119/// Generates:
120/// ```html
121/// <div class="alert alert-{color}" role="alert">
122/// <h4 class="alert-heading">{heading}</h4>
123/// <p>{text}</p>
124/// <hr>
125/// <p class="mb-0">{footer}</p>
126/// </div>
127/// ```
128#[must_use]
129pub fn alert_with_heading(color: Color, heading: &str, text: &str, footer: &str) -> Element<Div> {
130 use ironhtml_elements::{Hr, H4, P};
131
132 let class = format!("alert alert-{}", color.as_str());
133 Element::<Div>::new()
134 .class(&class)
135 .attr("role", "alert")
136 .child::<H4, _>(|h| h.class("alert-heading").text(heading))
137 .child::<P, _>(|p| p.text(text))
138 .child::<Hr, _>(|hr| hr)
139 .child::<P, _>(|p| p.class("mb-0").text(footer))
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_simple_alert() {
148 let a = alert(Color::Success, "Well done!");
149 let html = a.render();
150 assert!(html.contains(r#"class="alert alert-success"#));
151 assert!(html.contains(r#"role="alert"#));
152 assert!(html.contains("Well done!"));
153 }
154
155 #[test]
156 fn test_dismissible_alert() {
157 let a = alert_dismissible(Color::Warning, "Warning!");
158 let html = a.render();
159 assert!(html.contains("alert-dismissible"));
160 assert!(html.contains("fade show"));
161 assert!(html.contains("btn-close"));
162 assert!(html.contains(r#"data-bs-dismiss="alert"#));
163 }
164
165 #[test]
166 fn test_alert_with_heading() {
167 let a = alert_with_heading(Color::Success, "Well done!", "Content here", "Footer");
168 let html = a.render();
169 assert!(html.contains("alert-heading"));
170 assert!(html.contains("Well done!"));
171 assert!(html.contains("<hr"));
172 assert!(html.contains("mb-0"));
173 }
174
175 #[test]
176 fn test_all_colors() {
177 for color in [
178 Color::Primary,
179 Color::Secondary,
180 Color::Success,
181 Color::Danger,
182 Color::Warning,
183 Color::Info,
184 Color::Light,
185 Color::Dark,
186 ] {
187 let a = alert(color, "Test");
188 assert!(a.render().contains(&format!("alert-{}", color.as_str())));
189 }
190 }
191}