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/// # use dioxus::prelude::*;
26/// # use dioxus_bootstrap_css::prelude::*;
27/// # fn _doctest() -> Element {
28/// // Dioxus equivalent — no JavaScript needed
29/// rsx! {
30///     Tooltip { text: "Save your work", placement: TooltipPlacement::Top,
31///         Button { color: Color::Primary, "Save" }
32///     }
33///     Tooltip { text: "More info", placement: TooltipPlacement::End,
34///         Icon { name: "info-circle" }
35///     }
36/// }
37/// # }
38/// ```
39///
40/// # Props
41///
42/// - `text` — tooltip text content
43/// - `placement` — `TooltipPlacement::Top`, `Bottom`, `Start`, `End`
44#[derive(Clone, PartialEq, Props)]
45pub struct TooltipProps {
46    /// Tooltip text content.
47    pub text: String,
48    /// Tooltip placement relative to the trigger element.
49    #[props(default)]
50    pub placement: TooltipPlacement,
51    /// Additional CSS classes for the tooltip.
52    #[props(default)]
53    pub class: String,
54    /// Child element (the trigger).
55    pub children: Element,
56}
57
58#[component]
59pub fn Tooltip(props: TooltipProps) -> Element {
60    let hovering = use_signal(|| false);
61    let is_hovering = *hovering.read();
62    let mut hover_signal = hovering;
63
64    let placement_class = match props.placement {
65        TooltipPlacement::Top => "bs-tooltip-top",
66        TooltipPlacement::Bottom => "bs-tooltip-bottom",
67        TooltipPlacement::Start => "bs-tooltip-start",
68        TooltipPlacement::End => "bs-tooltip-end",
69    };
70
71    let tooltip_class = if props.class.is_empty() {
72        format!("tooltip fade {placement_class} show")
73    } else {
74        format!("tooltip fade {placement_class} show {}", props.class)
75    };
76
77    // Position styles based on placement
78    let position_style = match props.placement {
79        TooltipPlacement::Top => {
80            "position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 0.4rem;"
81        }
82        TooltipPlacement::Bottom => {
83            "position: absolute; top: 100%; left: 50%; transform: translateX(-50%); margin-top: 0.4rem;"
84        }
85        TooltipPlacement::Start => {
86            "position: absolute; right: 100%; top: 50%; transform: translateY(-50%); margin-right: 0.4rem;"
87        }
88        TooltipPlacement::End => {
89            "position: absolute; left: 100%; top: 50%; transform: translateY(-50%); margin-left: 0.4rem;"
90        }
91    };
92
93    let arrow_placement = match props.placement {
94        TooltipPlacement::Top => "bottom: -6px; left: 50%; transform: translateX(-50%);",
95        TooltipPlacement::Bottom => "top: -6px; left: 50%; transform: translateX(-50%);",
96        TooltipPlacement::Start => "right: -6px; top: 50%; transform: translateY(-50%);",
97        TooltipPlacement::End => "left: -6px; top: 50%; transform: translateY(-50%);",
98    };
99
100    rsx! {
101        div {
102            style: "position: relative; display: inline-block;",
103            onmouseenter: move |_| hover_signal.set(true),
104            onmouseleave: move |_| hover_signal.set(false),
105            {props.children}
106            if is_hovering {
107                div {
108                    class: "{tooltip_class}",
109                    role: "tooltip",
110                    style: "{position_style} z-index: 1080; pointer-events: none; white-space: nowrap;",
111                    div { class: "tooltip-arrow", style: "position: absolute; {arrow_placement}" }
112                    div { class: "tooltip-inner", "{props.text}" }
113                }
114            }
115        }
116    }
117}