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}