adui_dioxus/components/
drawer.rs1use crate::components::overlay::{OverlayKey, OverlayKind, use_overlay};
2use dioxus::prelude::*;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum DrawerPlacement {
7 Left,
8 Right,
9 Top,
10 Bottom,
11}
12
13#[derive(Props, Clone, PartialEq)]
15pub struct DrawerProps {
16 pub open: bool,
17 #[props(optional)]
18 pub title: Option<String>,
19 #[props(optional)]
20 pub on_close: Option<EventHandler<()>>,
21 #[props(default = true)]
22 pub mask_closable: bool,
23 #[props(default = true)]
24 pub closable: bool,
25 #[props(default)]
27 pub destroy_on_close: bool,
28 #[props(default = DrawerPlacement::Right)]
30 pub placement: DrawerPlacement,
31 #[props(optional)]
33 pub size: Option<f32>,
34 #[props(optional)]
35 pub class: Option<String>,
36 #[props(optional)]
37 pub style: Option<String>,
38 pub children: Element,
39}
40
41#[component]
43pub fn Drawer(props: DrawerProps) -> Element {
44 let DrawerProps {
45 open,
46 title,
47 on_close,
48 mask_closable,
49 closable,
50 destroy_on_close,
51 placement,
52 size,
53 class,
54 style,
55 children,
56 } = props;
57
58 let overlay = use_overlay();
59 let drawer_key: Signal<Option<OverlayKey>> = use_signal(|| None);
60 let z_index: Signal<i32> = use_signal(|| 1000);
61
62 {
63 let overlay = overlay.clone();
64 let mut key_signal = drawer_key;
65 let mut z_signal = z_index;
66 use_effect(move || {
67 if let Some(handle) = overlay.clone() {
68 let current_key = {
69 let guard = key_signal.read();
70 *guard
71 };
72 if open {
73 if current_key.is_none() {
74 let (key, meta) = handle.open(OverlayKind::Drawer, true);
75 z_signal.set(meta.z_index);
76 key_signal.set(Some(key));
77 }
78 } else if let Some(key) = current_key {
79 handle.close(key);
80 key_signal.set(None);
81 }
82 }
83 });
84 }
85
86 if !open && destroy_on_close {
87 return rsx! {};
88 }
89
90 let current_z = *z_index.read();
91 let logical_size = size.unwrap_or(378.0);
92 let class_attr = class.unwrap_or_else(|| "adui-drawer".to_string());
93 let style_attr = style.unwrap_or_default();
94
95 let close = move || {
96 if let Some(cb) = on_close {
97 cb.call(());
98 }
99 };
100
101 let (panel_style, wrapper_align_style) = match placement {
103 DrawerPlacement::Left => (
104 format!("left: 0; top: 0; bottom: 0; width: {logical_size}px;",),
105 "justify-content: flex-start; align-items: stretch;".to_string(),
106 ),
107 DrawerPlacement::Right => (
108 format!("right: 0; top: 0; bottom: 0; width: {logical_size}px;",),
109 "justify-content: flex-end; align-items: stretch;".to_string(),
110 ),
111 DrawerPlacement::Top => (
112 format!("top: 0; left: 0; right: 0; height: {logical_size}px;",),
113 "justify-content: flex-start; align-items: stretch;".to_string(),
114 ),
115 DrawerPlacement::Bottom => (
116 format!("bottom: 0; left: 0; right: 0; height: {logical_size}px;",),
117 "justify-content: flex-end; align-items: stretch;".to_string(),
118 ),
119 };
120
121 rsx! {
122 if open {
123 div {
125 class: "adui-drawer-mask",
126 style: "position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: {current_z};",
127 onclick: move |_| {
128 if mask_closable {
129 close();
130 }
131 }
132 }
133 div {
135 class: "{class_attr}",
136 style: "position: fixed; inset: 0; display: flex; {wrapper_align_style} z-index: {current_z + 1}; {style_attr}",
137 div {
138 class: "adui-drawer-panel",
139 style: "position: absolute; {panel_style} background: var(--adui-color-bg-container); border-radius: 0; box-shadow: var(--adui-shadow-secondary); border: 1px solid var(--adui-color-border); display: flex; flex-direction: column;",
140 if title.is_some() || closable {
142 div {
143 class: "adui-drawer-header",
144 style: "display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--adui-color-border);",
145 if let Some(text) = title {
146 div { class: "adui-drawer-title", "{text}" }
147 }
148 if closable {
149 button {
150 class: "adui-drawer-close",
151 r#type: "button",
152 style: "border: none; background: none; cursor: pointer; font-size: 16px;",
153 onclick: move |_| close(),
154 "×"
155 }
156 }
157 }
158 }
159 div {
161 class: "adui-drawer-body",
162 style: "padding: 16px; flex: 1; overflow: auto;",
163 {children}
164 }
165 }
166 }
167 }
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn drawer_placement_all_variants() {
177 assert_eq!(DrawerPlacement::Left, DrawerPlacement::Left);
178 assert_eq!(DrawerPlacement::Right, DrawerPlacement::Right);
179 assert_eq!(DrawerPlacement::Top, DrawerPlacement::Top);
180 assert_eq!(DrawerPlacement::Bottom, DrawerPlacement::Bottom);
181 assert_ne!(DrawerPlacement::Left, DrawerPlacement::Right);
182 assert_ne!(DrawerPlacement::Left, DrawerPlacement::Top);
183 assert_ne!(DrawerPlacement::Left, DrawerPlacement::Bottom);
184 assert_ne!(DrawerPlacement::Right, DrawerPlacement::Top);
185 assert_ne!(DrawerPlacement::Right, DrawerPlacement::Bottom);
186 assert_ne!(DrawerPlacement::Top, DrawerPlacement::Bottom);
187 }
188
189 #[test]
190 fn drawer_placement_clone() {
191 let original = DrawerPlacement::Left;
192 let cloned = original;
193 assert_eq!(original, cloned);
194 }
195
196 #[test]
197 fn drawer_placement_debug() {
198 let placement = DrawerPlacement::Right;
199 let debug_str = format!("{:?}", placement);
200 assert!(debug_str.contains("Right"));
201 }
202}