Documentación

Overlays

0.2.0 añade una capa completa de overlays: diálogos, drawers, menús, popovers, menús contextuales y tooltips. Todos viven tras @yoltra/ds/client (necesitan APIs del navegador y se portan a document.body) y cada uno tiene su propia hoja de estilo.

#Por qué se portan

Dialog, Drawer y las superficies ancladas se renderizan a través de Portal en un nodo bajo document.body, no donde se escriben. Renderizar en su sitio pierde contra el CSS de tres formas, y ningún z-index arregla ninguna: un ancestro con overflow: hidden recorta el panel; un ancestro con transform/filter se vuelve el bloque contenedor de position: fixed; y un ancestro que estableció un contexto de apilamiento atrapa el overlay por debajo de lo que haya encima de ese ancestro.

Portarlo al body deja el overlay compitiendo solo con el orden de apilamiento del propio documento —los tokens --yl-z-*—. Su orden codifica la contención: un popover abierto dentro de un diálogo va encima de él, y un tooltip encima de ambos.

#La capa modal — Dialog, Drawer

Ambos son controlados: open y onClose son todo el contrato de estado, así la superficie nunca discrepa con tu app sobre si está visible. Traen el comportamiento que hace un overlay usable y no solo visible —foco atrapado dentro y restaurado al cerrar, scroll de la página bloqueado (con recuento de referencias) y cierre por Escape / clic fuera que se apila (un Escape cierra el menú, no el diálogo de detrás)—.

title es obligatorio: un modal sin nombre accesible se anuncia como "diálogo" y nada más. Envuélvelo en VisuallyHidden si el diseño no lleva encabezado visible.

tsx
import { Dialog } from "@yoltra/ds/client";
import "@yoltra/ds/styles/modal.css";

<Dialog open={open} onClose={close} title="Buscar" size="lg">
  <input type="search" placeholder="Busca en la documentación…" />
</Dialog>;

Drawer es la misma maquinaria anclada a un borde —side="left" | "right" | "top" | "bottom" y un size (cualquier longitud CSS)—. Úsalo cuando el contenido es una lista o un formulario largo para el que una caja centrada necesitaría igualmente su propia barra de scroll.

#La capa anclada — Menu, Popover, ContextMenu, Tooltip

Se posicionan respecto a un disparador (o, en ContextMenu, a un punto). No son modales: sin foco atrapado ni bloqueo de scroll; se cierran con Escape, con un clic fuera y cuando el foco sale. La render prop trigger te entrega el cableado ARIA (aria-expanded, aria-haspopup, aria-controls) y la ref contra la que anclar —espárcelo; el estado de apertura sigue siendo tuyo—.

tsx
import { Menu, MenuItem } from "@yoltra/ds/client";
import "@yoltra/ds/styles/popover.css";

<Menu
  open={open}
  onClose={close}
  label="Idioma"
  trigger={(props) => (
    <Button {...props} onClick={() => setOpen((v) => !v)}>ES</Button>
  )}
>
  <MenuItem onSelect={() => setLang("en")}>English</MenuItem>
  <MenuItem onSelect={() => setLang("es")}>Español</MenuItem>
</Menu>;

Menu implementa el patrón de teclado completo (flechas con envolvente, Home/End, Enter/Espacio, Tab para cerrar). Popover es la misma cáscara para controles arbitrarios en vez de una lista de comandos. Tooltip es la excepción: no controlado, porque su visibilidad pertenece al puntero y al anillo de foco, y cableado con aria-describedby (complementa el nombre de un control, no lo sustituye):

tsx
import { Tooltip } from "@yoltra/ds/client";
import "@yoltra/ds/styles/tooltip.css";

<Tooltip content="Cambiar tema">
  {(props) => <IconButton {...props} label="Cambiar tema">🌙</IconButton>}
</Tooltip>;

Cada overlay necesita su hoja de estilo —modal.css para Dialog/Drawer, popover.css para Menu/Popover/ContextMenu, tooltip.css para Tooltip— además de los siempre obligatorios tokens.css y base.css. Consulta la referencia de la API para cada prop.