shadow_design/
dropdown.rs1use 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}