ironhtml_bootstrap/
tooltip.rs

1//! Bootstrap tooltip and popover components.
2//!
3//! Provides type-safe Bootstrap tooltips and popovers matching the
4//! [Bootstrap tooltips documentation](https://getbootstrap.com/docs/5.3/components/tooltips/) and
5//! [Bootstrap popovers documentation](https://getbootstrap.com/docs/5.3/components/popovers/).
6
7use ironhtml::typed::Element;
8use ironhtml_elements::{Button, A};
9
10extern crate alloc;
11use alloc::format;
12
13/// Tooltip/popover placement options.
14#[derive(Clone, Copy, Default)]
15pub enum Placement {
16    #[default]
17    Top,
18    Right,
19    Bottom,
20    Left,
21}
22
23impl Placement {
24    const fn as_str(self) -> &'static str {
25        match self {
26            Self::Top => "top",
27            Self::Right => "right",
28            Self::Bottom => "bottom",
29            Self::Left => "left",
30        }
31    }
32}
33
34/// Create a button with a tooltip.
35///
36/// ## Example
37///
38/// ```rust
39/// use ironhtml_bootstrap::tooltip::{tooltip_button, Placement};
40/// use ironhtml_bootstrap::Color;
41///
42/// let btn = tooltip_button(Color::Primary, "Hover me", "Tooltip text", Placement::Top);
43/// assert!(btn.render().contains("data-bs-toggle=\"tooltip\""));
44/// ```
45#[must_use]
46pub fn tooltip_button(
47    color: crate::Color,
48    text: &str,
49    tooltip: &str,
50    placement: Placement,
51) -> Element<Button> {
52    let class = format!("btn btn-{}", color.as_str());
53    Element::<Button>::new()
54        .attr("type", "button")
55        .class(&class)
56        .attr("data-bs-toggle", "tooltip")
57        .attr("data-bs-placement", placement.as_str())
58        .attr("data-bs-title", tooltip)
59        .text(text)
60}
61
62/// Create a link with a tooltip.
63#[must_use]
64pub fn tooltip_link(href: &str, text: &str, tooltip: &str, placement: Placement) -> Element<A> {
65    Element::<A>::new()
66        .attr("href", href)
67        .attr("data-bs-toggle", "tooltip")
68        .attr("data-bs-placement", placement.as_str())
69        .attr("data-bs-title", tooltip)
70        .text(text)
71}
72
73/// Create a button with HTML tooltip content.
74#[must_use]
75pub fn tooltip_html_button(
76    color: crate::Color,
77    text: &str,
78    tooltip_html: &str,
79    placement: Placement,
80) -> Element<Button> {
81    let class = format!("btn btn-{}", color.as_str());
82    Element::<Button>::new()
83        .attr("type", "button")
84        .class(&class)
85        .attr("data-bs-toggle", "tooltip")
86        .attr("data-bs-placement", placement.as_str())
87        .attr("data-bs-html", "true")
88        .attr("data-bs-title", tooltip_html)
89        .text(text)
90}
91
92/// Create a button with a popover.
93///
94/// ## Example
95///
96/// ```rust
97/// use ironhtml_bootstrap::tooltip::{popover_button, Placement};
98/// use ironhtml_bootstrap::Color;
99///
100/// let btn = popover_button(
101///     Color::Danger,
102///     "Click me",
103///     "Popover title",
104///     "And here's some content.",
105///     Placement::Right,
106/// );
107/// assert!(btn.render().contains("data-bs-toggle=\"popover\""));
108/// ```
109#[must_use]
110pub fn popover_button(
111    color: crate::Color,
112    text: &str,
113    title: &str,
114    content: &str,
115    placement: Placement,
116) -> Element<Button> {
117    let class = format!("btn btn-{}", color.as_str());
118    Element::<Button>::new()
119        .attr("type", "button")
120        .class(&class)
121        .attr("data-bs-toggle", "popover")
122        .attr("data-bs-placement", placement.as_str())
123        .attr("data-bs-title", title)
124        .attr("data-bs-content", content)
125        .text(text)
126}
127
128/// Create a dismissible popover button (click to open, click again to close).
129#[must_use]
130pub fn popover_dismissible(
131    color: crate::Color,
132    text: &str,
133    title: &str,
134    content: &str,
135) -> Element<A> {
136    let class = format!("btn btn-{}", color.as_str());
137    Element::<A>::new()
138        .attr("tabindex", "0")
139        .class(&class)
140        .attr("role", "button")
141        .attr("data-bs-toggle", "popover")
142        .attr("data-bs-trigger", "focus")
143        .attr("data-bs-title", title)
144        .attr("data-bs-content", content)
145        .text(text)
146}
147
148/// Create a popover with HTML content.
149#[must_use]
150pub fn popover_html(
151    color: crate::Color,
152    text: &str,
153    title: &str,
154    content_html: &str,
155    placement: Placement,
156) -> Element<Button> {
157    let class = format!("btn btn-{}", color.as_str());
158    Element::<Button>::new()
159        .attr("type", "button")
160        .class(&class)
161        .attr("data-bs-toggle", "popover")
162        .attr("data-bs-placement", placement.as_str())
163        .attr("data-bs-html", "true")
164        .attr("data-bs-title", title)
165        .attr("data-bs-content", content_html)
166        .text(text)
167}
168
169/// Create a link that triggers a popover on hover.
170#[must_use]
171pub fn popover_hover(href: &str, text: &str, title: &str, content: &str) -> Element<A> {
172    Element::<A>::new()
173        .attr("href", href)
174        .attr("data-bs-toggle", "popover")
175        .attr("data-bs-trigger", "hover focus")
176        .attr("data-bs-title", title)
177        .attr("data-bs-content", content)
178        .text(text)
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_tooltip_button() {
187        let btn = tooltip_button(
188            crate::Color::Primary,
189            "Hover",
190            "Tooltip text",
191            Placement::Top,
192        );
193        let html = btn.render();
194        assert!(html.contains(r#"data-bs-toggle="tooltip"#));
195        assert!(html.contains(r#"data-bs-placement="top"#));
196        assert!(html.contains("data-bs-title"));
197    }
198
199    #[test]
200    fn test_popover_button() {
201        let btn = popover_button(
202            crate::Color::Secondary,
203            "Click",
204            "Title",
205            "Content",
206            Placement::Right,
207        );
208        let html = btn.render();
209        assert!(html.contains(r#"data-bs-toggle="popover"#));
210        assert!(html.contains(r#"data-bs-placement="right"#));
211        assert!(html.contains("data-bs-title"));
212        assert!(html.contains("data-bs-content"));
213    }
214
215    #[test]
216    fn test_tooltip_placements() {
217        for placement in [
218            Placement::Top,
219            Placement::Right,
220            Placement::Bottom,
221            Placement::Left,
222        ] {
223            let btn = tooltip_button(crate::Color::Info, "Test", "Tip", placement);
224            assert!(btn
225                .render()
226                .contains(&format!(r#"data-bs-placement="{}""#, placement.as_str())));
227        }
228    }
229}