Skip to main content

dioxus_ui_system/molecules/
tooltip.rs

1//! Tooltip molecule component
2//!
3//! A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.
4
5use crate::styles::Style;
6use crate::theme::{use_style, use_theme};
7use dioxus::prelude::*;
8
9/// Tooltip properties
10#[derive(Props, Clone, PartialEq)]
11pub struct TooltipProps {
12    /// Trigger element (the element that shows the tooltip)
13    pub children: Element,
14    /// Tooltip content
15    pub content: String,
16    /// Tooltip placement
17    #[props(default)]
18    pub placement: TooltipPlacement,
19    /// Delay before showing tooltip (in ms)
20    #[props(default = 200)]
21    pub delay: u64,
22    /// Custom inline styles
23    #[props(default)]
24    pub style: Option<String>,
25}
26
27/// Tooltip placement options
28#[derive(Default, Clone, PartialEq)]
29pub enum TooltipPlacement {
30    /// Top placement (default)
31    #[default]
32    Top,
33    /// Top-start placement
34    TopStart,
35    /// Top-end placement
36    TopEnd,
37    /// Right placement
38    Right,
39    /// Right-start placement
40    RightStart,
41    /// Right-end placement
42    RightEnd,
43    /// Bottom placement
44    Bottom,
45    /// Bottom-start placement
46    BottomStart,
47    /// Bottom-end placement
48    BottomEnd,
49    /// Left placement
50    Left,
51    /// Left-start placement
52    LeftStart,
53    /// Left-end placement
54    LeftEnd,
55}
56
57/// Tooltip component
58#[component]
59pub fn Tooltip(props: TooltipProps) -> Element {
60    let _theme = use_theme();
61    let mut is_visible = use_signal(|| false);
62    let show_timeout = use_signal(|| None::<i32>);
63
64    let position_style = match props.placement {
65        TooltipPlacement::Top => {
66            "bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);"
67        }
68        TooltipPlacement::TopStart => "bottom: calc(100% + 6px); left: 0;",
69        TooltipPlacement::TopEnd => "bottom: calc(100% + 6px); right: 0;",
70        TooltipPlacement::Right => "left: calc(100% + 6px); top: 50%; transform: translateY(-50%);",
71        TooltipPlacement::RightStart => "left: calc(100% + 6px); top: 0;",
72        TooltipPlacement::RightEnd => "left: calc(100% + 6px); bottom: 0;",
73        TooltipPlacement::Bottom => {
74            "top: calc(100% + 6px); left: 50%; transform: translateX(-50%);"
75        }
76        TooltipPlacement::BottomStart => "top: calc(100% + 6px); left: 0;",
77        TooltipPlacement::BottomEnd => "top: calc(100% + 6px); right: 0;",
78        TooltipPlacement::Left => "right: calc(100% + 6px); top: 50%; transform: translateY(-50%);",
79        TooltipPlacement::LeftStart => "right: calc(100% + 6px); top: 0;",
80        TooltipPlacement::LeftEnd => "right: calc(100% + 6px); bottom: 0;",
81    };
82
83    let tooltip_style = use_style(|t| {
84        Style::new()
85            .absolute()
86            .px(&t.spacing, "sm")
87            .py(&t.spacing, "xs")
88            .rounded(&t.radius, "md")
89            .bg(&t.colors.foreground)
90            .text_color(&t.colors.background)
91            .font_size(12)
92            .font_weight(500)
93            .whitespace_nowrap()
94            .z_index(100)
95            .build()
96    });
97
98    let mut show_tooltip = move || {
99        // Use set_timeout for delay (simplified)
100        // In a real implementation, you'd use web_sys::set_timeout_with_callback
101        // For now, we show immediately
102        is_visible.set(true);
103    };
104
105    let mut hide_tooltip = move || {
106        if let Some(timeout) = show_timeout() {
107            // Clear timeout would go here
108            let _ = timeout;
109        }
110        is_visible.set(false);
111    };
112
113    rsx! {
114        span {
115            style: "position: relative; display: inline-block;",
116            onmouseenter: move |_| show_tooltip(),
117            onmouseleave: move |_| hide_tooltip(),
118            onfocus: move |_| show_tooltip(),
119            onblur: move |_| hide_tooltip(),
120
121            {props.children}
122
123            if is_visible() {
124                div {
125                    role: "tooltip",
126                    style: "{tooltip_style} {position_style} {props.style.clone().unwrap_or_default()}",
127                    "{props.content}"
128                }
129            }
130        }
131    }
132}
133
134/// Simple tooltip that wraps any element
135#[derive(Props, Clone, PartialEq)]
136pub struct SimpleTooltipProps {
137    /// Element to wrap
138    pub children: Element,
139    /// Tooltip text
140    pub text: String,
141    /// Tooltip placement
142    #[props(default)]
143    pub placement: TooltipPlacement,
144}
145
146/// Simple tooltip wrapper
147#[component]
148pub fn SimpleTooltip(props: SimpleTooltipProps) -> Element {
149    rsx! {
150        Tooltip {
151            content: props.text,
152            placement: props.placement,
153            {props.children}
154        }
155    }
156}