Skip to main content

shadow_design/
dropdown.rs

1use crate::icon::{Icon, IconKind};
2use dioxus::prelude::*;
3
4#[derive(Clone, Copy, PartialEq, Debug, Default)]
5pub enum DropdownPlacement {
6    #[default]
7    BottomRight,
8    BottomLeft,
9    TopRight,
10    TopLeft,
11}
12
13impl DropdownPlacement {
14    pub fn scss_class(&self) -> &'static str {
15        match self {
16            DropdownPlacement::BottomRight => "sd-dropdown-menu--bottom-right",
17            DropdownPlacement::BottomLeft => "sd-dropdown-menu--bottom-left",
18            DropdownPlacement::TopRight => "sd-dropdown-menu--top-right",
19            DropdownPlacement::TopLeft => "sd-dropdown-menu--top-left",
20        }
21    }
22}
23
24#[component]
25pub fn Dropdown(
26    is_open: Signal<bool>,
27    trigger: Element,
28    #[props(default)] placement: DropdownPlacement,
29    #[props(default)] glow: Option<bool>,
30    #[props(default)] class: String,
31    children: Element,
32) -> Element {
33    let open = *is_open.read();
34
35    #[cfg(feature = "theme")]
36    let theme_val = crate::theme::use_theme()();
37    #[cfg(feature = "theme")]
38    let base_override = theme_val.resolve_component_base("dropdown").unwrap_or("");
39    #[cfg(feature = "theme")]
40    let class_modifiers = theme_val.resolve_class_modifiers("dropdown", &class);
41    #[cfg(feature = "theme")]
42    let glow_class = theme_val.resolve_elevation_class("dropdown", glow);
43
44    #[cfg(not(feature = "theme"))]
45    let base_override = "";
46    #[cfg(not(feature = "theme"))]
47    let class_modifiers = String::new();
48    #[cfg(not(feature = "theme"))]
49    let glow_class = if glow == Some(true) {
50        "sd-dropdown-menu--glow".to_string()
51    } else {
52        String::new()
53    };
54
55    let open_container_class = if open { "sd-dropdown-container--open" } else { "" };
56    let placement_class = placement.scss_class();
57
58    #[cfg(feature = "animation")]
59    let anim_class = crate::animation::Animations::DROPDOWN_ENTER;
60    #[cfg(not(feature = "animation"))]
61    let anim_class = "animate-dropdown-enter";
62
63    rsx! {
64        div { class: "sd-dropdown-container {open_container_class} {class_modifiers} {class}",
65            div {
66                onclick: move |_| {
67                    let next = !*is_open.read();
68                    *is_open.write() = next;
69                },
70                {trigger}
71            }
72            if open {
73                div {
74                    class: "sd-dropdown-backdrop",
75                    onclick: move |e| {
76                        e.stop_propagation();
77                        *is_open.write() = false;
78                    },
79                }
80                div {
81                    class: "sd-dropdown-menu {base_override} {placement_class} {glow_class} {anim_class}",
82                    onclick: move |e| e.stop_propagation(),
83                    {children}
84                }
85            }
86        }
87    }
88}
89
90#[component]
91pub fn DropdownItem(
92    #[props(default)] icon: Option<IconKind>,
93    #[props(default)] danger: bool,
94    #[props(default)] class: String,
95    onclick: Option<EventHandler<MouseEvent>>,
96    children: Element,
97) -> Element {
98    let danger_class = if danger { "sd-dropdown-item--danger" } else { "" };
99
100    #[cfg(feature = "theme")]
101    let theme_val = crate::theme::use_theme()();
102    #[cfg(feature = "theme")]
103    let base_override = theme_val.resolve_component_base("dropdown-item").unwrap_or("");
104    #[cfg(feature = "theme")]
105    let class_modifiers = theme_val.resolve_class_modifiers("dropdown-item", &class);
106
107    #[cfg(not(feature = "theme"))]
108    let base_override = "";
109    #[cfg(not(feature = "theme"))]
110    let class_modifiers = String::new();
111
112    rsx! {
113        button {
114            r#type: "button",
115            class: "sd-dropdown-item {base_override} {danger_class} {class_modifiers} {class}",
116            onclick: move |e| {
117                if let Some(h) = onclick.as_ref() {
118                    h.call(e);
119                }
120            },
121            if let Some(i) = icon {
122                span { class: "sd-dropdown-item__icon",
123                    Icon { kind: i, size: 15 }
124                }
125            }
126            span { class: "sd-dropdown-item__text", {children} }
127        }
128    }
129}
130
131#[component]
132pub fn DropdownDivider() -> Element {
133    rsx! {
134        div { class: "sd-dropdown-divider" }
135    }
136}