ironhtml_bootstrap/
collapse.rs

1//! Bootstrap collapse components.
2//!
3//! Provides type-safe Bootstrap collapse functionality matching the
4//! [Bootstrap collapse documentation](https://getbootstrap.com/docs/5.3/components/collapse/).
5
6use ironhtml::typed::Element;
7use ironhtml_elements::{Button, Div, A};
8
9extern crate alloc;
10use alloc::format;
11
12/// Create a collapse trigger button.
13///
14/// ## Example
15///
16/// ```rust
17/// use ironhtml_bootstrap::collapse::collapse_button;
18///
19/// let btn = collapse_button("collapseExample", "Toggle content");
20/// assert!(btn.render().contains("data-bs-toggle"));
21/// ```
22#[must_use]
23pub fn collapse_button(target_id: &str, text: &str) -> Element<Button> {
24    let target = format!("#{target_id}");
25    Element::<Button>::new()
26        .class("btn btn-primary")
27        .attr("type", "button")
28        .attr("data-bs-toggle", "collapse")
29        .attr("data-bs-target", &target)
30        .attr("aria-expanded", "false")
31        .attr("aria-controls", target_id)
32        .text(text)
33}
34
35/// Create a collapse trigger link.
36#[must_use]
37pub fn collapse_link(target_id: &str, text: &str) -> Element<A> {
38    let href = format!("#{target_id}");
39    Element::<A>::new()
40        .class("btn btn-primary")
41        .attr("data-bs-toggle", "collapse")
42        .attr("href", &href)
43        .attr("role", "button")
44        .attr("aria-expanded", "false")
45        .attr("aria-controls", target_id)
46        .text(text)
47}
48
49/// Create a collapse content container.
50///
51/// ## Example
52///
53/// ```rust
54/// use ironhtml_bootstrap::collapse::{collapse_button, collapse_content};
55///
56///
57/// let button = collapse_button("demo", "Click me");
58/// let content = collapse_content("demo", |div| {
59///     div.class("card card-body").text("Hidden content here")
60/// });
61///
62/// assert!(content.render().contains("collapse"));
63/// ```
64#[must_use]
65pub fn collapse_content<F>(id: &str, f: F) -> Element<Div>
66where
67    F: FnOnce(Element<Div>) -> Element<Div>,
68{
69    f(Element::<Div>::new().class("collapse").attr("id", id))
70}
71
72/// Create a collapse content container that starts open.
73#[must_use]
74pub fn collapse_content_show<F>(id: &str, f: F) -> Element<Div>
75where
76    F: FnOnce(Element<Div>) -> Element<Div>,
77{
78    f(Element::<Div>::new().class("collapse show").attr("id", id))
79}
80
81/// Create a horizontal collapse content container.
82#[must_use]
83pub fn collapse_horizontal<F>(id: &str, f: F) -> Element<Div>
84where
85    F: FnOnce(Element<Div>) -> Element<Div>,
86{
87    f(Element::<Div>::new()
88        .class("collapse collapse-horizontal")
89        .attr("id", id))
90}
91
92/// Create a button that triggers multiple collapse targets.
93#[must_use]
94pub fn collapse_multi_button(target_class: &str, text: &str) -> Element<Button> {
95    let target = format!(".{target_class}");
96    Element::<Button>::new()
97        .class("btn btn-primary")
98        .attr("type", "button")
99        .attr("data-bs-toggle", "collapse")
100        .attr("data-bs-target", &target)
101        .attr("aria-expanded", "false")
102        .text(text)
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_collapse_button() {
111        let btn = collapse_button("demo", "Toggle");
112        let html = btn.render();
113        assert!(html.contains("data-bs-toggle=\"collapse\""));
114        assert!(html.contains("data-bs-target=\"#demo\""));
115        assert!(html.contains("aria-controls=\"demo\""));
116    }
117
118    #[test]
119    fn test_collapse_content() {
120        let content = collapse_content("demo", |div| div.text("Content"));
121        let html = content.render();
122        assert!(html.contains("collapse"));
123        assert!(html.contains("id=\"demo\""));
124    }
125
126    #[test]
127    fn test_collapse_horizontal() {
128        let content = collapse_horizontal("demo", |div| div.text("Content"));
129        assert!(content.render().contains("collapse-horizontal"));
130    }
131}