dioxus_bootstrap_css/
dropdown.rs1use dioxus::prelude::*;
2
3#[derive(Clone, PartialEq, Props)]
52pub struct DropdownProps {
53 pub open: Signal<bool>,
55 pub toggle: Element,
57 pub menu: Element,
59 #[props(default)]
61 pub class: String,
62 #[props(default)]
64 pub toggle_class: String,
65 #[props(default)]
67 pub direction: DropDirection,
68 #[props(default)]
70 pub align_end: bool,
71 #[props(default)]
73 pub split: bool,
74 #[props(default)]
76 pub color: Option<crate::types::Color>,
77}
78
79#[derive(Clone, Copy, Debug, Default, PartialEq)]
81pub enum DropDirection {
82 #[default]
83 Down,
84 Up,
85 Start,
86 End,
87}
88
89#[component]
90pub fn Dropdown(props: DropdownProps) -> Element {
91 let is_open = *props.open.read();
92 let mut open_signal = props.open;
93
94 let dir_class = match props.direction {
95 DropDirection::Down => "dropdown",
96 DropDirection::Up => "dropup",
97 DropDirection::Start => "dropstart",
98 DropDirection::End => "dropend",
99 };
100
101 let container_class = if props.class.is_empty() {
102 dir_class.to_string()
103 } else {
104 format!("{dir_class} {}", props.class)
105 };
106
107 let color_name = match &props.color {
108 Some(c) => format!("{c}"),
109 None => "secondary".to_string(),
110 };
111
112 let toggle_class = if props.split {
113 format!("btn btn-{color_name} dropdown-toggle dropdown-toggle-split")
114 } else if props.toggle_class.is_empty() {
115 format!("btn btn-{color_name} dropdown-toggle")
116 } else {
117 format!("btn dropdown-toggle {}", props.toggle_class)
118 };
119
120 let menu_class = if is_open {
121 if props.align_end {
122 "dropdown-menu dropdown-menu-end show"
123 } else {
124 "dropdown-menu show"
125 }
126 } else if props.align_end {
127 "dropdown-menu dropdown-menu-end"
128 } else {
129 "dropdown-menu"
130 };
131
132 rsx! {
133 if is_open {
135 div {
136 style: "position: fixed; inset: 0; z-index: 990;",
137 onclick: move |_| open_signal.set(false),
138 }
139 }
140 div { class: "{container_class}",
141 style: if is_open { "position: relative; z-index: 991;" } else { "" },
142 if props.split {
144 button {
145 class: "btn btn-{color_name}",
146 r#type: "button",
147 {props.toggle.clone()}
148 }
149 }
150 button {
151 class: "{toggle_class}",
152 r#type: "button",
153 "aria-expanded": if is_open { "true" } else { "false" },
154 onclick: move |evt| {
155 evt.stop_propagation();
156 open_signal.set(!is_open);
157 },
158 if !props.split {
159 {props.toggle}
160 }
161 if props.split {
162 span { class: "visually-hidden", "Toggle Dropdown" }
163 }
164 }
165 ul { class: "{menu_class}",
166 onclick: move |_| open_signal.set(false),
168 {props.menu}
169 }
170 }
171 }
172}
173
174#[derive(Clone, PartialEq, Props)]
176pub struct DropdownItemProps {
177 #[props(default)]
179 pub active: bool,
180 #[props(default)]
182 pub disabled: bool,
183 #[props(default)]
185 pub onclick: Option<EventHandler<MouseEvent>>,
186 #[props(default)]
188 pub class: String,
189 pub children: Element,
191}
192
193#[component]
194pub fn DropdownItem(props: DropdownItemProps) -> Element {
195 let mut classes = vec!["dropdown-item".to_string()];
196 if props.active {
197 classes.push("active".to_string());
198 }
199 if props.disabled {
200 classes.push("disabled".to_string());
201 }
202 if !props.class.is_empty() {
203 classes.push(props.class.clone());
204 }
205 let full_class = classes.join(" ");
206
207 rsx! {
208 li {
209 button {
210 class: "{full_class}",
211 r#type: "button",
212 disabled: props.disabled,
213 onclick: move |evt| {
214 if let Some(handler) = &props.onclick {
215 handler.call(evt);
216 }
217 },
218 {props.children}
219 }
220 }
221 }
222}
223
224#[component]
226pub fn DropdownDivider() -> Element {
227 rsx! {
228 li { hr { class: "dropdown-divider" } }
229 }
230}
231
232#[derive(Clone, PartialEq, Props)]
234pub struct DropdownHeaderProps {
235 pub children: Element,
236}
237
238#[component]
239pub fn DropdownHeader(props: DropdownHeaderProps) -> Element {
240 rsx! {
241 li { h6 { class: "dropdown-header", {props.children} } }
242 }
243}