Skip to main content

dioxus_bootstrap_css/
tooltip.rs

1use dioxus::prelude::*;
2
3/// Tooltip placement.
4#[derive(Clone, Copy, Debug, Default, PartialEq)]
5pub enum TooltipPlacement {
6    #[default]
7    Top,
8    Bottom,
9    Start,
10    End,
11}
12
13/// Bootstrap Tooltip component — CSS-positioned, no JavaScript.
14///
15/// Shows a tooltip on hover. Uses CSS-based positioning relative to the trigger element.
16///
17/// # Bootstrap HTML → Dioxus
18///
19/// ```html
20/// <!-- Bootstrap HTML (requires JavaScript + Popper.js) -->
21/// <button data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip text">Hover me</button>
22/// ```
23///
24/// ```rust,no_run
25/// // Dioxus equivalent — no JavaScript needed
26/// rsx! {
27///     Tooltip { text: "Save your work", placement: TooltipPlacement::Top,
28///         Button { color: Color::Primary, "Save" }
29///     }
30///     Tooltip { text: "More info", placement: TooltipPlacement::End,
31///         Icon { name: "info-circle" }
32///     }
33/// }
34/// ```
35///
36/// # Props
37///
38/// - `text` — tooltip text content
39/// - `placement` — `TooltipPlacement::Top`, `Bottom`, `Start`, `End`
40#[derive(Clone, PartialEq, Props)]
41pub struct TooltipProps {
42    /// Tooltip text content.
43    pub text: String,
44    /// Tooltip placement relative to the trigger element.
45    #[props(default)]
46    pub placement: TooltipPlacement,
47    /// Additional CSS classes for the tooltip.
48    #[props(default)]
49    pub class: String,
50    /// Child element (the trigger).
51    pub children: Element,
52}
53
54#[component]
55pub fn Tooltip(props: TooltipProps) -> Element {
56    let hovering = use_signal(|| false);
57    let is_hovering = *hovering.read();
58    let mut hover_signal = hovering;
59
60    let placement_class = match props.placement {
61        TooltipPlacement::Top => "bs-tooltip-top",
62        TooltipPlacement::Bottom => "bs-tooltip-bottom",
63        TooltipPlacement::Start => "bs-tooltip-start",
64        TooltipPlacement::End => "bs-tooltip-end",
65    };
66
67    let tooltip_class = if props.class.is_empty() {
68        format!("tooltip fade {placement_class} show")
69    } else {
70        format!("tooltip fade {placement_class} show {}", props.class)
71    };
72
73    // Position styles based on placement
74    let position_style = match props.placement {
75        TooltipPlacement::Top => {
76            "position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 0.4rem;"
77        }
78        TooltipPlacement::Bottom => {
79            "position: absolute; top: 100%; left: 50%; transform: translateX(-50%); margin-top: 0.4rem;"
80        }
81        TooltipPlacement::Start => {
82            "position: absolute; right: 100%; top: 50%; transform: translateY(-50%); margin-right: 0.4rem;"
83        }
84        TooltipPlacement::End => {
85            "position: absolute; left: 100%; top: 50%; transform: translateY(-50%); margin-left: 0.4rem;"
86        }
87    };
88
89    let arrow_placement = match props.placement {
90        TooltipPlacement::Top => "bottom: -6px; left: 50%; transform: translateX(-50%);",
91        TooltipPlacement::Bottom => "top: -6px; left: 50%; transform: translateX(-50%);",
92        TooltipPlacement::Start => "right: -6px; top: 50%; transform: translateY(-50%);",
93        TooltipPlacement::End => "left: -6px; top: 50%; transform: translateY(-50%);",
94    };
95
96    rsx! {
97        div {
98            style: "position: relative; display: inline-block;",
99            onmouseenter: move |_| hover_signal.set(true),
100            onmouseleave: move |_| hover_signal.set(false),
101            {props.children}
102            if is_hovering {
103                div {
104                    class: "{tooltip_class}",
105                    role: "tooltip",
106                    style: "{position_style} z-index: 1080; pointer-events: none; white-space: nowrap;",
107                    div { class: "tooltip-arrow", style: "position: absolute; {arrow_placement}" }
108                    div { class: "tooltip-inner", "{props.text}" }
109                }
110            }
111        }
112    }
113}