Capas
Estado: BetaTooltip direccional
Para nombrar los botones de ícono del admin, explicar una acción o mostrar el dato que no cupo. Nace pegado a su disparador y se aleja con resorte hacia su lado; la primera vez espera un momento y, al recorrer una barra de botones, el siguiente sale de inmediato. Flota en la capa superior y se voltea si no cabe.
- Resorte
- Capa superior
- Calentamiento
Exportar CSV⌘E
Duplicar semana
Pasar lista de Vinyasa Flow · 7:00C
Regina Castro Fernández
regina.castro.fernandez@lunaestudio.mx
regina.castro.fernandez@lunaestudio.mx
Exportar CSV⌘E
Duplicar semana
Pasar lista de Vinyasa Flow · 7:00C
Regina Castro Fernández
regina.castro.fernandez@lunaestudio.mx
regina.castro.fernandez@lunaestudio.mxapp/views/… (la demo de arriba)
<% icon_button = "grid size-9 place-items-center rounded-lg text-muted-foreground transition-colors duration-quick hover:bg-surface-hover hover:text-foreground eui-press eui-focus" %>
<div class="flex w-full max-w-xs flex-col items-center gap-7">
<%# Botones de ícono del horario del admin: el aria-label nombra el botón y
el tooltip lo muestra (no se lee dos veces). %>
<div class="flex items-center gap-0.5 rounded-xl border border-border bg-card p-1 shadow-subtle" role="group" aria-label="Acciones del horario">
<%= render Ui::TooltipComponent.new(text: "Exportar CSV", shortcut: [ "⌘", "E" ]) do %>
<button type="button" class="<%= icon_button %>" aria-label="Exportar CSV">
<%= render Ui::IconComponent.new(:download) %>
</button>
<% end %>
<%# Enganchado por selector: el botón no cambia, el tooltip lo busca. %>
<button type="button" class="<%= icon_button %>" aria-label="Duplicar semana" data-tooltip-demo="duplicar">
<%= render Ui::IconComponent.new(:copy) %>
</button>
<%= render Ui::TooltipComponent.new(text: "Duplicar semana", side: :bottom, selector: "[data-tooltip-demo=duplicar]") %>
<span class="mx-1 h-5 w-px bg-border" aria-hidden="true"></span>
<%= render Ui::TooltipComponent.new(text: "Pasar lista de Vinyasa Flow · 7:00", shortcut: "C") do %>
<button type="button" class="<%= icon_button %>" aria-label="Check-in">
<%= render Ui::IconComponent.new(:user_check) %>
</button>
<% end %>
</div>
<%# Un dato que no cabe: el tooltip solo aparece si de verdad está recortado. %>
<div class="flex w-full items-center gap-3 rounded-xl border border-border bg-card px-3.5 py-3">
<span class="grid size-9 shrink-0 place-items-center rounded-full border border-border-subtle bg-raised text-2xs font-medium text-muted-foreground" aria-hidden="true">RC</span>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-foreground">Regina Castro Fernández</p>
<%= render Ui::TooltipComponent.new(text: "regina.castro.fernandez@lunaestudio.mx", side: :bottom, align: :start, truncated_only: true) do %>
<p class="truncate rounded-sm font-mono text-2xs text-muted-foreground eui-focus" tabindex="0">regina.castro.fernandez@lunaestudio.mx</p>
<% end %>
</div>
<span class="shrink-0 font-mono text-xs tabular-nums text-muted-foreground">4/10</span>
</div>
</div>Qué hace
- Calentamiento compartido - el primero espera 400 ms; mientras uno está abierto, o medio segundo después, el siguiente sale ya y con menos recorrido.
- Entra con el resorte snappy desde el lado del ancla (data-side) y crece desde --eui-origin; sale con una transición breve.
- Popover manual en la capa superior - no lo recorta un overflow y no cierra los menús abiertos.
- La flecha sigue apuntando al disparador aunque la burbuja se recorra para no salirse de la pantalla.
Props
| Nombre | Tipo | Default | Descripción |
|---|---|---|---|
| text | String | (obligatorio) | Lo que dice el tooltip. |
| side | :top | :bottom | :left | :right | :top | Lado preferido; se voltea si no cabe y entra desde ahí. |
| align | :start | :center | :end | :center | Alineación sobre ese lado. |
| shortcut | String | Array | — | Atajo en <kbd> mono ("⌘E" o ["⌘", "E"]). Solo visual: el atajo lo implementa la vista. |
| selector | String (CSS) | — | Engancha el tooltip a otro elemento en vez de envolverlo; se busca primero cerca del componente. |
| delay | Integer (ms) | 400 | Espera la primera vez; en caliente sale de inmediato. |
| arrow | Boolean | true | Flecha que apunta al centro del disparador. |
| truncated_only | Boolean | false | Solo aparece si el disparador está recortado (truncate). |
| content | bloque | — | El disparador: su primer elemento (un botón, un enlace o un texto con tabindex=0). |
Accesibilidad
- role=tooltip y aria-describedby en el disparador; si el texto repite su aria-label (botón de ícono) no se duplica, y si no tiene nombre lo nombra con aria-labelledby.
- Aparece con el cursor y con el foco del teclado (:focus-visible); al tocar en el teléfono no, así que el botón debe tener su nombre.
- Se oculta con Esc (sin cerrar el diálogo de abajo), al salir, al presionar y al desplazar; puedes pasar el cursor encima sin que se vaya (WCAG 1.4.13).
- Los botones de ícono siguen necesitando aria-label - el tooltip es la versión visible, no el nombre.