Tucano v0.37.2

Pestañas

La plantilla ya trae la lista dibujada, la pestaña inicial marcada y los demás paneles con hidden — la página nace correcta antes del script. El JavaScript pone lo que el HTML no da: los roles que hacen que el lector de pantalla anuncie "pestaña, 2 de 4", la conexión entre pestaña y panel y el teclado, en el que la lista es una sola parada del Tab.

Ejemplos

El mismo marcado, con una clase o un atributo más.

Segmentadas

is-segmented tiene la altura de un campo, para cambiar la vista de una lista junto a un filtro.

128 contratos.

Segmentadas junto a un campo

Borde y relleno suman la altura del control: la fila no se desalinea.

Activación manual

data-manual="true": las flechas solo mueven el foco, y Enter o Espacio abren.

Aquí iría el panel cargado por HTMX.

Con icono y contador

El svg y la .tuc-badge is-plain dentro de la pestaña siguen su color.

Vista general del proyecto.

Muchas pestañas

La lista se desplaza en horizontal en lugar de partirse en dos líneas; la pestaña enfocada con las flechas entra en pantalla.

Agosto — la pestaña marcada en la plantilla.

Pestaña desactivada

disabled o aria-disabled="true": no se abre, y las flechas la saltan.

En edición.

Cómo usar

Escribe las clases, aria-selected="true" en la pestaña inicial y hidden en los demás paneles. Las pestañas y los paneles se emparejan por orden: la tercera pestaña abre el tercer panel.

<div class="tuc-tabs" data-tuc-tabs>
  <div class="tuc-tabs__list">
    <button class="tuc-tabs__tab" aria-selected="true">Datos</button>
    <button class="tuc-tabs__tab">Dirección</button>
  </div>
  <div class="tuc-tabs__panel">…</div>
  <div class="tuc-tabs__panel" hidden>…</div>
</div>

Sin aria-selected en ninguna, se abre la primera pestaña activa. El script da type="button" a las pestañas que no tienen type — sin eso, cambiar de pestaña dentro de un <form> enviaría el formulario —, crea los id que faltan y conecta pestaña y panel mediante aria-controls y aria-labelledby.

Por qué las clases vienen de la plantilla, y no del script

Así la lista ya nace dibujada en el primer fotograma, sin bloque de espera y sin salto cuando llega el JavaScript. Por el mismo motivo el hidden de los paneles es obligatorio: sin él, todos aparecerían apilados hasta que se ejecute el script.

Con HTMX, panel bajo demanda

Para no cargar el contenido de todas las pestañas a la vez, cada pestaña busca su propio panel la primera vez que se abre. Activa data-manual="true": en el modo manual, abrir es siempre un clic — de ratón, de Enter o de Espacio —, así que el hx-trigger en la pestaña cubre todos los caminos.

<div class="tuc-tabs" data-tuc-tabs data-manual="true">
  <div class="tuc-tabs__list">
    <button class="tuc-tabs__tab" aria-selected="true">Resumen</button>
    <button class="tuc-tabs__tab" hx-get="{% url 'customer-invoices' customer.pk %}"
            hx-target="#invoices" hx-trigger="click once">Facturas</button>
  </div>
  <div class="tuc-tabs__panel">{% include "customers/summary.html" %}</div>
  <div class="tuc-tabs__panel" id="invoices" hidden></div>
</div>

Por qué existe el modo manual

En la activación automática, la flecha cambia el panel al instante. Con paneles que cargan del servidor, recorrer cuatro pestañas hasta la deseada sería disparar cuatro peticiones. En el manual la flecha solo lleva el foco, y quien decide abrir es la persona.

En JavaScript

const tabs = new Tucano.Tabs('#customer', {
  selected: 1,        // índice de la pestaña inicial
  manual: true,
  onChange: (index, { tab, panel }) => console.log(index, tab.textContent),
});

tabs.select(2);                     // abre la tercera; dispara onChange
tabs.select(0, { silent: true });   // abre sin disparar nada
tabs.index;                         // índice de la pestaña abierta, o -1
tabs.tabs;                          // las pestañas, en orden
tabs.panels;                        // los paneles, en orden

Cada cambio — por la persona o por select() — dispara tucano:change en el elemento de las pestañas, y el evento burbujea. select() en una pestaña desactivada no hace nada.

document.addEventListener('tucano:change', (e) => {
  if (!e.target.matches('.tuc-tabs')) return;
  e.detail.value;      // índice de la pestaña abierta
  e.detail.tab;        // el botón de la pestaña
  e.detail.panel;      // el panel
  e.detail.instance;   // las Tabs
});

Las pestañas dentro de un panel son otro conjunto: el componente solo mira la lista y los paneles que son hijos directos de su elemento.

Teclado y accesibilidad

El teclado sigue el patrón de pestañas del ARIA APG. Toda la lista es una sola parada del Tab — si no, quien navega con teclado recorrería todas las pestañas antes de llegar al contenido de la abierta.

TeclaAcción
TabEntra en la pestaña abierta; el siguiente Tab va al contenido del panel
← →Pestaña anterior y siguiente, saltando las desactivadas y dando la vuelta en los extremos
Home EndPrimera y última pestaña activas
Enter EspacioEn el modo manual, abre la pestaña enfocada

La lista recibe role="tablist", cada pestaña role="tab" con aria-selected, y cada panel role="tabpanel". El panel sin nada enfocable dentro recibe tabindex="0", para que el Tab llegue al contenido; con un campo o un enlace dentro, no — sería una parada inútil antes de él.

API

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

Marcado
[data-tuc-tabs]
En JS
new Tucano.Tabs(alvo, opcoes)
Atributos
data-manual
Métodos
select destroy
Eventos
tucano:change

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é
selectednullindice da aba inicial; sem ele vale a marcada com aria-selected="true", ou a primeira
manualfalsesetas so movem o foco, e Enter ou Espaco trocam o painel — para painel que carrega por HTMX
onChangenull(index, detail) a cada troca feita pela pessoa ou por select()