Tooltip source

Inspect and copy the reviewed source included with this Free component.

Free source · 4 files

recipe.rs

// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Tooltip recipe — semantic root placement and named slot classes.

use crate::theme::rounded;
use crate::tw_join;
use proa_core::shared::attr_value::AttrValue;
use proa_core::{DataLoader, WebContext, WriteBuf, WriteError};

/// Tooltip placement options.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, proa_macros::ProaVariant)]
#[variant(name = "side")]
pub enum TooltipSide {
    #[default]
    Top,
    Bottom,
    Left,
    Right,
}

impl TooltipSide {
    pub const fn classes(self) -> &'static str {
        ""
    }

    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Top => "top",
            Self::Bottom => "bottom",
            Self::Left => "left",
            Self::Right => "right",
        }
    }
}

/// Tooltip alignment relative to its trigger.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, proa_macros::ProaVariant)]
#[variant(name = "align")]
pub enum TooltipAlign {
    #[default]
    Center,
    Start,
    End,
}

impl TooltipAlign {
    pub const fn classes(self) -> &'static str {
        ""
    }

    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Center => "center",
            Self::Start => "start",
            Self::End => "end",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TooltipRecipeProps {
    pub side: Option<TooltipSide>,
    pub align: Option<TooltipAlign>,
}

/// Static named classes for every Tooltip part.
#[derive(Debug, Clone, Copy)]
pub struct TooltipRecipe {
    pub root: &'static str,
    pub trigger: &'static str,
    pub content: &'static str,
    side_default: TooltipSide,
    align_default: TooltipAlign,
}

impl TooltipRecipe {
    pub const fn resolve(&'static self, props: TooltipRecipeProps) -> TooltipStyle {
        let side = match props.side {
            Some(side) => side,
            None => self.side_default,
        };
        let align = match props.align {
            Some(align) => align,
            None => self.align_default,
        };
        TooltipStyle {
            recipe: self,
            side,
            align,
        }
    }
}

#[must_use]
#[derive(Debug, Clone, Copy)]
pub struct TooltipStyle {
    recipe: &'static TooltipRecipe,
    side: TooltipSide,
    align: TooltipAlign,
}

impl TooltipStyle {
    pub const fn side(self) -> TooltipSide {
        self.side
    }

    pub const fn align(self) -> TooltipAlign {
        self.align
    }
}

impl AttrValue for TooltipStyle {
    #[inline(always)]
    fn should_render(&self) -> bool {
        true
    }

    #[inline(always)]
    fn render_attr_value<B: WriteBuf, L: DataLoader>(
        &self,
        cx: &mut WebContext<L, B>,
    ) -> Result<(), WriteError> {
        cx.out.extend_static(self.recipe.root.as_bytes())
    }
}

const TOOLTIP_ROOT: &str = tw_join!("relative", "inline-block");

/// Trigger has no intrinsic visual treatment; its native button behavior is
/// preserved while the named slot remains available for additive classes.
const TOOLTIP_TRIGGER: &str = "";

/// `w-max` instead of `whitespace-nowrap`: the bubble is absolutely
/// positioned inside the trigger-sized root, so without an explicit sizing
/// basis it would shrink-wrap to the trigger's width. `max-content` keeps it
/// on one line when it fits, while the runtime caps `max-width` to the
/// viewport so long text wraps instead of overflowing the window.
pub const TOOLTIP_CONTENT_BASE: &str = tw_join!(
    "z-50",
    "overflow-hidden",
    "bg-gray-900",
    "text-gray-50",
    "px-3",
    "py-1.5",
    "text-xs",
    "shadow-md",
    "w-max",
    "break-words",
    rounded::MD
);

pub const TOOLTIP_RECIPE: TooltipRecipe = TooltipRecipe {
    root: TOOLTIP_ROOT,
    trigger: TOOLTIP_TRIGGER,
    content: TOOLTIP_CONTENT_BASE,
    side_default: TooltipSide::Top,
    align_default: TooltipAlign::Center,
};

pub const fn tooltip_recipe() -> &'static TooltipRecipe {
    &TOOLTIP_RECIPE
}

impl crate::variant_spec::ComponentSpec for TooltipRecipe {
    const NAME: &'static str = "Tooltip";

    fn base_classes(&self) -> &'static str {
        self.root
    }

    fn variant_metadata(&self) -> Vec<crate::variant_spec::VariantMetadata> {
        vec![
            crate::variant_spec::variant_metadata::<TooltipSide>(),
            crate::variant_spec::variant_metadata::<TooltipAlign>(),
        ]
    }

    fn anatomy(&self) -> Option<crate::Anatomy> {
        Some(super::TOOLTIP_ANATOMY)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn recipe_exposes_named_slots() {
        let recipe = tooltip_recipe();
        assert!(recipe.root.contains("relative"));
        assert_eq!(recipe.trigger, "");
        assert!(recipe.content.contains("bg-gray-900"));
    }

    #[test]
    fn recipe_resolves_defaults_and_overrides() {
        let default = tooltip_recipe().resolve(TooltipRecipeProps::default());
        assert_eq!(default.side(), TooltipSide::Top);
        assert_eq!(default.align(), TooltipAlign::Center);

        let custom = tooltip_recipe().resolve(TooltipRecipeProps {
            side: Some(TooltipSide::Right),
            align: Some(TooltipAlign::Start),
        });
        assert_eq!(custom.side(), TooltipSide::Right);
        assert_eq!(custom.align(), TooltipAlign::Start);
    }

    #[test]
    fn component_metadata_uses_canonical_anatomy_and_axes() {
        use crate::variant_spec::ComponentSpec;

        assert_eq!(
            TOOLTIP_RECIPE.anatomy(),
            Some(super::super::TOOLTIP_ANATOMY)
        );
        assert_eq!(TOOLTIP_RECIPE.variant_metadata().len(), 2);
    }
}

Copy this source with the Free Proa UI components it imports; those component files remain available from their own source pages.

Licensed under MIT OR Apache-2.0. Third-party notices remain attached to the installed payload.

Open Markdown
FreeTooltip

Short contextual help.

Install with the Proa CLI
proa ui add tooltip

Installs the reviewed component source, dependencies, shared support files, and required legal notices.

CLI and registry setup
recipe.rs
// SPDX-FileCopyrightText: 2026 Proa Labs, Inc.
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Tooltip recipe — semantic root placement and named slot classes.

use crate::theme::rounded;
use crate::tw_join;
use proa_core::shared::attr_value::AttrValue;
use proa_core::{DataLoader, WebContext, WriteBuf, WriteError};

/// Tooltip placement options.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, proa_macros::ProaVariant)]
#[variant(name = "side")]
pub enum TooltipSide {
    #[default]
    Top,
    Bottom,
    Left,
    Right,
}

impl TooltipSide {

Search

Type at least 2 characters

Customize

Make it yours

Palette

Radius

Overview
Components
Settings

Build your own

Themed Proa UI surface

Preview
Components52
RecipesTyped
customer-portal