Tucano v0.37.2

Toast

Aviso pasajero en una esquina de la pantalla, para lo que acaba de pasar: guardado, enviado, no salió bien. Se apila, se pausa cuando el puntero o el foco entra y lo anuncia el lector de pantalla. Para lo que sigue vigente mientras la persona está en la pantalla, usa el aviso.

Ejemplos

Cada tipo tiene color, icono y duración propios. El error se queda más tiempo porque la persona necesita leerlo y, muchas veces, actuar; el cargando no se cierra solo, porque quien lo termina es el fin de la operación.

Tipos

info, success, warning, error y loading.

No se cierra solo

duration: null lo deja en la pantalla hasta que alguien haga clic en la X.

Duración propia

duration en milisegundos, en lugar de la predeterminada del tipo.

Pila

Hasta tres a la vista, apilados; el puntero o el foco los abre en abanico. El quinto cierra el más antiguo.

TipoSe queda en pantallaSe anuncia como
info4 srole="status", región cortés
success3,5 srole="status", región cortés
warning6 srole="status", región cortés
error8 srole="alert", región asertiva
loadinghasta el fin de la operaciónrole="status", región cortés

En el toast el rojo es el tipo error. En las clases del aviso y de la etiqueta, el mismo tono se llama is-danger.

Cómo usar

No hay nada que marcar en el HTML: el toast se llama cuando algo pasa. El contenedor de cada posición se crea la primera vez que se usa.

Tucano.toast('Guardado');                       // info
Tucano.toast.success('Registro guardado');
Tucano.toast.error('No se pudo guardar', { title: 'Error 500' });

Tucano.toast({
  type: 'success',
  title: 'Contrato eliminado',
  text: 'Todavía se puede deshacer.',
  action: { text: 'Deshacer', onClick: (t) => restore() },
  position: 'bottom-end',
  duration: null,       // no se cierra solo
  closable: true,       // la X
});

Los atajos info, success, warning, error y loading reciben el texto y, después, cualquier otra opción. La acción es un botón del sistema: llama a onClick con el toast y se cierra. Todo devuelve la instancia, que es lo mismo que new Tucano.Toast({ ... }) — este recibe solo las opciones, sin objetivo.

const t = Tucano.toast('Exportando...');
t.close();

// El evento sale de la propia tarjeta después de que deja la pantalla; no se propaga.
t.node.addEventListener('tucano:toast-closed', () => console.log('cerrado'));

Cargando y promesa

El toast de loading se convierte en el resultado en la misma tarjeta, en lugar de cerrar uno y abrir otro: la pila no se reorganiza y la vista no pierde el aviso que ya estaba leyendo.

// Control manual: guarda la instancia y cambia lo que necesites.
const t = Tucano.toast.loading('Guardando contrato...');
await save();
t.update({ type: 'success', text: 'Contrato guardado' });

// O entrega la promesa y deja los tres estados en sus manos.
Tucano.toast.promise(fetch(url), {
  loading: 'Enviando...',
  success: (response) => `Enviado (${response.status})`,
  error: 'No se pudo enviar',
  position: 'top-center',       // el resto vale para el toast
});

update() cambia tipo, título, texto y acción sin recrear la tarjeta. Al cambiar de tipo sin duration, vale la duración del tipo nuevo, y el reloj vuelve a empezar: sin eso el cargando, que no se cierra, se convertiría en un "guardado" eterno en la pantalla.

En toast.promise(), success y error aceptan texto o una función que recibe el resultado o el error. Sin ellos, se usan los textos por defecto de la biblioteca: "Carregando...", "Pronto" y "Algo deu errado" en portugués, o los que definas para toda la página con Tucano.setTexts({ toast: { loading, success, error } }). La función devuelve la misma promesa que recibió, para no estorbar a quien ya encadenaba sobre ella — si se rechaza, sigue rechazada, y el error se trata donde ya se trataría.

null quiere decir "no se cierra", y no "usa el valor predeterminado"

La duración predeterminada se resuelve con in, y no con ??. El null del cargando es un valor: con ?? caería en los 4 segundos predeterminados, y el toast desaparecería en medio de la operación.

Posiciones

Seis esquinas y centros, con bottom-end como predeterminada. Cada posición tiene su propia pila.

En el móvil, por debajo de 40rem, el toast ocupa el ancho de la pantalla y entra por el eje vertical, sea cual sea la posición: pegado a una esquina quedaba con márgenes distintos a cada lado, y deslizar en horizontal una tarjeta tan ancha como la pantalla es un recorrido demasiado largo para un aviso corto. El borde respeta el indicador de inicio del iPhone y el recorte de la cámara.

max limita los toasts abiertos en la misma posición, 4 por defecto; el que se pasa cierra el más antiguo. En la pila, a partir del tercero desaparecen de la vista, porque una pila de diez no ayuda a nadie.

Mensajes de Django

El framework de messages se convierte en toast sin una línea de JavaScript. Pon el bucle una vez, en la plantilla base: cada <div> se convierte en un toast y sale de la página.

{% for m in messages %}
  <div data-tuc-toast data-type="{{ m.level_tag }}">{{ m }}</div>
{% endfor %}
messages.success(request, "Contrato guardado")
return redirect("contracts")
AtributoPara qué
data-tuc-toastMarca el elemento; su texto se convierte en el texto del toast
data-typeEl tipo. debug se convierte en info; info, success, warning y error pasan tal cual
data-titleTítulo
data-textTexto, en lugar del contenido del elemento
data-durationMilisegundos; false no se cierra solo
data-positionUna de las seis posiciones

m.level_tag, y no m.tags, si usas extra_tags

Del data-type vale solo la primera palabra. El m.tags de Django escribe las extra_tags antes del nivel, y entonces la primera palabra deja de ser el tipo. Sin extra_tags, los dos dan lo mismo.

Por la cabecera de HTMX

En una respuesta de HTMX no hay redirect ni plantilla base para el bucle de mensajes. El servidor dispara el toast por la cabecera HX-Trigger, y el script del CDN ya está escuchando.

import json

def save(request, pk):
    ...
    return HttpResponse(headers={
        "HX-Trigger": json.dumps({
            "tucano:toast": {"type": "success", "text": "Contrato guardado"},
        }),
    })

El detalle acepta cualquier opción del toast, o solo un texto: {"tucano:toast": "Guardado"}. HTMX dispara el evento tucano:toast en el elemento de la petición, este sube hasta el <body>, y ahí se convierte en toast. El mismo evento sirve desde cualquier otro código:

document.body.dispatchEvent(new CustomEvent('tucano:toast', {
  detail: { type: 'success', text: 'Contrato guardado' },
}));

Por el CDN, listenForEvents() se activa junto con la inicialización. Importando tucano desde npm nada se ejecuta solo: llama a listenForEvents(), o importa tucano/auto.

Accesibilidad

El toast no roba el foco: avisa sin sacar a la persona de lo que estaba haciendo. Quien lo anuncia es el lector de pantalla.

La región se crea antes del mensaje

El aria-live está en el contenedor, y no en el toast. Una región viva tiene que existir en el DOM antes de que llegue el contenido; creada junto con el mensaje, el lector de pantalla no la anuncia. El error habla en una región assertive y el resto en una polite, y las dos comparten el mismo escenario: posicionadas cada una por su cuenta, se volverían dos pilas paralelas en la pantalla y el límite contaría el doble.

Cuando update() cambia un toast a error, o de error a otro tipo, este cambia de región, y eso es lo que hace que el lector de pantalla anuncie el cambio. En la pantalla nada se mueve, porque la posición viene del escenario.

El reloj se pausa cuando el puntero entra en el toast o algo dentro de él recibe el foco: nadie consigue leer algo que desaparece mientras intenta hacer clic en "Deshacer". Por el mismo motivo la pila se abre en abanico también con el foco, y no solo con el puntero. La X es un botón de verdad, con aria-label="Fechar" (la etiqueta por defecto de la biblioteca, en portugués hasta que la cambies con Tucano.setTexts({ toast: { close } })). Con prefers-reduced-motion, entrar y salir se reducen a un fundido.

API

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

Marcado
[data-tuc-toast]
En JS
new Tucano.Toast(opcoes) Tucano.toast() Tucano.listenForEvents()
Atributos
data-duration data-position data-text data-title data-type
Métodos
update close
Eventos
tucano:toast-closed

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é
type'info''info' | 'success' | 'warning' | 'error' | 'loading'
titlenull
text''
durationundefinedms. null nao fecha sozinho. Padrao depende do tipo
position'bottom-end'top-start|top-center|top-end|bottom-start|bottom-center|bottom-end
closabletrue
actionnull{ text, onClick }
max4toasts simultaneos na mesma posicao