dioxus_popup/
lib.rs

1#![allow(non_snake_case)]
2use dioxus::prelude::*;
3
4#[derive(Debug, Clone, Copy, Default)]
5pub struct PopupService {
6    pub id: Signal<Option<String>>,
7    pub title: Signal<Option<String>>,
8    pub data: Signal<Option<Element>>,
9    pub close: Signal<bool>,
10}
11
12impl PopupService {
13    pub fn init() {
14        let srv = Self {
15            data: Signal::new(None),
16            id: Signal::new(None),
17            title: Signal::new(None),
18            close: Signal::new(true),
19        };
20        use_context_provider(|| srv);
21    }
22
23    pub fn render(&self) -> Element {
24        (self.data)().clone().unwrap_or(default())
25    }
26
27    pub fn is_opened(&self) -> bool {
28        (self.data)().is_some()
29    }
30
31    pub fn get_id(&self) -> String {
32        (self.id)().clone().unwrap_or("popup-zone".to_string())
33    }
34
35    pub fn get_title(&self) -> Option<String> {
36        (self.title)().clone()
37    }
38
39    pub fn open(&mut self, popup: Element) -> &mut Self {
40        (self.data).set(Some(popup));
41
42        self
43    }
44
45    pub fn with_id(&mut self, id: &str) -> &mut Self {
46        (self.id).set(Some(id.to_string()));
47
48        self
49    }
50
51    pub fn with_title(&mut self, title: &str) -> &mut Self {
52        (self.title).set(Some(title.to_string()));
53
54        self
55    }
56
57    pub fn without_close(&mut self) -> &mut Self {
58        (self.close).set(false);
59
60        self
61    }
62
63    pub fn close(&mut self) {
64        (self.data).set(None);
65        (self.id).set(None);
66        (self.title).set(None);
67        (self.close).set(true);
68    }
69
70    pub fn use_popup_service() -> PopupService {
71        use_context()
72    }
73}
74
75#[component]
76pub fn default() -> Element {
77    rsx! {}
78}
79
80#[component]
81pub fn PopupZone() -> Element {
82    let mut popup: PopupService = use_context();
83    let mut hover_close = use_signal(|| false);
84
85    rsx! {
86        div {
87            class: format!(
88                "{}",
89                match popup.is_opened() {
90                    true => {
91                        "fixed top-0 left-0 w-screen h-screen bg-black bg-opacity-50 flex justify-center items-center backdrop-blur-[4px] bg-black/25 z-[101]"
92                    }
93                    false => "hidden",
94                },
95            ),
96            onclick: move |_| {
97                popup.close();
98            },
99            if popup.is_opened() {
100                div {
101                    class: "relative bg-[#424563] rounded-[12px] border-[#292B3C] border-[1px] p-[25px] min-w-[350px]",
102                    onclick: move |e| {
103                        e.stop_propagation();
104                    },
105                    if (popup.close)() {
106                        div {
107                            class: format!(
108                                "absolute top-[25px] right-[25px] rounded-[4px] cursor-pointer {}",
109                                if hover_close() { "bg-[{theme.background}]" } else { "" },
110                            ),
111                            onclick: move |_| {
112                                popup.close();
113                            },
114                            onmouseenter: move |_| {
115                                hover_close.set(true);
116                            },
117                            onmouseleave: move |_| {
118                                hover_close.set(false);
119                            },
120                            Close { color: if hover_close() { "#74789E" } else { "white" } }
121                        }
122                    }
123                    div {
124                        id: popup.get_id(),
125                        class: "flex flex-col items-center justify-center gap-[25px]",
126                        match popup.get_title() {
127                            Some(title) => {
128                                rsx! {
129                                    div { class: "text-[20px] font-bold text-white", "{title}" }
130                                }
131                            }
132                            None => rsx! {},
133                        }
134                        {popup.render()}
135                    }
136                }
137            }
138        }
139    }
140}
141
142#[component]
143pub fn Close(#[props(default = "white".to_string())] color: String) -> Element {
144    rsx! {
145        svg {
146            width: "24",
147            height: "24",
148            view_box: "0 0 24 24",
149            fill: "none",
150            xmlns: "http://www.w3.org/2000/svg",
151            path {
152                d: "M16.9498 7.05029L7.05029 16.9498M7.05029 7.05029L16.9498 16.9498",
153                stroke: "{color}",
154                stroke_width: "2",
155                stroke_linecap: "round",
156                stroke_linejoin: "round",
157            }
158        }
159    }
160}