docs/advanced/notifications
Capacidades · Notifier

Notificaciones

Usa el Notifier para notificaciones dentro de la ventana que nunca bloquean la tarea en curso — éxito, error, progreso, deshacer y mensajes —; el apilado, la expansión al pasar el cursor y las ocho posiciones de anclaje ya vienen resueltos.

9 min de lectura · con grabaciones de ventanas reales

Qué es el Notifier

ui.widgets.Notifier es un servicio de nivel de ventana: inicialízalo una vez y luego llama a show desde cualquier sitio. Las tarjetas viven en la capa toast del portal de superposición de la ventana (z 2000: por encima de los diálogos, por debajo de los tooltips) y nunca participan en el layout de la página. Cada tarjeta tiene un nodo fijo, así que quitar una nunca reconstruye las demás ni repite su entrada. Sustituye al antiguo ToastManager.

El Storybook real: éxito, error, advertencia, progreso, acción, persona, deshacer, silencioso y resumen uno tras otro; luego una ráfaga de cuatro que forma una pila, expandida al pasar el cursor, y se cierra la tarjeta del medio.

La API campo por campo (Notification, NotifierOptions y la firma de cada método) está en la página de Notification del sitio de componentes.

Crear un Notifier

Créalo una vez en tu función de montaje raíz y pasa el puntero a lo que necesite notificar (por ejemplo, el estado de la app guardado con cx.bindState). El Notifier pertenece al Scope que le pasas y desaparece con él.

app.zig
const notifier = try ui.widgets.Notifier.init(scope, cx, .{
    .position = .bottom_center,
    // Keep the stack clear of a 56px toolbar.
    .content_insets = .{ .top = 56 },
    .max_visible = 4,
});
Campo de NotifierOptionsFunción
positionUna de ocho posiciones, por defecto .bottom_center; cámbiala en tiempo de ejecución con setPosition
max_visibleCuántas tarjetas se muestran al expandir (2–6, por defecto 4)
expand_on_hoverSi es false, el hover solo pausa los temporizadores y no expande
content_insetsMárgenes interiores desde los bordes de la ventana (barra de título, barra de herramientas, barra de estado); el margen de anclaje de 22px empieza aquí
widthAncho de la tarjeta, por defecto 392
stringsTextos de la UI ("now", "Undo", "Reply…", la insignia plegada…) para localizar
listenerCallback para acciones del usuario y expiración; también se puede fijar con setListener

Mostrar una notificación

show recibe un valor ui.widgets.Notification y devuelve su id. El Notifier copia cada cadena, así que puedes reutilizar tus búferes de inmediato. Con más de seis tarjetas en pantalla, la más antigua se va con su animación de salida normal.

notify.zig
// Success: auto-dismisses after 3.6 s.
_ = try notifier.show(.{
    .tone = .success,
    .title = "Saved to iCloud",
    .body = "3 files synced · 2.1 MB",
});

// Error with two actions: errors stay until the user acts.
const failed = try notifier.show(.{
    .tone = .@"error",
    .title = "Upload failed",
    .body = "network timeout · dist/app.zip",
    .actions = &.{
        .{ .label = "Retry", .primary = true, .tag = "retry" },
        .{ .label = "View log", .tag = "log" },
    },
});

// Undo: a 7 s countdown ring and a default "Undo" button.
_ = try notifier.show(.{
    .kind = .undo,
    .title = "Moved 4 items to the Trash",
    .body = "You can undo this for 7 seconds.",
});

// A person's message with an inline reply box.
_ = try notifier.show(.{
    .tone = .violet,
    .avatar = "L",
    .title = "Lin",
    .body = "I finished the table widths in section three.",
    .reply = true,
});

Tipos, tonos y duración

El aspecto de una tarjeta depende de dos ejes: kind (layout) y tone (color semántico). El icono inicial se deriva de ambos o se fija explícitamente con lead / glyph.

kindLayout
.defaultIcono, título y cuerpo, con botones, cuadro de respuesta o avatar opcionales
.progressSpinner, barra determinada y un contador n / N; sin barra de vida
.undoAnillo de cuenta atrás con los segundos restantes, más un botón "Undo" por defecto
toneUsoDuración por defecto
.successAlgo terminó3.6 s
.@"error"Un fallo que requiere atenciónfija
.warningConviene verlo, no bloquea5.6 s
.infoInformación general (por defecto)4.2 s
.violetPersonas, invitaciones y otros mensajes sociales4.2 s; fija con botones
.quietEstado discreto (un punto pequeño en lugar de un icono)2.8 s
  • ✓

    Fijas por defecto: las tarjetas progress, las que tienen cuadro de respuesta, el tono error y las tarjetas violet con botones nunca se cierran solas.

  • ✓

    Ajustes explícitos: sticky = true la fija; duration_ms sustituye la duración por defecto; life_bar activa o desactiva la barra de vida.

  • ✓

    Undo dura 7 segundos por defecto, con el anillo siguiendo el temporizador.

Actualizaciones en el sitio y progreso

update(id, …) reescribe el tipo, el tono, el título y el cuerpo de la misma tarjeta y reinicia su duración: sin destruir, sin reconstruir, sin repetir la entrada. Haz avanzar una tarjeta de progreso con setProgress, que solo toca la capa de progreso. Llamar a show con un id existente también la transforma en el sitio.

upload.zig
const id = try notifier.show(.{
    .kind = .progress,
    .title = "Uploading attachment",
    .body = "assets/hero-shot.png",
    .progress = .{ .value = 0, .label = "Uploading · about 5 s", .count = "0 / 5" },
});

// Each tick: only the progress layer is touched, the card isn't rebuilt.
notifier.setProgress(id, 0.6, "Uploading · about 2 s", "3 / 5");

// Done: the same card turns into a success card in place —
// no new entrance, and it now dismisses itself after 3.4 s.
try notifier.update(id, .{
    .tone = .success,
    .title = "Upload complete",
    .body = "assets/hero-shot.png",
    .duration_ms = 3400,
});

Gestionar las acciones del usuario

El Notifier informa a través de un único ui.widgets.NotificationListener. Un NotificationEvent lleva el id de la tarjeta, el kind del evento y el tag / index del botón o el text de la respuesta.

EventKindCuándo
.actionSe pulsó un botón (distínguelos por tag)
.undoSe pulsó Undo; el anillo ya se ha convertido en un ✓
.replySe envió el cuadro de respuesta (Enter o Send); text lo contiene
.dismissedEl usuario la cerró (✕ o arrastrándola fuera)
.expiredExpiró
uploads.zig
const Uploads = struct {
    fn onEvent(ctx: ?*anyopaque, n: *ui.widgets.Notifier, e: ui.widgets.NotificationEvent) void {
        const self: *Uploads = @ptrCast(@alignCast(ctx orelse return));
        switch (e.kind) {
            .action => if (std.mem.eql(u8, e.tag, "retry")) {
                // Turn the sticky error card into a progress card in place.
                n.update(e.id, .{
                    .kind = .progress,
                    .title = "Retrying upload",
                    .progress = .{ .value = 0, .label = "Uploading", .count = "0 / 5" },
                }) catch return;
                self.restart(e.id);
            } else n.dismiss(e.id),
            .reply => self.send(e.text),
            .undo => self.restoreTrash(),
            .dismissed, .expired => {},
        }
    }
    // …restart / send / restoreTrash
};

const uploads = try cx.bindState(Uploads, .{});
notifier.setListener(.{ .context = @ptrCast(uploads), .callback = Uploads.onEvent });

Apilado y hover

Varias notificaciones se pliegan en una pila: la más reciente queda más cerca del ancla con la z más alta; cada capa de detrás asoma 9px y se reduce un 3.8%, hasta tres capas, con una insignia "N más · pasa el cursor para expandir". Cuando el puntero entra en los límites de la pila (ampliados 18px), se expande con separaciones de 9px y todos los temporizadores se pausan; al salir se pliega y se reanudan.

STACK
Los valores provienen de Spec en notification/model.zig: borde visible de 9px, escala del 3.8% por capa, separación de 9px al expandir.
01
Borrar todo

Junto a la insignia hay un botón para borrar todo: el primer clic muestra su texto, el segundo borra de verdad y, al apartar el cursor, se vuelve a plegar. En código, llama a dismissAll().

02
Deslizar para cerrar

Una tarjeta suelta se puede lanzar fuera, lo que emite dismissed.

03
Redimensionar la ventana

Al redimensionar la ventana, la pila sigue a su ancla de forma rígida, sin interpolación que se quede atrás.

Posiciones

setPosition solo cambia el ancla, la dirección de crecimiento y el desplazamiento de entrada: las tarjetas no se reconstruyen, y cualquier tarjeta que esté saliendo termina su salida en la nueva dirección.

POSITIONS
Las mismas cuatro notificaciones ancladas en cada una de las ocho posiciones; encuadre de ventana completa, porque las posiciones de la izquierda van pegadas al borde de la ventana.

El centro de notificaciones es tuyo

El historial, el modo no molestar y un punto de entrada con campana — las funciones de un centro de notificaciones — son lógica de la app y quedan fuera de zenit: registra el historial donde llamas a show, decide si mostrar una tarjeta o no y entérate de cierres, expiraciones y acciones de botones a través del Listener. La tarjeta de resumen del Storybook muestra el patrón: al terminar el modo no molestar, una tarjeta quiet resume lo que llegó.

zenit · Doble licenciaGratis para proyectos de código abierto bajo GPL-3.0-only; los productos cerrados o comerciales necesitan una licencia comercial.Contacta con el autor: zongyi.xzy#gmail.com (cambia # por @)zenit 5f9add5+wip 2026-09-30