impulse_thaw/tooltip/
tooltip.rs1use leptos::{
2 ev::{self, on},
3 html,
4 leptos_dom::helpers::TimeoutHandle,
5 prelude::*,
6 tachys::html::class::class as tachys_class,
7};
8use std::time::Duration;
9use thaw_components::{Follower, FollowerPlacement};
10use thaw_utils::{class_list, mount_style};
11
12#[component]
13pub fn Tooltip<T>(
14 #[prop(optional, into)]
16 content: Option<Signal<String>>,
17 #[prop(optional)]
19 position: TooltipPosition,
20 #[prop(optional, into)]
22 appearance: Signal<TooltipAppearance>,
23 children: TypedChildren<T>,
24) -> impl IntoView
25where
26 T: AddAnyAttr + IntoView + Send + 'static,
27{
28 mount_style("tooltip", include_str!("./tooltip.css"));
29
30 let is_show_content = RwSignal::new(false);
31 let content_handle = StoredValue::new(None::<TimeoutHandle>);
32
33 let on_mouse_enter = move |_| {
34 content_handle.update_value(|handle| {
35 if let Some(handle) = handle.take() {
36 handle.clear();
37 }
38 });
39 is_show_content.set(true);
40 };
41 let on_mouse_leave = move |_| {
42 content_handle.update_value(|handle| {
43 if let Some(handle) = handle.take() {
44 handle.clear();
45 }
46 *handle = set_timeout_with_handle(
47 move || {
48 is_show_content.set(false);
49 },
50 Duration::from_millis(100),
51 )
52 .ok();
53 });
54 };
55
56 let arrow_ref = NodeRef::<html::Div>::new();
57 let edge_length = 1.414 * 8.0;
58 let arrow_style = format!(
59 "--thaw-positioning-arrow-height: {}px; --thaw-positioning-arrow-offset: {}px;",
60 edge_length,
61 (edge_length / 2.0) * -1.0
62 );
63
64 Owner::on_cleanup(move || {
65 content_handle.update_value(|handle| {
66 if let Some(handle) = handle.take() {
67 handle.clear();
68 }
69 });
70 });
71
72 view! {
73 <crate::_binder::Binder>
74 {children
75 .into_inner()()
76 .into_inner()
77 .add_any_attr(tachys_class(("thaw-tooltip", true)))
78 .add_any_attr(on(ev::mouseenter, on_mouse_enter))
79 .add_any_attr(on(ev::mouseleave, on_mouse_leave))}
80 <Follower slot show=is_show_content placement=position arrow=(edge_length / 2.0 + 2.0, arrow_ref)>
81 <div
82 class=class_list![
83 "thaw-tooltip-content",
84 move || format!("thaw-tooltip-content--{}", appearance.get().as_str())
85 ]
86 role="tooltip"
87 on:mouseenter=on_mouse_enter
88 on:mouseleave=on_mouse_leave
89 >
90 {move || { content.as_ref().map(|c| c.get()).unwrap_or_default() }}
91 <div class="thaw-tooltip-content__angle" style=arrow_style node_ref=arrow_ref></div>
92 </div>
93 </Follower>
94 </crate::_binder::Binder>
95 }
96}
97
98#[derive(Clone, Default)]
99pub enum TooltipAppearance {
100 #[default]
101 Normal,
102 Inverted,
103}
104
105impl TooltipAppearance {
106 pub fn as_str(&self) -> &'static str {
107 match self {
108 Self::Normal => "normal",
109 Self::Inverted => "inverted",
110 }
111 }
112}
113
114#[derive(Default)]
115pub enum TooltipPosition {
116 #[default]
117 Top,
118 Bottom,
119 Left,
120 Right,
121 TopStart,
122 TopEnd,
123 LeftStart,
124 LeftEnd,
125 RightStart,
126 RightEnd,
127 BottomStart,
128 BottomEnd,
129}
130
131impl From<TooltipPosition> for FollowerPlacement {
132 fn from(value: TooltipPosition) -> Self {
133 match value {
134 TooltipPosition::Top => Self::Top,
135 TooltipPosition::Bottom => Self::Bottom,
136 TooltipPosition::Left => Self::Left,
137 TooltipPosition::Right => Self::Right,
138 TooltipPosition::TopStart => Self::TopStart,
139 TooltipPosition::TopEnd => Self::TopEnd,
140 TooltipPosition::LeftStart => Self::LeftStart,
141 TooltipPosition::LeftEnd => Self::LeftEnd,
142 TooltipPosition::RightStart => Self::RightStart,
143 TooltipPosition::RightEnd => Self::RightEnd,
144 TooltipPosition::BottomStart => Self::BottomStart,
145 TooltipPosition::BottomEnd => Self::BottomEnd,
146 }
147 }
148}