Tucano v0.37.2

Panel lateral

Panel que entra por un borde y ocupa todo su eje: filtros de una lista, menú en el móvil, carrito, detalle de un registro. Comparte con el modal la mecánica del <dialog> — lo que cambia es la geometría y el movimiento: en el centro el diálogo crece, en el borde se desliza, porque así el panel dice de dónde vino.

Filtros

Afina la lista de contratos.

Estado

Ejemplos

Todos los de abajo son el mismo Tucano.drawer() con opciones distintas.

Los cuatro lados

side elige el borde; right es el predeterminado.

Ancho de la columna

size en los laterales: 18, 24 o 34rem.

Tonos

tone cambia el brillo, que nace del borde en el que se apoya el panel.

Sin cerrar desde fuera

closeOnBackdrop: false y closable: false, como en el modal.

Contenido libre

content(nodo) rellena el cuerpo, que se desplaza cuando el contenido supera la altura.

Menú de aplicación

Panel por la izquierda con la lista de navegación dentro — el ejemplo completo está en el menú lateral.

Cómo usar

Los mismos dos caminos del modal. Desde JavaScript el panel se monta en el momento y sale del DOM al cerrarse; desde la plantilla ya está en la página, y el componente solo lo abre, lo cierra y lo anima.

En JavaScript

Tucano.drawer({
  title: 'Filtros',
  text: 'Afina la lista de contratos.',
  side: 'right',      // left | right | top | bottom
  size: 'md',         // sm | md | lg — en los laterales, ancho de la columna
  tone: 'default',    // default | danger | success | warning
  actions: [
    { text: 'Limpiar', variant: 'ghost' },
    { text: 'Aplicar', variant: 'primary', onClick: (drawer) => apply() },
  ],
}).content(form);

// Montado antes, abierto después
const drawer = new Tucano.Drawer({ title: 'Carrito', side: 'right' });
drawer.content(list).open();
drawer.close();

actions sigue las mismas reglas del modal: text, variant (predeterminado outline), onClick, que recibe la instancia, y closes: false para mantenerlo abierto. onClose(reason, drawer) recibe 'button', 'action', 'escape', 'backdrop' o 'api'.

En la plantilla de Django

El caso típico es el formulario de filtros de una lista: lo renderiza el servidor, con los valores que llegaron en request.GET, y el panel solo lo oculta hasta que alguien lo pida.

<dialog class="tuc-drawer is-right is-md" id="filters" aria-labelledby="filters-title">
  <form class="tuc-drawer__panel" method="get">
    <div class="tuc-drawer__top">
      <div class="tuc-drawer__header">
        <h2 class="tuc-drawer__title" id="filters-title">Filtros</h2>
      </div>
      <button type="button" class="tuc-btn is-ghost is-icon is-sm tuc-drawer__close" aria-label="Cerrar" data-tuc-drawer-close>…</button>
    </div>
    <div class="tuc-drawer__body">
      {{ form.as_div }}
    </div>
    <div class="tuc-drawer__footer">
      <a class="tuc-btn is-ghost" href="?">Limpiar</a>
      <button class="tuc-btn is-primary">Aplicar</button>
    </div>
  </form>
</dialog>

<button class="tuc-btn is-outline" data-tuc-drawer="#filters">Filtros</button>
AtributoDóndePara qué
data-tuc-drawer="#id"DisparadorAbre el panel del selector al hacer clic
data-tuc-drawer-closeBotón dentro del panelCierra, con el motivo 'button'
data-closable="false"<dialog>Esc deja de cerrar
data-backdrop="false"<dialog>El clic en el fondo deja de cerrar

Todo dialog.tuc-drawer se adopta al cargar y en cada htmx:afterSwap. Las clases internas siguen el nombre del componente: __panel, __top, __header, __title, __text, __close, __body y __footer. En la plantilla, escribe la X y el aria-labelledby: desde JavaScript los dos salen listos, a mano no.

Lados y tamaños

La caja se estira en el eje del borde en el que se apoya — en los laterales ocupa toda la altura, arriba y abajo todo el ancho — y el borde de la caja queda solo del lado que toca la página.

sideClaseEntra desdeQué controla size
rightis-rightDerecha — el predeterminadoAncho de la columna
leftis-leftIzquierdaAncho de la columna
topis-topArribaNada: el ancho es el de la pantalla y la altura, la del contenido
bottomis-bottomAbajoNada: el ancho es el de la pantalla y la altura, la del contenido
sizeAncho en los laterales
sm18rem
md24rem — el predeterminado
lg34rem

El panel lateral es una columna, no una tarjeta, y por eso su escala es distinta a la del modal. En el móvil (por debajo de 40rem) se queda en min(20rem, 85vw) sea cual sea el tamaño: una columna estrecha ahí no se lee, y la franja que sobra muestra que la página sigue detrás. Los botones del pie ocupan la fila, como en el modal.

El brillo nace del borde

En el modal la luz viene del centro de la pantalla. En el panel lateral sale del lado en el que se apoya, y la elipse es mayor: al nacer en el borde, solo la mitad queda en pantalla. Centrada, la luz apuntaría al medio de la página mientras el panel entra por el lateral.

Teclado y accesibilidad

Es un <dialog> abierto con showModal(), igual que el modal: la página de detrás queda inerte, el foco no escapa y vuelve a quien lo abrió al cerrarse.

TeclaAcción
Tab Shift+TabRecorre los controles del panel, sin volver a la página
Enter EspacioActiva el botón con foco
EscCierra con animación y devuelve el foco; no hace nada con closable: false

Comparte el motor con el modal

Top layer, foco atrapado, Esc y foco devuelto viven en una sola base, y no en dos implementaciones parecidas. Eso es lo que impide que una corrección hecha en uno deje de valer en el otro.

Con title, el panel recibe aria-labelledby. La X es un .tuc-btn con aria-label="Fechar". Con prefers-reduced-motion, la entrada dura 100 ms y no se desliza.

API

Generada a partir del código en cada build — si algo no está aquí, no existe.

Marcado
dialog.tuc-drawer [data-tuc-drawer-close] [data-tuc-drawer]
En JS
new Tucano.Drawer(opcoes) Tucano.drawer()
Atributos
data-backdrop data-closable data-tuc-drawer
Métodos
open close content

Opciones

Las notas de esta tabla salen de los comentarios del código fuente, que están escritos en portugués.

OpciónPor defectoPara qué
titlenull
text''
side'right'left | right | top | bottom
size'md'sm | md | lg — nas laterais, largura da coluna
tone'default'default | danger | success | warning
closabletrue
closeOnBackdroptrue
actionsnull[{ text, variant, onClick, closes }] — closes:false mantem aberto
onClosenull
className''