ironhtml_bootstrap/
accordion.rs1use ironhtml::typed::Element;
7use ironhtml_elements::{Button, Div, H2};
8
9extern crate alloc;
10use alloc::format;
11use alloc::string::String;
12
13pub struct AccordionItem {
15 pub id: String,
16 pub header: String,
17 pub content: String,
18 pub expanded: bool,
19}
20
21#[must_use]
47pub fn accordion(id: &str, items: &[AccordionItem]) -> Element<Div> {
48 let mut acc = Element::<Div>::new().class("accordion").id(id);
49
50 for item in items {
51 acc = acc.child::<Div, _>(|_| accordion_item(id, item));
52 }
53
54 acc
55}
56
57#[must_use]
59pub fn accordion_flush(id: &str, items: &[AccordionItem]) -> Element<Div> {
60 let mut acc = Element::<Div>::new()
61 .class("accordion accordion-flush")
62 .id(id);
63
64 for item in items {
65 acc = acc.child::<Div, _>(|_| accordion_item(id, item));
66 }
67
68 acc
69}
70
71fn accordion_item(parent_id: &str, item: &AccordionItem) -> Element<Div> {
73 let collapse_id = format!("collapse{}", item.id);
74 let heading_id = format!("heading{}", item.id);
75 let target = format!("#{collapse_id}");
76 let parent = format!("#{parent_id}");
77
78 let button_class = if item.expanded {
79 "accordion-button"
80 } else {
81 "accordion-button collapsed"
82 };
83
84 let collapse_class = if item.expanded {
85 "accordion-collapse collapse show"
86 } else {
87 "accordion-collapse collapse"
88 };
89
90 Element::<Div>::new()
91 .class("accordion-item")
92 .child::<H2, _>(|h| {
93 h.class("accordion-header")
94 .id(&heading_id)
95 .child::<Button, _>(|btn| {
96 btn.class(button_class)
97 .attr("type", "button")
98 .attr("data-bs-toggle", "collapse")
99 .attr("data-bs-target", &target)
100 .attr(
101 "aria-expanded",
102 if item.expanded { "true" } else { "false" },
103 )
104 .attr("aria-controls", &collapse_id)
105 .text(&item.header)
106 })
107 })
108 .child::<Div, _>(|collapse| {
109 collapse
110 .id(&collapse_id)
111 .class(collapse_class)
112 .attr("aria-labelledby", &heading_id)
113 .attr("data-bs-parent", &parent)
114 .child::<Div, _>(|body| body.class("accordion-body").text(&item.content))
115 })
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use alloc::vec;
122
123 #[test]
124 fn test_accordion() {
125 let items = vec![
126 AccordionItem {
127 id: "one".into(),
128 header: "First".into(),
129 content: "Content 1".into(),
130 expanded: true,
131 },
132 AccordionItem {
133 id: "two".into(),
134 header: "Second".into(),
135 content: "Content 2".into(),
136 expanded: false,
137 },
138 ];
139
140 let acc = accordion("test", &items);
141 let html = acc.render();
142 assert!(html.contains("accordion"));
143 assert!(html.contains("accordion-item"));
144 assert!(html.contains("accordion-button"));
145 assert!(html.contains("collapse show"));
146 assert!(html.contains("collapsed"));
147 }
148}