Skip to main content

dioxus_bulma/components/
dropdown.rs

1use crate::utils::build_class;
2use dioxus::prelude::*;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum DropdownTrigger {
6    Hover,
7    Click,
8}
9
10#[derive(Props, Clone, PartialEq)]
11pub struct DropdownProps {
12    #[props(default)]
13    pub active: Option<bool>,
14    #[props(default)]
15    pub hoverable: Option<bool>,
16    #[props(default)]
17    pub right: Option<bool>,
18    #[props(default)]
19    pub up: Option<bool>,
20    #[props(default)]
21    pub id: Option<String>,
22    #[props(default)]
23    pub class: Option<String>,
24    #[props(default)]
25    pub style: Option<String>,
26    pub children: Element,
27}
28
29#[component]
30pub fn Dropdown(props: DropdownProps) -> Element {
31    let active = props.active.unwrap_or(false);
32    let hoverable = props.hoverable.unwrap_or(false);
33    let right = props.right.unwrap_or(false);
34    let up = props.up.unwrap_or(false);
35    
36    let base_classes = vec!["dropdown"];
37    let optional_classes = vec![
38        if active { Some("is-active".to_string()) } else { None },
39        if hoverable { Some("is-hoverable".to_string()) } else { None },
40        if right { Some("is-right".to_string()) } else { None },
41        if up { Some("is-up".to_string()) } else { None },
42        props.class.clone(),
43    ];
44    
45    let final_class = build_class(&base_classes, &optional_classes);
46    let dropdown_style = props.style.as_deref().unwrap_or("");
47
48    rsx! {
49        div {
50            class: "{final_class}",
51            style: "{dropdown_style}",
52            id: props.id.clone(),
53            {props.children}
54        }
55    }
56}
57
58#[derive(Props, Clone, PartialEq)]
59pub struct DropdownTriggerProps {
60    #[props(default)]
61    pub onclick: Option<EventHandler<MouseEvent>>,
62    #[props(default)]
63    pub id: Option<String>,
64    #[props(default)]
65    pub class: Option<String>,
66    #[props(default)]
67    pub style: Option<String>,
68    pub children: Element,
69}
70
71#[component]
72pub fn DropdownTrigger(props: DropdownTriggerProps) -> Element {
73    let base_classes = vec!["dropdown-trigger"];
74    let optional_classes = vec![props.class.clone()];
75    let final_class = build_class(&base_classes, &optional_classes);
76    let trigger_style = props.style.as_deref().unwrap_or("");
77
78    rsx! {
79        div {
80            class: "{final_class}",
81            style: "{trigger_style}",
82            id: props.id.clone(),
83            onclick: move |evt| {
84                if let Some(handler) = &props.onclick {
85                    handler.call(evt);
86                }
87            },
88            {props.children}
89        }
90    }
91}
92
93#[derive(Props, Clone, PartialEq)]
94pub struct DropdownMenuProps {
95    #[props(default)]
96    pub id: Option<String>,
97    #[props(default)]
98    pub class: Option<String>,
99    #[props(default)]
100    pub style: Option<String>,
101    pub children: Element,
102}
103
104#[component]
105pub fn DropdownMenu(props: DropdownMenuProps) -> Element {
106    let base_classes = vec!["dropdown-menu"];
107    let optional_classes = vec![props.class.clone()];
108    let final_class = build_class(&base_classes, &optional_classes);
109    let menu_style = props.style.as_deref().unwrap_or("");
110
111    rsx! {
112        div {
113            class: "{final_class}",
114            style: "{menu_style}",
115            id: props.id.clone(),
116            role: "menu",
117            div {
118                class: "dropdown-content",
119                {props.children}
120            }
121        }
122    }
123}
124
125#[derive(Props, Clone, PartialEq)]
126pub struct DropdownItemProps {
127    #[props(default)]
128    pub active: Option<bool>,
129    #[props(default)]
130    pub href: Option<String>,
131    #[props(default)]
132    pub onclick: Option<EventHandler<MouseEvent>>,
133    /// If present, use router navigation instead of href
134    #[cfg(feature = "router")]
135    #[props(default, into)]
136    pub to: crate::router_helpers::MaybeNav,
137    #[props(default)]
138    pub id: Option<String>,
139    #[props(default)]
140    pub class: Option<String>,
141    #[props(default)]
142    pub style: Option<String>,
143    pub children: Element,
144}
145
146#[component]
147pub fn DropdownItem(props: DropdownItemProps) -> Element {
148    let active = props.active.unwrap_or(false);
149    
150    let base_classes = vec!["dropdown-item"];
151    let optional_classes = vec![
152        if active { Some("is-active".to_string()) } else { None },
153        props.class.clone(),
154    ];
155    
156    let final_class = build_class(&base_classes, &optional_classes);
157    let item_style = props.style.as_deref().unwrap_or("");
158
159    #[cfg(feature = "router")]
160    if let Some(nav_target) = props.to.0 {
161        return rsx! {
162            Link {
163                to: nav_target,
164                class: "{final_class}",
165                style: "{item_style}",
166                id: props.id.clone(),
167                onclick: move |evt| {
168                    if let Some(handler) = &props.onclick {
169                        handler.call(evt);
170                    }
171                },
172                {props.children}
173            }
174        };
175    }
176
177    rsx! {
178        if let Some(href) = props.href {
179            a {
180                class: "{final_class}",
181                style: "{item_style}",
182                id: props.id.clone(),
183                href: "{href}",
184                onclick: move |evt| {
185                    if let Some(handler) = &props.onclick {
186                        handler.call(evt);
187                    }
188                },
189                {props.children}
190            }
191        } else {
192            div {
193                class: "{final_class}",
194                style: "{item_style}",
195                id: props.id.clone(),
196                onclick: move |evt| {
197                    if let Some(handler) = &props.onclick {
198                        handler.call(evt);
199                    }
200                },
201                {props.children}
202            }
203        }
204    }
205}
206
207#[derive(Props, Clone, PartialEq)]
208pub struct DropdownDividerProps {
209    #[props(default)]
210    pub id: Option<String>,
211    #[props(default)]
212    pub class: Option<String>,
213    #[props(default)]
214    pub style: Option<String>,
215}
216
217#[component]
218pub fn DropdownDivider(props: DropdownDividerProps) -> Element {
219    let base_classes = vec!["dropdown-divider"];
220    let optional_classes = vec![props.class.clone()];
221    let final_class = build_class(&base_classes, &optional_classes);
222    let divider_style = props.style.as_deref().unwrap_or("");
223
224    rsx! {
225        hr {
226            class: "{final_class}",
227            style: "{divider_style}"
228        }
229    }
230}