maud_ui/primitives/
menu.rs1use maud::{html, Markup};
4
5#[derive(Debug, Clone)]
7pub struct MenuItem {
8 pub label: String,
9 pub action: String,
10 pub disabled: bool,
11 pub destructive: bool,
12 pub shortcut: Option<String>,
14}
15
16#[derive(Debug, Clone)]
18pub enum MenuEntry {
19 Item(MenuItem),
20 Separator,
21 Label(String),
22}
23
24#[derive(Debug, Clone)]
26pub struct Props {
27 pub trigger_label: String,
29 pub id: String,
31 pub items: Vec<MenuEntry>,
33}
34
35pub fn render(props: Props) -> Markup {
37 html! {
38 div.mui-menu data-mui="menu" {
39 button.mui-menu__trigger.mui-btn.mui-btn--default.mui-btn--md
40 type="button"
41 aria-expanded="false"
42 aria-haspopup="menu"
43 aria-controls=(format!("{}-items", props.id))
44 {
45 (props.trigger_label)
46 span.mui-menu__chevron aria-hidden="true" { "▾" }
47 }
48 div.mui-menu__content id=(format!("{}-items", props.id)) role="menu" hidden {
49 @for entry in &props.items {
50 (render_entry(entry))
51 }
52 }
53 }
54 }
55}
56
57pub fn render_entry(entry: &MenuEntry) -> Markup {
59 html! {
60 @match entry {
61 MenuEntry::Item(item) => {
62 @let cls = if item.destructive { "mui-menu__item mui-menu__item--danger" } else { "mui-menu__item" };
63 button
64 type="button"
65 role="menuitem"
66 class=(cls)
67 data-action=(item.action)
68 tabindex="-1"
69 disabled[item.disabled]
70 {
71 (item.label.clone())
72 @if let Some(shortcut) = &item.shortcut {
73 span.mui-menu__shortcut { (shortcut) }
74 }
75 }
76 }
77 MenuEntry::Separator => {
78 div.mui-menu__separator role="separator" {}
79 }
80 MenuEntry::Label(text) => {
81 div.mui-menu__label { (text) }
82 }
83 }
84 }
85}
86
87pub fn showcase() -> Markup {
89 html! {
90 div.mui-showcase__grid {
91 div {
92 p.mui-showcase__caption { "File menu" }
93 div.mui-showcase__row {
94 (render(Props {
95 trigger_label: "File".into(),
96 id: "demo-menu-file".into(),
97 items: vec![
98 MenuEntry::Item(MenuItem {
99 label: "New".into(),
100 action: "new".into(),
101 disabled: false,
102 destructive: false,
103 shortcut: Some("\u{2318}N".into()),
104 }),
105 MenuEntry::Item(MenuItem {
106 label: "Open\u{2026}".into(),
107 action: "open".into(),
108 disabled: false,
109 destructive: false,
110 shortcut: Some("\u{2318}O".into()),
111 }),
112 MenuEntry::Separator,
113 MenuEntry::Item(MenuItem {
114 label: "Save".into(),
115 action: "save".into(),
116 disabled: false,
117 destructive: false,
118 shortcut: Some("\u{2318}S".into()),
119 }),
120 MenuEntry::Item(MenuItem {
121 label: "Save As\u{2026}".into(),
122 action: "save-as".into(),
123 disabled: false,
124 destructive: false,
125 shortcut: Some("\u{21e7}\u{2318}S".into()),
126 }),
127 MenuEntry::Separator,
128 MenuEntry::Item(MenuItem {
129 label: "Print\u{2026}".into(),
130 action: "print".into(),
131 disabled: false,
132 destructive: false,
133 shortcut: Some("\u{2318}P".into()),
134 }),
135 MenuEntry::Separator,
136 MenuEntry::Item(MenuItem {
137 label: "Exit".into(),
138 action: "exit".into(),
139 disabled: false,
140 destructive: false,
141 shortcut: None,
142 }),
143 ],
144 }))
145 }
146 }
147 div {
148 p.mui-showcase__caption { "User menu" }
149 div.mui-showcase__row {
150 (render(Props {
151 trigger_label: "My Account".into(),
152 id: "demo-menu-user".into(),
153 items: vec![
154 MenuEntry::Label("Account".into()),
155 MenuEntry::Item(MenuItem {
156 label: "Profile".into(),
157 action: "profile".into(),
158 disabled: false,
159 destructive: false,
160 shortcut: None,
161 }),
162 MenuEntry::Item(MenuItem {
163 label: "Settings".into(),
164 action: "settings".into(),
165 disabled: false,
166 destructive: false,
167 shortcut: None,
168 }),
169 MenuEntry::Separator,
170 MenuEntry::Item(MenuItem {
171 label: "Sign out".into(),
172 action: "sign-out".into(),
173 disabled: false,
174 destructive: true,
175 shortcut: None,
176 }),
177 ],
178 }))
179 }
180 }
181 }
182 }
183}