use crate::dioxus_core::IntoAttributeValue;
use dioxus::prelude::*;
use dioxus_core::AttributeValue;
#[derive(Clone, Copy)]
pub struct DropdownManager {
generation: u64,
}
impl DropdownManager {
pub(crate) fn new() -> Self {
Self { generation: 0 }
}
pub fn advance(&mut self) -> u64 {
self.generation += 1;
self.generation
}
pub(crate) fn generation(&self) -> u64 {
self.generation
}
}
#[derive(Clone, PartialEq, Props)]
pub struct DropdownManagerProviderProps {
children: Element,
}
#[component]
pub fn DropdownManagerProvider(props: DropdownManagerProviderProps) -> Element {
use_context_provider(|| Signal::new(DropdownManager::new()));
rsx! { {props.children} }
}
#[derive(Clone, Copy)]
struct DropdownState {
is_active: bool,
manager_generation: u64,
}
impl DropdownState {
fn new() -> Self {
Self {
is_active: false,
manager_generation: 0,
}
}
fn open_with_generation(&mut self, generation: u64) {
self.is_active = true;
self.manager_generation = generation;
}
fn toggle(&mut self) {
self.is_active = !self.is_active;
}
fn close(&mut self) {
self.is_active = false;
}
fn get_is_active(&self) -> bool {
self.is_active
}
}
impl IntoAttributeValue for DropdownState {
fn into_value(self) -> AttributeValue {
match self.is_active {
true => AttributeValue::Text("open".to_string()),
false => AttributeValue::Text("closed".to_string()),
}
}
}
#[derive(Clone, PartialEq, Props)]
pub struct DropdownProps {
#[props(extends = div, extends = GlobalAttributes)]
attributes: Vec<Attribute>,
children: Element,
}
#[component]
pub fn Dropdown(mut props: DropdownProps) -> Element {
let parent_state = try_use_context::<Signal<DropdownState>>();
let mut state = use_context_provider(|| Signal::new(DropdownState::new()));
let manager = try_use_context::<Signal<DropdownManager>>();
use_effect(move || {
if let Some(mgr) = manager {
let global_gen = mgr.read().generation();
if state.peek().get_is_active() && global_gen > state.peek().manager_generation {
state.write().close();
}
}
});
let default_classes = "dropdown";
crate::setup_class_attribute(&mut props.attributes, default_classes);
rsx! {
div { "data-state": state.read().into_value(), ..props.attributes, {props.children} }
if state.read().get_is_active() {
div {
class: "dropdown-backdrop",
onclick: move |_event| {
state.write().close();
if let Some(mut parent) = parent_state {
parent.write().close();
}
if let Some(mut mgr) = manager {
mgr.write().advance();
}
},
}
}
}
}
#[derive(Clone, PartialEq, Props)]
pub struct DropdownToggleProps {
#[props(extends = button, extends = GlobalAttributes)]
attributes: Vec<Attribute>,
children: Element,
}
#[component]
pub fn DropdownToggle(mut props: DropdownToggleProps) -> Element {
let mut state = use_context::<Signal<DropdownState>>();
let manager = try_use_context::<Signal<DropdownManager>>();
let default_classes = "button";
crate::setup_class_attribute(&mut props.attributes, default_classes);
rsx! {
button {
onclick: move |e: MouseEvent| {
e.stop_propagation();
e.prevent_default();
let will_open = !state.read().get_is_active();
if will_open {
if let Some(mut mgr) = manager {
let new_gen = mgr.write().advance();
state.write().open_with_generation(new_gen);
} else {
state.write().toggle();
}
} else {
state.write().toggle();
}
},
..props.attributes,
{props.children}
}
}
}
#[derive(Clone, PartialEq, Props)]
pub struct DropdownContentProps {
#[props(extends = div, extends = GlobalAttributes)]
attributes: Vec<Attribute>,
children: Element,
}
#[component]
pub fn DropdownContent(mut props: DropdownContentProps) -> Element {
let mut state = use_context::<Signal<DropdownState>>();
let default_classes = "dropdown-content";
crate::setup_class_attribute(&mut props.attributes, default_classes);
rsx! {
div {
"data-state": state.read().into_value(),
onclick: move |_| {
state.write().close();
},
..props.attributes,
{props.children}
}
}
}