Skip to main content

stratum_components/overlay/
tooltip.rs

1//! Styled Tooltip component.
2
3use crate::common::merge_classes;
4use stratum_core::aria::{AriaAttributes, AriaRole};
5use stratum_core::render::{AttrValue, RenderOutput};
6
7/// Side the tooltip appears on.
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
9pub enum TooltipSide {
10    #[default]
11    Top,
12    Right,
13    Bottom,
14    Left,
15}
16
17/// Properties for the styled Tooltip.
18#[derive(Debug, Clone, PartialEq, Default)]
19pub struct TooltipProps {
20    pub side: TooltipSide,
21    pub open: bool,
22    pub class: Option<String>,
23    pub content: String,
24    pub id: Option<String>,
25}
26
27pub struct Tooltip;
28
29impl Tooltip {
30    const BASE: &'static str = "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95";
31
32    pub fn classes(props: &TooltipProps) -> String {
33        let side_cls = match props.side {
34            TooltipSide::Top => "slide-in-from-bottom-2",
35            TooltipSide::Right => "slide-in-from-left-2",
36            TooltipSide::Bottom => "slide-in-from-top-2",
37            TooltipSide::Left => "slide-in-from-right-2",
38        };
39
40        let computed = format!("{} {}", Self::BASE, side_cls);
41        merge_classes(&computed, &props.class)
42    }
43
44    pub fn render(props: &TooltipProps) -> RenderOutput {
45        let classes = Self::classes(props);
46        let aria = AriaAttributes::new().with_role(AriaRole::ToolTip);
47
48        let mut output = RenderOutput::new()
49            .with_tag("div")
50            .with_class(classes)
51            .with_aria(aria)
52            .with_data("state", if props.open { "open" } else { "closed" })
53            .with_data(
54                "side",
55                match props.side {
56                    TooltipSide::Top => "top",
57                    TooltipSide::Right => "right",
58                    TooltipSide::Bottom => "bottom",
59                    TooltipSide::Left => "left",
60                },
61            );
62
63        if let Some(ref id) = props.id {
64            output = output.with_attr("id", AttrValue::String(id.clone()));
65        }
66
67        output
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn tooltip_default_classes() {
77        let props = TooltipProps::default();
78        let classes = Tooltip::classes(&props);
79        assert!(classes.contains("rounded-md"));
80        assert!(classes.contains("slide-in-from-bottom-2"));
81    }
82
83    #[test]
84    fn tooltip_side_classes() {
85        let props = TooltipProps {
86            side: TooltipSide::Left,
87            ..Default::default()
88        };
89        let classes = Tooltip::classes(&props);
90        assert!(classes.contains("slide-in-from-right-2"));
91    }
92
93    #[test]
94    fn tooltip_render_role() {
95        let props = TooltipProps::default();
96        let output = Tooltip::render(&props);
97        assert_eq!(output.aria.role, Some(AriaRole::ToolTip));
98    }
99}