dioxus_bootstrap_css/
dropdown.rs1use dioxus::prelude::*;
2
3#[derive(Clone, PartialEq, Props)]
56pub struct DropdownProps {
57 pub open: Signal<bool>,
59 pub toggle: Element,
61 pub menu: Element,
63 #[props(default)]
65 pub class: String,
66 #[props(default)]
68 pub toggle_class: String,
69 #[props(default)]
71 pub direction: DropDirection,
72 #[props(default)]
74 pub align_end: bool,
75 #[props(default)]
77 pub split: bool,
78 #[props(default)]
80 pub color: Option<crate::types::Color>,
81 #[props(extends = GlobalAttributes)]
83 attributes: Vec<Attribute>,
84}
85
86#[derive(Clone, Copy, Debug, Default, PartialEq)]
88pub enum DropDirection {
89 #[default]
90 Down,
91 Up,
92 Start,
93 End,
94}
95
96#[component]
97pub fn Dropdown(props: DropdownProps) -> Element {
98 let is_open = *props.open.read();
99 let mut open_signal = props.open;
100
101 let dir_class = match props.direction {
102 DropDirection::Down => "dropdown",
103 DropDirection::Up => "dropup",
104 DropDirection::Start => "dropstart",
105 DropDirection::End => "dropend",
106 };
107
108 let container_class = if props.class.is_empty() {
109 dir_class.to_string()
110 } else {
111 format!("{dir_class} {}", props.class)
112 };
113
114 let color_name = match &props.color {
115 Some(c) => format!("{c}"),
116 None => "secondary".to_string(),
117 };
118
119 let toggle_class = if props.split {
120 format!("btn btn-{color_name} dropdown-toggle dropdown-toggle-split")
121 } else if props.toggle_class.is_empty() {
122 format!("btn btn-{color_name} dropdown-toggle")
123 } else {
124 format!("btn dropdown-toggle {}", props.toggle_class)
125 };
126
127 let menu_class = if is_open {
128 if props.align_end {
129 "dropdown-menu dropdown-menu-end show"
130 } else {
131 "dropdown-menu show"
132 }
133 } else if props.align_end {
134 "dropdown-menu dropdown-menu-end"
135 } else {
136 "dropdown-menu"
137 };
138
139 rsx! {
140 if is_open {
142 div {
143 style: "position: fixed; inset: 0; z-index: 990;",
144 onclick: move |_| open_signal.set(false),
145 }
146 }
147 div { class: "{container_class}",
148 style: if is_open { "position: relative; z-index: 991;" } else { "" },
149 ..props.attributes,
150 if props.split {
152 button {
153 class: "btn btn-{color_name}",
154 r#type: "button",
155 {props.toggle.clone()}
156 }
157 }
158 button {
159 class: "{toggle_class}",
160 r#type: "button",
161 "aria-expanded": if is_open { "true" } else { "false" },
162 onclick: move |evt| {
163 evt.stop_propagation();
164 open_signal.set(!is_open);
165 },
166 if !props.split {
167 {props.toggle}
168 }
169 if props.split {
170 span { class: "visually-hidden", "Toggle Dropdown" }
171 }
172 }
173 ul { class: "{menu_class}",
174 style: if props.align_end { "right: 0; left: auto;" } else { "" },
178 onclick: move |_| open_signal.set(false),
180 {props.menu}
181 }
182 }
183 }
184}
185
186#[derive(Clone, PartialEq, Props)]
191pub struct DropdownMenuProps {
192 #[props(default)]
194 pub show: bool,
195 #[props(default)]
197 pub align_end: bool,
198 #[props(default)]
200 pub class: String,
201 #[props(extends = GlobalAttributes)]
203 attributes: Vec<Attribute>,
204 pub children: Element,
206}
207
208#[component]
209pub fn DropdownMenu(props: DropdownMenuProps) -> Element {
210 let mut classes = vec!["dropdown-menu".to_string()];
211 if props.align_end {
212 classes.push("dropdown-menu-end".to_string());
213 }
214 if props.show {
215 classes.push("show".to_string());
216 }
217 if !props.class.is_empty() {
218 classes.push(props.class.clone());
219 }
220 let full_class = classes.join(" ");
221 let align_style = if props.align_end {
224 "right: 0; left: auto;"
225 } else {
226 ""
227 };
228 rsx! {
229 ul { class: "{full_class}", style: "{align_style}", ..props.attributes, {props.children} }
230 }
231}
232
233#[derive(Clone, PartialEq, Props)]
235pub struct DropdownItemProps {
236 #[props(default)]
238 pub active: bool,
239 #[props(default)]
241 pub disabled: bool,
242 #[props(default)]
244 pub onclick: Option<EventHandler<MouseEvent>>,
245 #[props(default)]
247 pub class: String,
248 #[props(extends = GlobalAttributes)]
250 attributes: Vec<Attribute>,
251 pub children: Element,
253}
254
255#[component]
256pub fn DropdownItem(props: DropdownItemProps) -> Element {
257 let mut classes = vec!["dropdown-item".to_string()];
258 if props.active {
259 classes.push("active".to_string());
260 }
261 if props.disabled {
262 classes.push("disabled".to_string());
263 }
264 if !props.class.is_empty() {
265 classes.push(props.class.clone());
266 }
267 let full_class = classes.join(" ");
268
269 rsx! {
270 li {
271 button {
272 class: "{full_class}",
273 r#type: "button",
274 disabled: props.disabled,
275 onclick: move |evt| {
276 if let Some(handler) = &props.onclick {
277 handler.call(evt);
278 }
279 },
280 ..props.attributes,
281 {props.children}
282 }
283 }
284 }
285}
286
287#[component]
289pub fn DropdownDivider() -> Element {
290 rsx! {
291 li { hr { class: "dropdown-divider" } }
292 }
293}
294
295#[derive(Clone, PartialEq, Props)]
297pub struct DropdownHeaderProps {
298 #[props(extends = GlobalAttributes)]
300 attributes: Vec<Attribute>,
301 pub children: Element,
302}
303
304#[component]
305pub fn DropdownHeader(props: DropdownHeaderProps) -> Element {
306 rsx! {
307 li { h6 { class: "dropdown-header", ..props.attributes, {props.children} } }
308 }
309}