dioxus_tw_components/components/molecules/hovercard/
props.rs1use crate::attributes::*;
2use chrono::{DateTime, Local, TimeDelta};
3use dioxus::prelude::*;
4use dioxus_core::AttributeValue;
5use dioxus_tw_components_macro::UiComp;
6
7#[cfg(target_arch = "wasm32")]
8use gloo_timers::future::TimeoutFuture;
9
10#[derive(Clone, Debug)]
11pub struct HoverState {
12 is_active: bool,
13 is_hovered: bool,
14 last_hover: DateTime<Local>,
15 closing_delay_ms: TimeDelta,
16}
17
18impl HoverState {
19 fn new(closing_delay_ms: u32) -> Self {
20 Self {
21 is_active: false,
22 closing_delay_ms: TimeDelta::milliseconds(closing_delay_ms as i64),
23 is_hovered: false,
24 last_hover: DateTime::default(),
25 }
26 }
27
28 fn toggle(&mut self) {
29 self.is_active = !self.is_active;
30 }
31
32 fn open(&mut self) {
33 self.is_active = true;
34 }
35
36 fn close(&mut self) {
37 self.is_active = false;
38 }
39
40 fn set_is_hovered(&mut self, is_hovered: bool) {
41 self.is_hovered = is_hovered;
42 }
43
44 fn get_is_hovered(&self) -> bool {
45 self.is_hovered
46 }
47
48 fn set_last_hover(&mut self, last_hover: DateTime<Local>) {
49 self.last_hover = last_hover;
50 }
51
52 fn get_last_hover(&self) -> DateTime<Local> {
53 self.last_hover
54 }
55
56 fn get_closing_delay(&self) -> TimeDelta {
57 self.closing_delay_ms
58 }
59}
60
61impl IntoAttributeValue for HoverState {
62 fn into_value(self) -> AttributeValue {
63 match self.is_active {
64 true => AttributeValue::Text("active".to_string()),
65 false => AttributeValue::Text("inactive".to_string()),
66 }
67 }
68}
69
70#[derive(Clone, PartialEq, Props, UiComp)]
71pub struct HoverCardProps {
72 #[props(default = 500)]
74 closing_delay_ms: u32,
75
76 #[props(extends = div, extends = GlobalAttributes)]
77 attributes: Vec<Attribute>,
78
79 children: Element,
80}
81
82impl std::default::Default for HoverCardProps {
83 fn default() -> Self {
84 Self {
85 closing_delay_ms: 500,
86 attributes: Vec::<Attribute>::default(),
87 children: rsx! {},
88 }
89 }
90}
91
92#[component]
93pub fn HoverCard(mut props: HoverCardProps) -> Element {
94 let mut state = use_context_provider(|| Signal::new(HoverState::new(props.closing_delay_ms)));
95
96 props.update_class_attribute();
97
98 let onmouseenter = move |_event| {
99 state.write().set_is_hovered(true);
100 state.write().open();
101 };
102
103 let onmouseleave = move |_| {
104 state.write().set_last_hover(Local::now());
105 state.write().set_is_hovered(false);
106
107 let closing_delay_ms = state.read().closing_delay_ms;
108
109 spawn(async move {
110 #[cfg(target_arch = "wasm32")]
111 {
112 TimeoutFuture::new(
113 closing_delay_ms
114 .num_milliseconds()
115 .try_into()
116 .unwrap_or_default(),
117 )
118 .await;
119 }
120 #[cfg(not(target_arch = "wasm32"))]
121 {
122 let _ = tokio::time::sleep(std::time::Duration::from_millis(
123 closing_delay_ms
124 .num_milliseconds()
125 .try_into()
126 .unwrap_or_default(),
127 ))
128 .await;
129 }
130
131 let is_hovered = state.read().get_is_hovered();
132
133 let last_hover = state.read().get_last_hover();
134 let now = Local::now();
135 let dt = state.read().get_closing_delay();
136
137 if !is_hovered && now - last_hover >= dt {
138 state.write().close();
139 }
140 });
141 };
142
143 rsx! {
144 div {
145 "data-state": state.into_value(),
146 onmouseenter,
147 onmouseleave,
148 ..props.attributes,
149 {props.children}
150 }
151 }
152}
153
154#[derive(Clone, PartialEq, Props, UiComp)]
155pub struct HoverCardTriggerProps {
156 #[props(extends = div, extends = GlobalAttributes)]
157 attributes: Vec<Attribute>,
158
159 #[props(optional, default)]
160 onclick: EventHandler<MouseEvent>,
161
162 children: Element,
163}
164
165impl std::default::Default for HoverCardTriggerProps {
166 fn default() -> Self {
167 Self {
168 attributes: Vec::<Attribute>::default(),
169 onclick: EventHandler::<MouseEvent>::default(),
170 children: rsx! {},
171 }
172 }
173}
174
175#[component]
176pub fn HoverCardTrigger(mut props: HoverCardTriggerProps) -> Element {
177 let mut state = use_context::<Signal<HoverState>>();
178
179 props.update_class_attribute();
180
181 let onclick = move |event| {
183 state.write().toggle();
184 props.onclick.call(event);
185 };
186
187 rsx! {
188 div {
189 role: "button",
190 "data-state": state.into_value(),
191 onclick,
192 ..props.attributes,
193 {props.children}
194 }
195 }
196}
197
198#[derive(Clone, PartialEq, Props, UiComp)]
199pub struct HoverCardContentProps {
200 #[props(extends = div, extends = GlobalAttributes)]
201 attributes: Vec<Attribute>,
202
203 #[props(optional, default)]
204 pub animation: ReadOnlySignal<Animation>,
205
206 children: Element,
207}
208
209impl std::default::Default for HoverCardContentProps {
210 fn default() -> Self {
211 Self {
212 attributes: Vec::<Attribute>::default(),
213 animation: ReadOnlySignal::<Animation>::default(),
214 children: rsx! {},
215 }
216 }
217}
218
219#[component]
220pub fn HoverCardContent(mut props: HoverCardContentProps) -> Element {
221 let state = use_context::<Signal<HoverState>>();
222
223 props.update_class_attribute();
224
225 rsx! {
226 div { "data-state": state.into_value(), ..props.attributes, {props.children} }
227 }
228}