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 #[props(extends = GlobalAttributes)]
79 attributes: Vec<Attribute>,
80}
81
82#[derive(Clone, Copy, Debug, Default, PartialEq)]
84pub enum DropDirection {
85 #[default]
86 Down,
87 Up,
88 Start,
89 End,
90}
91
92#[component]
93pub fn Dropdown(props: DropdownProps) -> Element {
94 let is_open = *props.open.read();
95 let mut open_signal = props.open;
96
97 let dir_class = match props.direction {
98 DropDirection::Down => "dropdown",
99 DropDirection::Up => "dropup",
100 DropDirection::Start => "dropstart",
101 DropDirection::End => "dropend",
102 };
103
104 let container_class = if props.class.is_empty() {
105 dir_class.to_string()
106 } else {
107 format!("{dir_class} {}", props.class)
108 };
109
110 let color_name = match &props.color {
111 Some(c) => format!("{c}"),
112 None => "secondary".to_string(),
113 };
114
115 let toggle_class = if props.split {
116 format!("btn btn-{color_name} dropdown-toggle dropdown-toggle-split")
117 } else if props.toggle_class.is_empty() {
118 format!("btn btn-{color_name} dropdown-toggle")
119 } else {
120 format!("btn dropdown-toggle {}", props.toggle_class)
121 };
122
123 let menu_class = if is_open {
124 if props.align_end {
125 "dropdown-menu dropdown-menu-end show"
126 } else {
127 "dropdown-menu show"
128 }
129 } else if props.align_end {
130 "dropdown-menu dropdown-menu-end"
131 } else {
132 "dropdown-menu"
133 };
134
135 rsx! {
136 if is_open {
138 div {
139 style: "position: fixed; inset: 0; z-index: 990;",
140 onclick: move |_| open_signal.set(false),
141 }
142 }
143 div { class: "{container_class}",
144 style: if is_open { "position: relative; z-index: 991;" } else { "" },
145 ..props.attributes,
146 if props.split {
148 button {
149 class: "btn btn-{color_name}",
150 r#type: "button",
151 {props.toggle.clone()}
152 }
153 }
154 button {
155 class: "{toggle_class}",
156 r#type: "button",
157 "aria-expanded": if is_open { "true" } else { "false" },
158 onclick: move |evt| {
159 evt.stop_propagation();
160 open_signal.set(!is_open);
161 },
162 if !props.split {
163 {props.toggle}
164 }
165 if props.split {
166 span { class: "visually-hidden", "Toggle Dropdown" }
167 }
168 }
169 ul { class: "{menu_class}",
170 onclick: move |_| open_signal.set(false),
172 {props.menu}
173 }
174 }
175 }
176}
177
178#[derive(Clone, PartialEq, Props)]
180pub struct DropdownItemProps {
181 #[props(default)]
183 pub active: bool,
184 #[props(default)]
186 pub disabled: bool,
187 #[props(default)]
189 pub onclick: Option<EventHandler<MouseEvent>>,
190 #[props(default)]
192 pub class: String,
193 #[props(extends = GlobalAttributes)]
195 attributes: Vec<Attribute>,
196 pub children: Element,
198}
199
200#[component]
201pub fn DropdownItem(props: DropdownItemProps) -> Element {
202 let mut classes = vec!["dropdown-item".to_string()];
203 if props.active {
204 classes.push("active".to_string());
205 }
206 if props.disabled {
207 classes.push("disabled".to_string());
208 }
209 if !props.class.is_empty() {
210 classes.push(props.class.clone());
211 }
212 let full_class = classes.join(" ");
213
214 rsx! {
215 li {
216 button {
217 class: "{full_class}",
218 r#type: "button",
219 disabled: props.disabled,
220 onclick: move |evt| {
221 if let Some(handler) = &props.onclick {
222 handler.call(evt);
223 }
224 },
225 ..props.attributes,
226 {props.children}
227 }
228 }
229 }
230}
231
232#[component]
234pub fn DropdownDivider() -> Element {
235 rsx! {
236 li { hr { class: "dropdown-divider" } }
237 }
238}
239
240#[derive(Clone, PartialEq, Props)]
242pub struct DropdownHeaderProps {
243 #[props(extends = GlobalAttributes)]
245 attributes: Vec<Attribute>,
246 pub children: Element,
247}
248
249#[component]
250pub fn DropdownHeader(props: DropdownHeaderProps) -> Element {
251 rsx! {
252 li { h6 { class: "dropdown-header", ..props.attributes, {props.children} } }
253 }
254}