freya_components/
tooltip.rs

1use dioxus::prelude::*;
2use freya_elements::{
3    self as dioxus_elements,
4    events::MouseEvent,
5};
6use freya_hooks::{
7    use_applied_theme,
8    use_node_signal,
9    TooltipTheme,
10    TooltipThemeWith,
11};
12
13/// Properties for the [`Tooltip`] component.
14#[derive(Props, Clone, PartialEq)]
15pub struct TooltipProps {
16    /// Theme override.
17    pub theme: Option<TooltipThemeWith>,
18    /// Text to show in the [Tooltip].
19    pub text: String,
20}
21
22/// `Tooltip` component. Use in combination with [TooltipContainer()].
23///
24/// # Styling
25/// Inherits the [`TooltipTheme`](freya_hooks::TooltipTheme)
26///
27/// # Example
28///
29/// ```rust
30/// # use freya::prelude::*;
31/// fn app() -> Element {
32///     rsx!(
33///         TooltipContainer {
34///             tooltip: rsx!(
35///                 Tooltip {
36///                     text: "Hey!"
37///                 }
38///             ),
39///             label { "Hover me!" }
40///        }
41///     )
42/// }
43/// # use freya_testing::prelude::*;
44/// # launch_doc_with_utils(|| {
45/// #   rsx!(
46/// #       Preview {
47/// #           {app()}
48/// #       }
49/// #   )
50/// # }, (250., 250.).into(), |mut utils| async move {
51/// #   utils.wait_for_update().await;
52/// #   utils.move_cursor((125., 125.)).await;
53/// #   utils.wait_for_update().await;
54/// #   utils.save_snapshot("./images/gallery_tooltip.png");
55/// # });
56/// ```
57///
58/// # Preview
59/// ![Tooltip Preview][tooltip]
60#[cfg_attr(feature = "docs",
61    doc = embed_doc_image::embed_image!("tooltip", "images/gallery_tooltip.png")
62)]
63#[allow(non_snake_case)]
64pub fn Tooltip(TooltipProps { text, theme }: TooltipProps) -> Element {
65    let theme = use_applied_theme!(&theme, tooltip);
66    let TooltipTheme {
67        background,
68        color,
69        border_fill,
70    } = theme;
71
72    rsx!(
73        rect {
74            padding: "4 10",
75            shadow: "0 1 2 1 rgb(0, 0, 0, 0.05)",
76            border: "1 inner {border_fill}",
77            corner_radius: "8",
78            background: "{background}",
79            label { max_lines: "1", font_size: "14", color: "{color}", "{text}" }
80        }
81    )
82}
83
84#[derive(PartialEq, Clone, Copy, Debug)]
85pub enum TooltipPosition {
86    Besides,
87    Below,
88}
89
90/// `TooltipContainer` component.
91///
92/// Provides a hoverable area where to show a [Tooltip].
93///
94/// # Example
95#[component]
96pub fn TooltipContainer(
97    tooltip: Element,
98    children: Element,
99    #[props(default = TooltipPosition::Below, into)] position: TooltipPosition,
100) -> Element {
101    let mut is_hovering = use_signal(|| false);
102    let (reference, size) = use_node_signal();
103
104    let onmouseenter = move |_: MouseEvent| {
105        is_hovering.set(true);
106    };
107
108    let onmouseleave = move |_: MouseEvent| {
109        is_hovering.set(false);
110    };
111
112    let direction = match position {
113        TooltipPosition::Below => "vertical",
114        TooltipPosition::Besides => "horizontal",
115    };
116
117    rsx!(
118        rect {
119            direction,
120            reference,
121            onmouseenter,
122            onmouseleave,
123            {children},
124            rect {
125                height: "0",
126                width: "0",
127                layer: "-1500",
128                if *is_hovering.read() {
129                    match position {
130                        TooltipPosition::Below => rsx!(
131                            rect {
132                                width: "{size.read().area.width()}",
133                                cross_align: "center",
134                                padding: "5 0 0 0",
135                                {tooltip}
136                            }
137                        ),
138                        TooltipPosition::Besides => rsx!(
139                            rect {
140                                height: "{size.read().area.height()}",
141                                main_align: "center",
142                                padding: "0 0 0 5",
143                                {tooltip}
144                            }
145                        ),
146                    }
147                }
148            }
149        }
150    )
151}