Skip to main content

dioxus_element_plug/components/
menu.rs

1use dioxus::prelude::*;
2
3/// Menu mode
4#[derive(Clone, PartialEq)]
5pub enum MenuMode {
6    Horizontal,
7    Vertical,
8}
9
10impl MenuMode {
11    pub fn as_str(&self) -> &'static str {
12        match self {
13            MenuMode::Horizontal => "horizontal",
14            MenuMode::Vertical => "vertical",
15        }
16    }
17}
18
19/// Menu props
20#[derive(Props, Clone, PartialEq)]
21pub struct MenuProps {
22    #[props(default)]
23    pub children: Element,
24
25    #[props(default = MenuMode::Vertical)]
26    pub mode: MenuMode,
27
28    /// Index of the active menu item
29    #[props(default)]
30    pub default_active: Option<String>,
31
32    /// Whether the menu is collapsed (vertical mode only)
33    #[props(default = false)]
34    pub collapse: bool,
35
36    /// Background color
37    #[props(default)]
38    pub background_color: Option<String>,
39
40    /// Text color
41    #[props(default)]
42    pub text_color: Option<String>,
43
44    /// Active text color
45    #[props(default)]
46    pub active_text_color: Option<String>,
47
48    /// Whether to use unique opened mode
49    #[props(default = false)]
50    pub unique_opened: bool,
51
52    /// Select event handler
53    #[props(default)]
54    pub on_select: Option<EventHandler<String>>,
55
56    #[props(default)]
57    pub class: Option<String>,
58
59    #[props(default)]
60    pub style: Option<String>,
61}
62
63/// Menu component for navigation
64#[component]
65pub fn Menu(props: MenuProps) -> Element {
66    let mut class_names = vec!["el-menu".to_string()];
67    class_names.push(format!("el-menu--{}", props.mode.as_str()));
68    if props.collapse { class_names.push("el-menu--collapse".to_string()); }
69    if let Some(ref c) = props.class { class_names.push(c.clone()); }
70
71    let mut style = props.style.clone().unwrap_or_default();
72    if let Some(ref bg) = props.background_color { style = format!("background-color: {}; {}", bg, style); }
73
74    rsx! {
75        ul {
76            class: "{class_names.join(\" \")}",
77            style: "{style}",
78            role: "menubar",
79            {props.children}
80        }
81    }
82}
83
84/// MenuItem props
85#[derive(Props, Clone, PartialEq)]
86pub struct MenuItemProps {
87    #[props(default)]
88    pub children: Option<Element>,
89
90    /// Unique index for this menu item
91    pub index: String,
92
93    /// Route to navigate to
94    #[props(default)]
95    pub route: Option<String>,
96
97    /// Whether the item is disabled
98    #[props(default = false)]
99    pub disabled: bool,
100
101    /// Select event handler
102    #[props(default)]
103    pub on_click: Option<EventHandler<String>>,
104
105    #[props(default)]
106    pub class: Option<String>,
107}
108
109/// MenuItem component for individual menu items
110#[component]
111pub fn MenuItem(props: MenuItemProps) -> Element {
112    let mut class_names = vec!["el-menu-item".to_string()];
113    if props.disabled { class_names.push("is-disabled".to_string()); }
114    if let Some(ref c) = props.class { class_names.push(c.clone()); }
115
116    let index_clone = props.index.clone();
117
118    rsx! {
119        li {
120            class: "{class_names.join(\" \")}",
121            role: "menuitem",
122            onclick: move |_| {
123                if !props.disabled {
124                    if let Some(handler) = props.on_click {
125                        handler.call(index_clone.clone());
126                    }
127                }
128            },
129            {props.children}
130        }
131    }
132}
133
134/// MenuItemGroup props
135#[derive(Props, Clone, PartialEq)]
136pub struct MenuItemGroupProps {
137    #[props(default)]
138    pub children: Element,
139
140    #[props(default)]
141    pub title: Option<String>,
142
143    #[props(default)]
144    pub class: Option<String>,
145}
146
147/// MenuItemGroup component for grouping menu items
148#[component]
149pub fn MenuItemGroup(props: MenuItemGroupProps) -> Element {
150    let mut class_names = vec!["el-menu-item-group".to_string()];
151    if let Some(ref c) = props.class { class_names.push(c.clone()); }
152
153    rsx! {
154        li {
155            class: "{class_names.join(\" \")}",
156            role: "menuitemgroup",
157            if let Some(ref title) = props.title {
158                div { class: "el-menu-item-group__title", "{title}" }
159            }
160            ul {
161                class: "el-menu-item-group__content",
162                {props.children}
163            }
164        }
165    }
166}
167
168/// SubMenu props
169#[derive(Props, Clone, PartialEq)]
170pub struct SubMenuProps {
171    #[props(default)]
172    pub children: Element,
173
174    pub index: String,
175
176    #[props(default)]
177    pub title: Option<String>,
178
179    #[props(default)]
180    pub icon: Option<String>,
181
182    #[props(default = false)]
183    pub disabled: bool,
184
185    #[props(default)]
186    pub class: Option<String>,
187}
188
189/// SubMenu component for nested menu items
190#[component]
191pub fn SubMenu(props: SubMenuProps) -> Element {
192    let mut class_names = vec!["el-sub-menu".to_string()];
193    if props.disabled { class_names.push("is-disabled".to_string()); }
194    if let Some(ref c) = props.class { class_names.push(c.clone()); }
195
196    rsx! {
197        li {
198            class: "{class_names.join(\" \")}",
199            role: "menuitem",
200            div {
201                class: "el-sub-menu__title",
202                if let Some(ref icon) = props.icon {
203                    i { class: "{icon}" }
204                }
205                if let Some(ref title) = props.title {
206                    span { "{title}" }
207                }
208                i { class: "el-sub-menu__icon-arrow el-icon-arrow-down" }
209            }
210            ul {
211                class: "el-menu el-menu--inline",
212                role: "menu",
213                {props.children}
214            }
215        }
216    }
217}