maud_ui/primitives/
command.rs1use maud::{html, Markup};
3
4#[derive(Clone, Debug)]
6pub struct CommandItem {
7 pub label: String,
9 pub shortcut: Option<String>,
11 pub group: Option<String>,
13 pub disabled: bool,
15}
16
17#[derive(Clone, Debug)]
19pub struct Props {
20 pub id: String,
22 pub items: Vec<CommandItem>,
24 pub placeholder: String,
26}
27
28impl Default for Props {
29 fn default() -> Self {
30 Self {
31 id: "command".to_string(),
32 items: vec![],
33 placeholder: "Type a command or search\u{2026}".to_string(),
34 }
35 }
36}
37
38pub fn trigger(target_id: &str, label: &str) -> Markup {
40 html! {
41 button type="button"
42 class="mui-btn mui-btn--default mui-btn--md"
43 data-mui="command-trigger"
44 data-target=(target_id)
45 {
46 (label)
47 }
48 }
49}
50
51pub fn render(props: Props) -> Markup {
53 let mut groups: Vec<String> = Vec::new();
55 for item in &props.items {
56 let group_key = item.group.clone().unwrap_or_default();
57 let mut found = false;
58 for g in &groups {
59 if *g == group_key {
60 found = true;
61 break;
62 }
63 }
64 if !found {
65 groups.push(group_key);
66 }
67 }
68
69 html! {
70 dialog class="mui-command"
71 id=(props.id)
72 data-mui="command"
73 aria-label="Command palette"
74 {
75 div class="mui-command__search-wrap" {
76 span class="mui-command__search-icon" aria-hidden="true" { "\u{2315}" }
77 input type="text" class="mui-command__search"
78 placeholder=(props.placeholder)
79 autocomplete="off"
80 aria-label="Search commands";
81 }
82 div class="mui-command__list" role="listbox" {
83 @for group_name in &groups {
84 div class="mui-command__group" {
85 @if !group_name.is_empty() {
86 div class="mui-command__group-label" { (group_name) }
87 }
88 @for item in &props.items {
89 @let item_group = item.group.clone().unwrap_or_default();
90 @if item_group == *group_name {
91 @if item.disabled {
92 div class="mui-command__item mui-command__item--disabled"
93 role="option"
94 tabindex="-1"
95 aria-disabled="true"
96 data-label=(item.label)
97 {
98 span class="mui-command__item-label" { (item.label) }
99 @if let Some(shortcut) = &item.shortcut {
100 kbd class="mui-kbd" { (shortcut) }
101 }
102 }
103 } @else {
104 div class="mui-command__item"
105 role="option"
106 tabindex="-1"
107 data-label=(item.label)
108 {
109 span class="mui-command__item-label" { (item.label) }
110 @if let Some(shortcut) = &item.shortcut {
111 kbd class="mui-kbd" { (shortcut) }
112 }
113 }
114 }
115 }
116 }
117 }
118 }
119 }
120 div class="mui-command__empty" hidden { "No results found." }
121 }
122 }
123}
124
125pub fn showcase() -> Markup {
127 let items = vec![
128 CommandItem {
129 label: "Calendar".to_string(),
130 shortcut: None,
131 group: Some("Suggestions".to_string()),
132 disabled: false,
133 },
134 CommandItem {
135 label: "Search".to_string(),
136 shortcut: None,
137 group: Some("Suggestions".to_string()),
138 disabled: false,
139 },
140 CommandItem {
141 label: "Settings".to_string(),
142 shortcut: None,
143 group: Some("Suggestions".to_string()),
144 disabled: false,
145 },
146 CommandItem {
147 label: "New File".to_string(),
148 shortcut: Some("\u{2318}N".to_string()),
149 group: Some("Actions".to_string()),
150 disabled: false,
151 },
152 CommandItem {
153 label: "Save".to_string(),
154 shortcut: Some("\u{2318}S".to_string()),
155 group: Some("Actions".to_string()),
156 disabled: false,
157 },
158 CommandItem {
159 label: "Export".to_string(),
160 shortcut: None,
161 group: Some("Actions".to_string()),
162 disabled: false,
163 },
164 ];
165
166 html! {
167 div.mui-showcase__grid {
168 div {
169 p.mui-showcase__caption { "Command palette trigger" }
170 div.mui-showcase__row {
171 (trigger("demo-command", "Open command palette"))
172 span.mui-text-muted style="font-size: 0.875rem;" {
173 "Press "
174 kbd.mui-kbd { "\u{2318}K" }
175 }
176 }
177 }
178 div {
179 (render(Props {
180 id: "demo-command".to_string(),
181 items,
182 placeholder: "Type a command or search\u{2026}".to_string(),
183 }))
184 }
185 }
186 }
187}