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 dioxus::prelude::*;
6use crate::theme::{use_theme, use_style};
7use crate::styles::Style;
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 => "bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);",
66        TooltipPlacement::TopStart => "bottom: calc(100% + 6px); left: 0;",
67        TooltipPlacement::TopEnd => "bottom: calc(100% + 6px); right: 0;",
68        TooltipPlacement::Right => "left: calc(100% + 6px); top: 50%; transform: translateY(-50%);",
69        TooltipPlacement::RightStart => "left: calc(100% + 6px); top: 0;",
70        TooltipPlacement::RightEnd => "left: calc(100% + 6px); bottom: 0;",
71        TooltipPlacement::Bottom => "top: calc(100% + 6px); left: 50%; transform: translateX(-50%);",
72        TooltipPlacement::BottomStart => "top: calc(100% + 6px); left: 0;",
73        TooltipPlacement::BottomEnd => "top: calc(100% + 6px); right: 0;",
74        TooltipPlacement::Left => "right: calc(100% + 6px); top: 50%; transform: translateY(-50%);",
75        TooltipPlacement::LeftStart => "right: calc(100% + 6px); top: 0;",
76        TooltipPlacement::LeftEnd => "right: calc(100% + 6px); bottom: 0;",
77    };
78    
79    let tooltip_style = use_style(|t| {
80        Style::new()
81            .absolute()
82            .px(&t.spacing, "sm")
83            .py(&t.spacing, "xs")
84            .rounded(&t.radius, "md")
85            .bg(&t.colors.foreground)
86            .text_color(&t.colors.background)
87            .font_size(12)
88            .font_weight(500)
89            .whitespace_nowrap()
90            .z_index(100)
91            .build()
92    });
93    
94    let mut show_tooltip = move || {
95        // Use set_timeout for delay (simplified)
96        // In a real implementation, you'd use web_sys::set_timeout_with_callback
97        // For now, we show immediately
98        is_visible.set(true);
99    };
100    
101    let mut hide_tooltip = move || {
102        if let Some(timeout) = show_timeout() {
103            // Clear timeout would go here
104            let _ = timeout;
105        }
106        is_visible.set(false);
107    };
108    
109    rsx! {
110        span {
111            style: "position: relative; display: inline-block;",
112            onmouseenter: move |_| show_tooltip(),
113            onmouseleave: move |_| hide_tooltip(),
114            onfocus: move |_| show_tooltip(),
115            onblur: move |_| hide_tooltip(),
116            
117            {props.children}
118            
119            if is_visible() {
120                div {
121                    role: "tooltip",
122                    style: "{tooltip_style} {position_style} {props.style.clone().unwrap_or_default()}",
123                    "{props.content}"
124                }
125            }
126        }
127    }
128}
129
130/// Simple tooltip that wraps any element
131#[derive(Props, Clone, PartialEq)]
132pub struct SimpleTooltipProps {
133    /// Element to wrap
134    pub children: Element,
135    /// Tooltip text
136    pub text: String,
137    /// Tooltip placement
138    #[props(default)]
139    pub placement: TooltipPlacement,
140}
141
142/// Simple tooltip wrapper
143#[component]
144pub fn SimpleTooltip(props: SimpleTooltipProps) -> Element {
145    rsx! {
146        Tooltip {
147            content: props.text,
148            placement: props.placement,
149            {props.children}
150        }
151    }
152}