use dioxus::prelude::*;
#[derive(Clone, PartialEq, Props)]
pub struct DropdownProps {
pub open: Signal<bool>,
pub toggle: Element,
pub menu: Element,
#[props(default)]
pub class: String,
#[props(default)]
pub toggle_class: String,
#[props(default)]
pub direction: DropDirection,
#[props(default)]
pub align_end: bool,
#[props(default)]
pub split: bool,
#[props(default)]
pub color: Option<crate::types::Color>,
#[props(extends = GlobalAttributes)]
attributes: Vec<Attribute>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum DropDirection {
#[default]
Down,
Up,
Start,
End,
}
#[component]
pub fn Dropdown(props: DropdownProps) -> Element {
let is_open = *props.open.read();
let mut open_signal = props.open;
let dir_class = match props.direction {
DropDirection::Down => "dropdown",
DropDirection::Up => "dropup",
DropDirection::Start => "dropstart",
DropDirection::End => "dropend",
};
let container_class = if props.class.is_empty() {
dir_class.to_string()
} else {
format!("{dir_class} {}", props.class)
};
let color_name = match &props.color {
Some(c) => format!("{c}"),
None => "secondary".to_string(),
};
let toggle_class = if props.split {
format!("btn btn-{color_name} dropdown-toggle dropdown-toggle-split")
} else if props.toggle_class.is_empty() {
format!("btn btn-{color_name} dropdown-toggle")
} else {
format!("btn dropdown-toggle {}", props.toggle_class)
};
let menu_class = if is_open {
if props.align_end {
"dropdown-menu dropdown-menu-end show"
} else {
"dropdown-menu show"
}
} else if props.align_end {
"dropdown-menu dropdown-menu-end"
} else {
"dropdown-menu"
};
rsx! {
if is_open {
div {
style: "position: fixed; inset: 0; z-index: 990;",
onclick: move |_| open_signal.set(false),
}
}
div { class: "{container_class}",
style: if is_open { "position: relative; z-index: 991;" } else { "" },
..props.attributes,
if props.split {
button {
class: "btn btn-{color_name}",
r#type: "button",
{props.toggle.clone()}
}
}
button {
class: "{toggle_class}",
r#type: "button",
"aria-expanded": if is_open { "true" } else { "false" },
onclick: move |evt| {
evt.stop_propagation();
open_signal.set(!is_open);
},
if !props.split {
{props.toggle}
}
if props.split {
span { class: "visually-hidden", "Toggle Dropdown" }
}
}
ul { class: "{menu_class}",
onclick: move |_| open_signal.set(false),
{props.menu}
}
}
}
}
#[derive(Clone, PartialEq, Props)]
pub struct DropdownItemProps {
#[props(default)]
pub active: bool,
#[props(default)]
pub disabled: bool,
#[props(default)]
pub onclick: Option<EventHandler<MouseEvent>>,
#[props(default)]
pub class: String,
#[props(extends = GlobalAttributes)]
attributes: Vec<Attribute>,
pub children: Element,
}
#[component]
pub fn DropdownItem(props: DropdownItemProps) -> Element {
let mut classes = vec!["dropdown-item".to_string()];
if props.active {
classes.push("active".to_string());
}
if props.disabled {
classes.push("disabled".to_string());
}
if !props.class.is_empty() {
classes.push(props.class.clone());
}
let full_class = classes.join(" ");
rsx! {
li {
button {
class: "{full_class}",
r#type: "button",
disabled: props.disabled,
onclick: move |evt| {
if let Some(handler) = &props.onclick {
handler.call(evt);
}
},
..props.attributes,
{props.children}
}
}
}
}
#[component]
pub fn DropdownDivider() -> Element {
rsx! {
li { hr { class: "dropdown-divider" } }
}
}
#[derive(Clone, PartialEq, Props)]
pub struct DropdownHeaderProps {
#[props(extends = GlobalAttributes)]
attributes: Vec<Attribute>,
pub children: Element,
}
#[component]
pub fn DropdownHeader(props: DropdownHeaderProps) -> Element {
rsx! {
li { h6 { class: "dropdown-header", ..props.attributes, {props.children} } }
}
}