ironhtml_bootstrap/
navbar.rs1use crate::NavbarExpand;
27use ironhtml::typed::Element;
28use ironhtml_elements::{Button, Div, Li, Nav, Span, Ul, A};
29
30extern crate alloc;
31use alloc::format;
32
33#[must_use]
48pub fn navbar<F>(brand: &str, expand: NavbarExpand, id: &str, f: F) -> Element<Nav>
49where
50 F: FnOnce(Element<Ul>) -> Element<Ul>,
51{
52 let class = format!("navbar {} bg-body-tertiary", expand.as_class());
53 let target = format!("#{id}");
54
55 Element::<Nav>::new()
56 .class(&class)
57 .child::<Div, _>(|container| {
58 container
59 .class("container-fluid")
60 .child::<A, _>(|a| a.class("navbar-brand").attr("href", "#").text(brand))
61 .child::<Button, _>(|btn| {
62 btn.class("navbar-toggler")
63 .attr("type", "button")
64 .attr("data-bs-toggle", "collapse")
65 .attr("data-bs-target", &target)
66 .attr("aria-controls", id)
67 .attr("aria-expanded", "false")
68 .attr("aria-label", "Toggle navigation")
69 .child::<Span, _>(|s| s.class("navbar-toggler-icon"))
70 })
71 .child::<Div, _>(|collapse| {
72 collapse
73 .class("collapse navbar-collapse")
74 .id(id)
75 .child::<Ul, _>(|ul| f(ul.class("navbar-nav me-auto mb-2 mb-lg-0")))
76 })
77 })
78}
79
80#[must_use]
82pub fn navbar_dark<F>(brand: &str, expand: NavbarExpand, id: &str, f: F) -> Element<Nav>
83where
84 F: FnOnce(Element<Ul>) -> Element<Ul>,
85{
86 let class = format!("navbar {} bg-dark", expand.as_class());
87 let target = format!("#{id}");
88
89 Element::<Nav>::new()
90 .class(&class)
91 .attr("data-bs-theme", "dark")
92 .child::<Div, _>(|container| {
93 container
94 .class("container-fluid")
95 .child::<A, _>(|a| a.class("navbar-brand").attr("href", "#").text(brand))
96 .child::<Button, _>(|btn| {
97 btn.class("navbar-toggler")
98 .attr("type", "button")
99 .attr("data-bs-toggle", "collapse")
100 .attr("data-bs-target", &target)
101 .attr("aria-controls", id)
102 .attr("aria-expanded", "false")
103 .attr("aria-label", "Toggle navigation")
104 .child::<Span, _>(|s| s.class("navbar-toggler-icon"))
105 })
106 .child::<Div, _>(|collapse| {
107 collapse
108 .class("collapse navbar-collapse")
109 .id(id)
110 .child::<Ul, _>(|ul| f(ul.class("navbar-nav me-auto mb-2 mb-lg-0")))
111 })
112 })
113}
114
115#[must_use]
119pub fn nav_item(href: &str, text: &str, active: bool) -> Element<Li> {
120 let link_class = if active {
121 "nav-link active"
122 } else {
123 "nav-link"
124 };
125
126 if active {
127 Element::<Li>::new().class("nav-item").child::<A, _>(|a| {
128 a.class(link_class)
129 .attr("aria-current", "page")
130 .attr("href", href)
131 .text(text)
132 })
133 } else {
134 Element::<Li>::new()
135 .class("nav-item")
136 .child::<A, _>(|a| a.class(link_class).attr("href", href).text(text))
137 }
138}
139
140#[must_use]
142pub fn nav_item_disabled(text: &str) -> Element<Li> {
143 Element::<Li>::new().class("nav-item").child::<A, _>(|a| {
144 a.class("nav-link disabled")
145 .attr("aria-disabled", "true")
146 .text(text)
147 })
148}
149
150pub trait NavItemExt {
154 #[must_use]
156 fn child(self, item: Element<Li>) -> Self;
157}
158
159impl NavItemExt for Element<Ul> {
160 fn child(self, item: Element<Li>) -> Self {
161 self.child::<Li, _>(|_| item)
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_navbar() {
171 let nav = navbar("Brand", NavbarExpand::Lg, "nav", |ul| ul);
172 let html = nav.render();
173 assert!(html.contains("navbar"));
174 assert!(html.contains("navbar-expand-lg"));
175 assert!(html.contains("navbar-brand"));
176 assert!(html.contains("navbar-toggler"));
177 assert!(html.contains("collapse navbar-collapse"));
178 }
179
180 #[test]
181 fn test_nav_item() {
182 let item = nav_item("/home", "Home", true);
183 let html = item.render();
184 assert!(html.contains("nav-item"));
185 assert!(html.contains("nav-link active"));
186 assert!(html.contains(r#"aria-current="page"#));
187 }
188
189 #[test]
190 fn test_nav_item_inactive() {
191 let item = nav_item("/about", "About", false);
192 let html = item.render();
193 assert!(html.contains("nav-item"));
194 assert!(html.contains("nav-link"));
195 assert!(!html.contains("active"));
196 }
197}