ironhtml_bootstrap/
breadcrumb.rs

1//! Bootstrap breadcrumb components.
2//!
3//! Provides type-safe Bootstrap breadcrumbs matching the
4//! [Bootstrap breadcrumb documentation](https://getbootstrap.com/docs/5.3/components/breadcrumb/).
5
6use ironhtml::typed::Element;
7use ironhtml_elements::{Li, Nav, Ol, A};
8
9extern crate alloc;
10use alloc::string::String;
11
12/// A breadcrumb item.
13pub struct BreadcrumbItem {
14    pub label: String,
15    pub href: Option<String>,
16    pub active: bool,
17}
18
19impl BreadcrumbItem {
20    /// Create a link breadcrumb item.
21    #[must_use]
22    pub fn link(label: impl Into<String>, href: impl Into<String>) -> Self {
23        Self {
24            label: label.into(),
25            href: Some(href.into()),
26            active: false,
27        }
28    }
29
30    /// Create the active (current) breadcrumb item.
31    #[must_use]
32    pub fn active(label: impl Into<String>) -> Self {
33        Self {
34            label: label.into(),
35            href: None,
36            active: true,
37        }
38    }
39}
40
41/// Create a Bootstrap breadcrumb navigation.
42///
43/// ## Example
44///
45/// ```rust
46/// use ironhtml_bootstrap::breadcrumb::{breadcrumb, BreadcrumbItem};
47///
48/// let items = vec![
49///     BreadcrumbItem::link("Home", "/"),
50///     BreadcrumbItem::link("Library", "/library"),
51///     BreadcrumbItem::active("Data"),
52/// ];
53///
54/// let nav = breadcrumb(&items);
55/// assert!(nav.render().contains("breadcrumb"));
56/// assert!(nav.render().contains(r#"aria-current="page"#));
57/// ```
58#[must_use]
59pub fn breadcrumb(items: &[BreadcrumbItem]) -> Element<Nav> {
60    Element::<Nav>::new()
61        .attr("aria-label", "breadcrumb")
62        .child::<Ol, _>(|ol| {
63            items.iter().fold(ol.class("breadcrumb"), |ol, item| {
64                ol.child::<Li, _>(|_| breadcrumb_item(item))
65            })
66        })
67}
68
69/// Create a single breadcrumb item.
70fn breadcrumb_item(item: &BreadcrumbItem) -> Element<Li> {
71    if item.active {
72        Element::<Li>::new()
73            .class("breadcrumb-item active")
74            .attr("aria-current", "page")
75            .text(&item.label)
76    } else if let Some(ref href) = item.href {
77        Element::<Li>::new()
78            .class("breadcrumb-item")
79            .child::<A, _>(|a| a.attr("href", href).text(&item.label))
80    } else {
81        Element::<Li>::new()
82            .class("breadcrumb-item")
83            .text(&item.label)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use alloc::vec;
91
92    #[test]
93    fn test_breadcrumb() {
94        let items = vec![
95            BreadcrumbItem::link("Home", "/"),
96            BreadcrumbItem::link("Products", "/products"),
97            BreadcrumbItem::active("Widget"),
98        ];
99
100        let nav = breadcrumb(&items);
101        let html = nav.render();
102        assert!(html.contains("breadcrumb"));
103        assert!(html.contains(r#"aria-label="breadcrumb"#));
104        assert!(html.contains(r#"href="/"#));
105        assert!(html.contains(r#"aria-current="page"#));
106        assert!(html.contains("active"));
107    }
108}