maud-ui 0.2.1

64 headless, accessible UI components for Rust web apps — shadcn Base UI API parity. Plus block templates, a live theme customiser, and shell hooks for 15 third-party widgets (Monaco, xyflow, Excalidraw, Three.js, AG Grid, Leaflet, FullCalendar, SortableJS, and more). Built on maud + htmx, styled like shadcn/ui.
Documentation
//! Menubar component — horizontal menu bar with top-level triggers and dropdown menus

use crate::primitives::menu::{render_entry, MenuEntry, MenuItem, RadioItem};
use maud::{html, Markup};

/// A single menu within the menubar (e.g. "File", "Edit", "View")
#[derive(Debug, Clone)]
pub struct MenubarMenu {
    pub label: String,
    pub items: Vec<MenuEntry>,
}

/// Menubar rendering properties
#[derive(Debug, Clone)]
pub struct Props {
    /// Unique identifier for the menubar
    pub id: String,
    /// Top-level menus
    pub menus: Vec<MenubarMenu>,
}

/// Render a menubar with the given properties
pub fn render(props: Props) -> Markup {
    html! {
        div.mui-menubar data-mui="menubar" role="menubar" aria-orientation="horizontal" id=(props.id) {
            @for (i, menu) in props.menus.iter().enumerate() {
                div.mui-menubar__menu {
                    button.mui-menubar__trigger
                        type="button"
                        role="menuitem"
                        aria-expanded="false"
                        aria-haspopup="menu"
                        aria-controls=(format!("{}-menu-{}", props.id, i))
                        tabindex=(if i == 0 { "0" } else { "-1" })
                    {
                        (menu.label)
                    }
                    div.mui-menubar__content
                        id=(format!("{}-menu-{}", props.id, i))
                        role="menu"
                        hidden
                    {
                        @for entry in &menu.items {
                            (render_entry(entry))
                        }
                    }
                }
            }
        }
    }
}

/// Showcase menubar component
pub fn showcase() -> Markup {
    html! {
        div.mui-showcase__grid {
            div {
                p.mui-showcase__caption { "Classic app menubar" }
                div.mui-showcase__row {
                    (render(Props {
                        id: "demo-menubar-1".into(),
                        menus: vec![
                            MenubarMenu {
                                label: "File".into(),
                                items: vec![
                                    MenuEntry::Item(MenuItem {
                                        label: "New".into(),
                                        action: "new".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: Some("\u{2318}N".into()),
                                    }),
                                    MenuEntry::Item(MenuItem {
                                        label: "Open".into(),
                                        action: "open".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: Some("\u{2318}O".into()),
                                    }),
                                    MenuEntry::Item(MenuItem {
                                        label: "Save".into(),
                                        action: "save".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: Some("\u{2318}S".into()),
                                    }),
                                    MenuEntry::Separator,
                                    MenuEntry::Item(MenuItem {
                                        label: "Exit".into(),
                                        action: "exit".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: None,
                                    }),
                                ],
                            },
                            MenubarMenu {
                                label: "Edit".into(),
                                items: vec![
                                    MenuEntry::Item(MenuItem {
                                        label: "Undo".into(),
                                        action: "undo".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: Some("\u{2318}Z".into()),
                                    }),
                                    MenuEntry::Item(MenuItem {
                                        label: "Redo".into(),
                                        action: "redo".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: Some("\u{2325}\u{2318}Z".into()),
                                    }),
                                    MenuEntry::Separator,
                                    MenuEntry::Item(MenuItem {
                                        label: "Cut".into(),
                                        action: "cut".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: Some("\u{2318}X".into()),
                                    }),
                                    MenuEntry::Item(MenuItem {
                                        label: "Copy".into(),
                                        action: "copy".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: Some("\u{2318}C".into()),
                                    }),
                                    MenuEntry::Item(MenuItem {
                                        label: "Paste".into(),
                                        action: "paste".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: Some("\u{2318}V".into()),
                                    }),
                                ],
                            },
                            MenubarMenu {
                                label: "View".into(),
                                items: vec![
                                    MenuEntry::CheckboxItem {
                                        label: "Show Sidebar".into(),
                                        checked: true,
                                        disabled: false,
                                        shortcut: Some("\u{2318}\\".into()),
                                    },
                                    MenuEntry::CheckboxItem {
                                        label: "Show Status Bar".into(),
                                        checked: false,
                                        disabled: false,
                                        shortcut: None,
                                    },
                                    MenuEntry::Separator,
                                    MenuEntry::Label("Theme".into()),
                                    MenuEntry::RadioGroup {
                                        name: "theme".into(),
                                        items: vec![
                                            RadioItem {
                                                label: "Light".into(),
                                                value: "light".into(),
                                                checked: false,
                                                disabled: false,
                                                shortcut: None,
                                            },
                                            RadioItem {
                                                label: "Dark".into(),
                                                value: "dark".into(),
                                                checked: true,
                                                disabled: false,
                                                shortcut: None,
                                            },
                                            RadioItem {
                                                label: "System".into(),
                                                value: "system".into(),
                                                checked: false,
                                                disabled: false,
                                                shortcut: None,
                                            },
                                        ],
                                    },
                                    MenuEntry::Separator,
                                    MenuEntry::Sub {
                                        trigger_label: "Zoom".into(),
                                        items: vec![
                                            MenuEntry::Item(MenuItem {
                                                label: "Zoom In".into(),
                                                action: "zoom-in".into(),
                                                disabled: false,
                                                destructive: false,
                                                shortcut: Some("\u{2318}+".into()),
                                            }),
                                            MenuEntry::Item(MenuItem {
                                                label: "Zoom Out".into(),
                                                action: "zoom-out".into(),
                                                disabled: false,
                                                destructive: false,
                                                shortcut: Some("\u{2318}-".into()),
                                            }),
                                            MenuEntry::Separator,
                                            MenuEntry::Item(MenuItem {
                                                label: "Actual Size".into(),
                                                action: "zoom-reset".into(),
                                                disabled: false,
                                                destructive: false,
                                                shortcut: Some("\u{2318}0".into()),
                                            }),
                                        ],
                                    },
                                    MenuEntry::Separator,
                                    MenuEntry::Item(MenuItem {
                                        label: "Fullscreen".into(),
                                        action: "fullscreen".into(),
                                        disabled: false,
                                        destructive: false,
                                        shortcut: Some("F11".into()),
                                    }),
                                ],
                            },
                        ],
                    }))
                }
            }
        }
    }
}