daisy_rsx 0.1.56

Daisy UI components for the Dioxus Rust library.
Documentation
use crate::marketing::site_header::SiteHeader;
use dioxus::prelude::*;

#[derive(PartialEq, Clone, Eq, Debug)]
pub enum Section {
    None,
    Home,
    Enterprise,
    Partners,
    McpServers,
    Pricing,
    Blog,
    Docs,
    ArchitectCourse,
    Contact,
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NavigationLink {
    pub href: String,
    pub label: String,
    pub section: Section,
    pub class: Option<String>,
    pub hx_boost: bool,
    pub badge_image: Option<String>,
    pub badge_alt: Option<String>,
}

impl NavigationLink {
    pub fn new(label: impl Into<String>, href: impl Into<String>, section: Section) -> Self {
        Self {
            href: href.into(),
            label: label.into(),
            section,
            class: None,
            hx_boost: true,
            badge_image: None,
            badge_alt: None,
        }
    }

    pub fn external(label: impl Into<String>, href: impl Into<String>, section: Section) -> Self {
        Self {
            href: href.into(),
            label: label.into(),
            section,
            class: None,
            hx_boost: false,
            badge_image: None,
            badge_alt: None,
        }
    }

    pub fn with_class(mut self, class: impl Into<String>) -> Self {
        self.class = Some(class.into());
        self
    }

    pub fn with_badge_image(mut self, src: impl Into<String>, alt: impl Into<String>) -> Self {
        self.badge_image = Some(src.into());
        self.badge_alt = Some(alt.into());
        self
    }
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct NavigationMenu {
    pub label: String,
    pub links: Vec<NavigationLink>,
}

impl NavigationMenu {
    pub fn new(label: impl Into<String>, links: Vec<NavigationLink>) -> Self {
        Self {
            label: label.into(),
            links,
        }
    }
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum NavigationEntry {
    Link(NavigationLink),
    Menu(NavigationMenu),
}

#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct NavigationModel {
    pub home: String,
    pub logo_src: Option<String>,
    pub logo_alt: Option<String>,
    pub desktop_left: Vec<NavigationEntry>,
    pub desktop_right: Vec<NavigationLink>,
    pub mobile: Vec<NavigationLink>,
}

#[component]
pub fn NavItem(link: NavigationLink, current_section: Section) -> Element {
    let mut added_class = "";
    if link.section == current_section {
        added_class = "underline";
    }

    let mut class = link.class.unwrap_or_default();
    if !added_class.is_empty() {
        if !class.is_empty() {
            class.push(' ');
        }
        class.push_str(added_class);
    }

    rsx! {
        li {
            a {
                class: class,
                "hx-boost": if link.hx_boost { "true" } else { "false" },
                href: link.href,
                if let Some(src) = link.badge_image {
                    img {
                        src: src,
                        alt: link.badge_alt.unwrap_or_default()
                    }
                } else {
                    "{link.label}"
                }
            }
        }
    }
}

#[component]
fn DesktopEntry(entry: NavigationEntry, current_section: Section) -> Element {
    match entry {
        NavigationEntry::Link(link) => rsx!(
            NavItem {
                link,
                current_section,
            }
        ),
        NavigationEntry::Menu(menu) => rsx!(
            li {
                details {
                    summary {
                        "{menu.label}"
                    }
                    ul {
                        class: "p-2",
                        for link in menu.links {
                            NavItem {
                                link,
                                current_section: current_section.clone(),
                            }
                        }
                    }
                }
            }
        ),
    }
}

#[component]
pub fn Navigation(
    mobile_menu: Option<Element>,
    section: Section,
    model: NavigationModel,
    brand: Option<String>,
    site_header: Option<SiteHeader>,
) -> Element {
    let brand = brand.unwrap_or_else(|| "Bionic".to_string());

    rsx! {
        header {
            class: "sticky top-0 z-50 site-nav-header",
            if let Some(site_header) = site_header {
                {site_header}
            }
            div {
                class: "backdrop-filter backdrop-blur-lg bg-base-100/80 border-b border-base-300 site-nav-shell",
                div {
                    class: "navbar justify-between site-nav-bar",

                    div {
                        class: "flex items-center gap-4",
                        a {
                            href: model.home.clone(),
                            span {
                                class: "pl-3 flex flex-row items-center gap-2",
                                if let Some(logo_src) = model.logo_src {
                                    img {
                                        class: "h-8 w-auto",
                                        src: logo_src,
                                        alt: model.logo_alt.unwrap_or_else(|| format!("{brand} logo"))
                                    }
                                }
                                strong {
                                    class: "leading-none",
                                    "{brand}"
                                }
                            }
                        }

                        div { class: "hidden lg:flex",
                            ul { class: "menu menu-horizontal px-1 dropdown-content",
                                for entry in model.desktop_left {
                                    DesktopEntry {
                                        entry,
                                        current_section: section.clone(),
                                    }
                                }
                            }
                        }
                    }

                    div { class: "hidden lg:flex items-center",
                        ul { class: "menu menu-horizontal px-3",
                            for link in model.desktop_right {
                                NavItem {
                                    link,
                                    current_section: section.clone(),
                                }
                            }
                        }
                    }

                    div { class: "dropdown lg:hidden dropdown-end",
                        div {
                            tabindex: "0",
                            role: "button",
                            class: "btn btn-ghost",
                            svg {
                                xmlns: "http://www.w3.org/2000/svg",
                                stroke: "currentColor",
                                view_box: "0 0 24 24",
                                fill: "none",
                                class: "h-5 w-5",
                                path {
                                    d: "M4 6h16M4 12h8m-8 6h16",
                                    stroke_linejoin: "round",
                                    stroke_linecap: "round",
                                    stroke_width: "2"
                                }
                            }
                        }
                        ul {
                            class: "menu menu-sm dropdown-content mt-3 z-1 p-2 shadow-sm bg-base-100 rounded-box w-52",
                            for link in model.mobile {
                                NavItem {
                                    link,
                                    current_section: section.clone(),
                                }
                            }
                            {mobile_menu}
                        }
                    }
                }
            }
        }
    }
}