1use ironhtml::typed::Element;
7use ironhtml_elements::{Button, Div, Img, Small, Strong};
8
9extern crate alloc;
10use alloc::format;
11use alloc::string::ToString;
12
13#[must_use]
24pub fn toast(message: &str, time: &str) -> Element<Div> {
25 Element::<Div>::new()
26 .class("toast")
27 .attr("role", "alert")
28 .attr("aria-live", "assertive")
29 .attr("aria-atomic", "true")
30 .child::<Div, _>(|header| {
31 header
32 .class("toast-header")
33 .child::<Strong, _>(|s| s.class("me-auto").text("Bootstrap"))
34 .child::<Small, _>(|s| s.text(time))
35 .child::<Button, _>(|b| {
36 b.attr("type", "button")
37 .class("btn-close")
38 .attr("data-bs-dismiss", "toast")
39 .attr("aria-label", "Close")
40 })
41 })
42 .child::<Div, _>(|body| body.class("toast-body").text(message))
43}
44
45#[must_use]
47pub fn toast_titled(title: &str, message: &str, time: &str) -> Element<Div> {
48 Element::<Div>::new()
49 .class("toast")
50 .attr("role", "alert")
51 .attr("aria-live", "assertive")
52 .attr("aria-atomic", "true")
53 .child::<Div, _>(|header| {
54 header
55 .class("toast-header")
56 .child::<Strong, _>(|s| s.class("me-auto").text(title))
57 .child::<Small, _>(|s| s.class("text-body-secondary").text(time))
58 .child::<Button, _>(|b| {
59 b.attr("type", "button")
60 .class("btn-close")
61 .attr("data-bs-dismiss", "toast")
62 .attr("aria-label", "Close")
63 })
64 })
65 .child::<Div, _>(|body| body.class("toast-body").text(message))
66}
67
68#[must_use]
70pub fn toast_with_image(
71 img_src: &str,
72 img_alt: &str,
73 title: &str,
74 message: &str,
75 time: &str,
76) -> Element<Div> {
77 Element::<Div>::new()
78 .class("toast")
79 .attr("role", "alert")
80 .attr("aria-live", "assertive")
81 .attr("aria-atomic", "true")
82 .child::<Div, _>(|header| {
83 header
84 .class("toast-header")
85 .child::<Img, _>(|i| {
86 i.attr("src", img_src)
87 .attr("alt", img_alt)
88 .class("rounded me-2")
89 .attr("style", "width: 20px; height: 20px;")
90 })
91 .child::<Strong, _>(|s| s.class("me-auto").text(title))
92 .child::<Small, _>(|s| s.text(time))
93 .child::<Button, _>(|b| {
94 b.attr("type", "button")
95 .class("btn-close")
96 .attr("data-bs-dismiss", "toast")
97 .attr("aria-label", "Close")
98 })
99 })
100 .child::<Div, _>(|body| body.class("toast-body").text(message))
101}
102
103#[must_use]
105pub fn toast_simple(message: &str) -> Element<Div> {
106 Element::<Div>::new()
107 .class("toast align-items-center")
108 .attr("role", "alert")
109 .attr("aria-live", "assertive")
110 .attr("aria-atomic", "true")
111 .child::<Div, _>(|d| {
112 d.class("d-flex")
113 .child::<Div, _>(|body| body.class("toast-body").text(message))
114 .child::<Button, _>(|b| {
115 b.attr("type", "button")
116 .class("btn-close me-2 m-auto")
117 .attr("data-bs-dismiss", "toast")
118 .attr("aria-label", "Close")
119 })
120 })
121}
122
123#[must_use]
125pub fn toast_colored(color: crate::Color, message: &str) -> Element<Div> {
126 let class = format!(
127 "toast align-items-center text-bg-{} border-0",
128 color.as_str()
129 );
130
131 Element::<Div>::new()
132 .class(&class)
133 .attr("role", "alert")
134 .attr("aria-live", "assertive")
135 .attr("aria-atomic", "true")
136 .child::<Div, _>(|d| {
137 d.class("d-flex")
138 .child::<Div, _>(|body| body.class("toast-body").text(message))
139 .child::<Button, _>(|b| {
140 let btn_class = if matches!(color, crate::Color::Light) {
141 "btn-close me-2 m-auto"
142 } else {
143 "btn-close btn-close-white me-2 m-auto"
144 };
145 b.attr("type", "button")
146 .class(btn_class)
147 .attr("data-bs-dismiss", "toast")
148 .attr("aria-label", "Close")
149 })
150 })
151}
152
153#[must_use]
157pub fn toast_container<F>(position_class: &str, f: F) -> Element<Div>
158where
159 F: FnOnce(Element<Div>) -> Element<Div>,
160{
161 let class = format!("toast-container position-fixed p-3 {position_class}");
162 f(Element::<Div>::new().class(&class))
163}
164
165#[must_use]
167pub fn toast_autohide(message: &str, delay_ms: u32) -> Element<Div> {
168 Element::<Div>::new()
169 .class("toast")
170 .attr("role", "alert")
171 .attr("aria-live", "assertive")
172 .attr("aria-atomic", "true")
173 .attr("data-bs-autohide", "true")
174 .attr("data-bs-delay", delay_ms.to_string())
175 .child::<Div, _>(|header| {
176 header
177 .class("toast-header")
178 .child::<Strong, _>(|s| s.class("me-auto").text("Notification"))
179 .child::<Button, _>(|b| {
180 b.attr("type", "button")
181 .class("btn-close")
182 .attr("data-bs-dismiss", "toast")
183 .attr("aria-label", "Close")
184 })
185 })
186 .child::<Div, _>(|body| body.class("toast-body").text(message))
187}
188
189#[must_use]
191pub fn toast_show(message: &str, time: &str) -> Element<Div> {
192 Element::<Div>::new()
193 .class("toast show")
194 .attr("role", "alert")
195 .attr("aria-live", "assertive")
196 .attr("aria-atomic", "true")
197 .child::<Div, _>(|header| {
198 header
199 .class("toast-header")
200 .child::<Strong, _>(|s| s.class("me-auto").text("Bootstrap"))
201 .child::<Small, _>(|s| s.text(time))
202 .child::<Button, _>(|b| {
203 b.attr("type", "button")
204 .class("btn-close")
205 .attr("data-bs-dismiss", "toast")
206 .attr("aria-label", "Close")
207 })
208 })
209 .child::<Div, _>(|body| body.class("toast-body").text(message))
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_toast() {
218 let t = toast("Hello!", "just now");
219 let html = t.render();
220 assert!(html.contains("toast"));
221 assert!(html.contains("toast-header"));
222 assert!(html.contains("toast-body"));
223 assert!(html.contains("Hello!"));
224 }
225
226 #[test]
227 fn test_toast_colored() {
228 let t = toast_colored(crate::Color::Success, "Success!");
229 let html = t.render();
230 assert!(html.contains("text-bg-success"));
231 assert!(html.contains("btn-close-white"));
232 }
233
234 #[test]
235 fn test_toast_simple() {
236 let t = toast_simple("Simple message");
237 let html = t.render();
238 assert!(html.contains("toast"));
239 assert!(!html.contains("toast-header"));
240 }
241
242 #[test]
243 fn test_toast_container() {
244 let container = toast_container("top-0 end-0", |c| {
245 c.child::<Div, _>(|_| toast_show("Message 1", "now"))
246 .child::<Div, _>(|_| toast_show("Message 2", "now"))
247 });
248 let html = container.render();
249 assert!(html.contains("toast-container"));
250 assert!(html.contains("position-fixed"));
251 }
252}