Coverage for src / lexigram / ui / atoms / tooltip.py: 100%
15 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1from __future__ import annotations
3from typing import Any
5from lexigram.ui.core.base import Component, el
8class Tooltip(Component):
9 """Tooltip component with auto-positioning and ARIA accessibility support.
11 The tooltip element receives ``role="tooltip"`` and a stable ``id``.
12 If a trigger element is wired via ``trigger_id``, the wrapper element
13 receives ``aria-describedby`` pointing to the tooltip's ``id``.
15 Args:
16 content: The tooltip text shown on hover.
17 position: Visual position hint (default: "top").
18 tooltip_id: Explicit ``id`` for the tooltip span. Defaults to a
19 generated value so ``aria-describedby`` always resolves.
20 trigger_id: Optional ``id`` of the associated trigger element.
21 """
23 def __init__(
24 self,
25 content: str,
26 position: str = "top",
27 tooltip_id: str | None = None,
28 trigger_id: str | None = None,
29 **props,
30 ) -> None:
31 super().__init__(content=content, position=position, **props)
32 self.content = content
33 self.position = position
34 self.tooltip_id = tooltip_id or f"tooltip-{id(self)}"
35 self.trigger_id = trigger_id
37 def render(self) -> Any:
38 wrapper_attrs: dict[str, Any] = {
39 "class_": "group relative flex justify-center",
40 }
41 if not self.trigger_id:
42 wrapper_attrs["aria_describedby"] = self.tooltip_id
44 return el(
45 "div",
46 *self.children,
47 el(
48 "span",
49 self.content,
50 id=self.tooltip_id,
51 role="tooltip",
52 class_="absolute top-10 scale-0 transition-all rounded bg-popover p-2 text-xs text-popover-foreground group-hover:scale-100 z-50 whitespace-nowrap",
53 ),
54 **wrapper_attrs,
55 )