docs/advanced/notifications
Fonctionnalités · Notifier

Notifications

Utilisez le Notifier pour des notifications dans la fenêtre qui ne bloquent jamais la tâche en cours — succès, erreur, progression, annulation et messages —, avec l’empilement, le déploiement au survol et huit positions d’ancrage gérés pour vous.

9 min de lecture · avec des enregistrements de fenêtres réelles

Qu’est-ce que le Notifier

ui.widgets.Notifier est un service au niveau de la fenêtre : initialisez-le une fois, puis appelez show depuis n’importe où. Les cartes vivent sur le niveau toast du portal de superposition de la fenêtre (z 2000 — au-dessus des dialogues, sous les tooltips) et ne participent jamais à la mise en page. Chaque carte possède un nœud fixe : en retirer une ne reconstruit jamais les autres et ne rejoue pas leur entrée. Il remplace l’ancien ToastManager.

Le vrai Storybook : succès, erreur, avertissement, progression, action, personne, annulation, discret et résumé l’un après l’autre, puis une rafale de quatre en pile, déployée au survol, avec la carte du milieu fermée.

L’API champ par champ (Notification, NotifierOptions et la signature de chaque méthode) se trouve sur la page Notification du site des composants.

Créer un Notifier

Créez-le une fois dans votre fonction de montage racine et passez le pointeur à ce qui doit notifier (par exemple l’état de l’application conservé par cx.bindState). Le Notifier appartient au Scope que vous passez et disparaît avec lui.

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,
});
Champ de NotifierOptionsRôle
positionL’une des huit positions, .bottom_center par défaut ; modifiable à l’exécution avec setPosition
max_visibleNombre de cartes affichées une fois déployé (2–6, 4 par défaut)
expand_on_hoverSi false, le survol met seulement les minuteurs en pause sans déployer
content_insetsRetraits depuis les bords de la fenêtre (barre de titre, barre d’outils, barre d’état) ; la marge d’ancrage de 22px part de là
widthLargeur des cartes, 392 par défaut
stringsTextes de l’UI ("now", "Undo", "Reply…", le badge replié…) pour la localisation
listenerCallback pour les actions de l’utilisateur et l’expiration ; aussi définissable avec setListener

Afficher une notification

show prend une valeur ui.widgets.Notification et renvoie son id. Le Notifier copie chaque chaîne, vos tampons sont donc réutilisables immédiatement. Au-delà de six cartes à l’écran, la plus ancienne part avec son animation de sortie normale.

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,
});

Types, tons et durées

L’apparence d’une carte dépend de deux axes : kind (mise en page) et tone (couleur sémantique). L’icône de tête découle des deux, ou se définit explicitement avec lead / glyph.

kindMise en page
.defaultIcône, titre et corps, avec boutons, zone de réponse ou avatar en option
.progressSpinner, barre déterminée et un compteur n / N ; pas de barre de vie
.undoAnneau de compte à rebours affichant les secondes restantes, plus un bouton "Undo" par défaut
toneUsageDurée par défaut
.successUne opération terminée3.6 s
.@"error"Un échec qui demande une actionpersistante
.warningÀ remarquer, sans bloquer5.6 s
.infoInformation générale (par défaut)4.2 s
.violetPersonnes, invitations et autres messages sociaux4.2 s ; persistante avec boutons
.quietStatut discret (un petit point au lieu d’une icône)2.8 s
  • ✓

    Persistantes par défaut : les cartes progress, celles avec une zone de réponse, le ton error et les cartes violet avec boutons ne se ferment jamais d’elles-mêmes.

  • ✓

    Surcharges explicites : sticky = true la rend persistante ; duration_ms remplace la durée par défaut ; life_bar affiche ou masque la barre de vie.

  • ✓

    Undo dure 7 secondes par défaut, l’anneau suivant le minuteur.

Mises à jour sur place et progression

update(id, …) réécrit le type, le ton, le titre et le corps de la même carte et relance sa durée de vie — ni destruction, ni reconstruction, ni entrée rejouée. Faites avancer une carte de progression avec setProgress, qui ne touche que la couche de progression. Appeler show avec un id existant la transforme aussi sur place.

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,
});

Gérer les actions de l’utilisateur

Le Notifier rend compte via un unique ui.widgets.NotificationListener. Un NotificationEvent transporte l’id de la carte, le kind de l’événement, et le tag / index du bouton ou le text de la réponse.

EventKindQuand
.actionUn bouton a été cliqué (distinguez-les par tag)
.undoUndo a été cliqué ; l’anneau est déjà devenu un ✓
.replyLa zone de réponse a été envoyée (Entrée ou Send) ; text la contient
.dismissedL’utilisateur l’a fermée (✕ ou d’un geste)
.expiredElle a expiré
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 });

Empilement et survol

Plusieurs notifications se replient en pile : la plus récente est la plus proche de l’ancre, avec le z le plus élevé ; chaque couche derrière dépasse de 9px et rétrécit de 3.8 %, jusqu’à trois couches, avec un badge « N de plus · survoler pour déployer ». Quand le pointeur entre dans les limites de la pile (agrandies de 18px), elle se déploie avec des écarts de 9px et tous les minuteurs se mettent en pause ; en sortant, elle se replie et ils reprennent.

STACK
Les valeurs viennent de Spec dans notification/model.zig : débord de 9px, échelle de 3.8 % par couche, écart de 9px une fois déployé.
01
Tout effacer

À côté du badge se trouve un bouton « tout effacer » : le premier clic révèle son libellé, le second efface vraiment, et s’éloigner le replie. Dans le code, appelez dismissAll().

02
Glisser pour fermer

Une carte isolée peut être écartée d’un geste, ce qui signale dismissed.

03
Redimensionnement de la fenêtre

Au redimensionnement de la fenêtre, la pile suit son ancre de façon rigide, sans interpolation à la traîne.

Positions

setPosition ne change que l’ancre, la direction de croissance et le décalage d’entrée — les cartes ne sont pas reconstruites, et une carte en train de sortir termine sa sortie dans la nouvelle direction.

POSITIONS
Les quatre mêmes notifications ancrées tour à tour aux huit positions ; cadrage sur toute la fenêtre, car les positions de gauche longent le bord de la fenêtre.

Le centre de notifications vous appartient

L’historique, le mode Ne pas déranger et un point d’entrée en forme de cloche — les fonctions d’un centre de notifications — relèvent de la logique de l’application et restent hors de zenit : enregistrez l’historique là où vous appelez show, décidez s’il faut afficher une carte, et suivez fermetures, expirations et actions de boutons via le Listener. La carte Résumé du Storybook illustre le principe : à la fin du mode Ne pas déranger, une seule carte quiet récapitule ce qui est arrivé.

zenit · Double licenceGratuit pour les projets open source sous GPL-3.0-only ; les produits propriétaires ou commerciaux nécessitent une licence commerciale.Contacter l’auteur : zongyi.xzy#gmail.com (remplacez # par @)zenit 5f9add5+wip 2026-09-30