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 = crate::theme::use_theme();
37    #[cfg(feature = "theme")]
38    let glow_class = match glow {
39        Some(true) => "sd-dropdown-menu--glow",
40        Some(false) => "",
41        None => {
42            if theme().glow_mode != crate::theme::GlowMode::Off {
43                "sd-dropdown-menu--glow"
44            } else {
45                ""
46            }
47        }
48    };
49
50    #[cfg(not(feature = "theme"))]
51    let glow_class = if glow == Some(true) {
52        "sd-dropdown-menu--glow"
53    } else {
54        ""
55    };
56
57    let open_container_class = if open { "sd-dropdown-container--open" } else { "" };
58    let placement_class = placement.scss_class();
59
60    #[cfg(feature = "animation")]
61    let anim_class = crate::animation::Animations::DROPDOWN_ENTER;
62    #[cfg(not(feature = "animation"))]
63    let anim_class = "animate-dropdown-enter";
64
65    rsx! {
66        div { class: "sd-dropdown-container {open_container_class} {class}",
67            div {
68                onclick: move |_| {
69                    let next = !*is_open.read();
70                    *is_open.write() = next;
71                },
72                {trigger}
73            }
74            if open {
75                div {
76                    class: "sd-dropdown-backdrop",
77                    onclick: move |e| {
78                        e.stop_propagation();
79                        *is_open.write() = false;
80                    },
81                }
82                div {
83                    class: "sd-dropdown-menu {placement_class} {glow_class} {anim_class}",
84                    onclick: move |e| e.stop_propagation(),
85                    {children}
86                }
87            }
88        }
89    }
90}
91
92#[component]
93pub fn DropdownItem(
94    #[props(default)] icon: Option<IconKind>,
95    #[props(default)] danger: bool,
96    #[props(default)] class: String,
97    onclick: Option<EventHandler<MouseEvent>>,
98    children: Element,
99) -> Element {
100    let danger_class = if danger { "sd-dropdown-item--danger" } else { "" };
101
102    rsx! {
103        button {
104            r#type: "button",
105            class: "sd-dropdown-item {danger_class} {class}",
106            onclick: move |e| {
107                if let Some(h) = onclick.as_ref() {
108                    h.call(e);
109                }
110            },
111            if let Some(i) = icon {
112                Icon { kind: i, size: 14 }
113            }
114            {children}
115        }
116    }
117}
118
119#[component]
120pub fn DropdownDivider() -> Element {
121    rsx! {
122        div { class: "sd-dropdown-divider" }
123    }
124}