docs/advanced/notifications
Funktionen · Notifier

Benachrichtigungen

Nutzen Sie den Notifier für Benachrichtigungen im Fenster, die die aktuelle Aufgabe nie blockieren — Erfolg, Fehler, Fortschritt, Rückgängig und Nachrichten —, wobei Stapeln, Aufklappen bei Hover und acht Andockpositionen für Sie übernommen werden.

9 Min. Lesezeit · mit Aufnahmen echter Fenster

Was der Notifier ist

ui.widgets.Notifier ist ein Dienst auf Fensterebene: einmal initialisieren, dann show von überall aufrufen. Die Karten liegen auf der Toast-Ebene des Overlay-Portals des Fensters (z 2000 — über Dialogen, unter Tooltips) und nehmen nie am Seitenlayout teil. Jede Karte besitzt einen festen Node, sodass das Entfernen einer Karte die anderen nie neu aufbaut oder ihren Auftritt erneut abspielt. Er ersetzt den früheren ToastManager.

Das echte Storybook: Erfolg, Fehler, Warnung, Fortschritt, Aktion, Person, Rückgängig, dezent und Zusammenfassung nacheinander, dann vier auf einmal als Stapel, per Hover aufgeklappt, und die mittlere Karte geschlossen.

Die API Feld für Feld (Notification, NotifierOptions und jede Methodensignatur) finden Sie auf der Notification-Seite der Komponenten-Website.

Einen Notifier erstellen

Erstellen Sie ihn einmal in Ihrer Root-Mount-Funktion und reichen Sie den Zeiger an alles weiter, was benachrichtigen muss (etwa App-State, der per cx.bindState gehalten wird). Der Notifier gehört dem übergebenen Scope und verschwindet mit ihm.

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,
});
NotifierOptions-FeldZweck
positionEine von acht Positionen, Standard .bottom_center; zur Laufzeit mit setPosition umschaltbar
max_visibleWie viele Karten aufgeklappt sichtbar sind (2–6, Standard 4)
expand_on_hoverBei false pausiert Hover nur die Timer und klappt nicht auf
content_insetsAbstände zu den Fensterrändern (Titelleiste, Toolbar, Statusleiste); der 22px-Andockabstand beginnt hier
widthKartenbreite, Standard 392
stringsUI-Texte ("now", "Undo", "Reply…", das eingeklappte Badge …) für die Lokalisierung
listenerCallback für Benutzeraktionen und Ablauf; auch per setListener setzbar

Eine Benachrichtigung anzeigen

show nimmt einen ui.widgets.Notification-Wert und gibt dessen id zurück. Der Notifier kopiert jeden String, Ihre Puffer sind also sofort wiederverwendbar. Ab mehr als sechs Karten auf dem Bildschirm verschwindet die älteste mit ihrer normalen Abgangsanimation.

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

Arten, Töne und Anzeigedauer

Das Aussehen einer Karte ergibt sich aus zwei Achsen: kind (Layout) und tone (semantische Farbe). Das vorangestellte Icon wird aus beiden abgeleitet oder explizit mit lead / glyph gesetzt.

kindLayout
.defaultIcon, Titel und Text, optional mit Buttons, Antwortfeld oder Avatar
.progressSpinner, determinierter Balken und ein n / N-Zähler; kein Lebensdauerbalken
.undoCountdown-Ring mit den verbleibenden Sekunden, dazu ein Standard-Button "Undo"
toneVerwendungStandarddauer
.successEtwas ist fertig3.6 s
.@"error"Ein Fehler, der Aufmerksamkeit brauchtdauerhaft
.warningBeachtenswert, nicht blockierend5.6 s
.infoAllgemeine Information (Standard)4.2 s
.violetPersonen, Einladungen und andere soziale Nachrichten4.2 s; mit Buttons dauerhaft
.quietDezenter Status (ein kleiner Punkt statt eines Icons)2.8 s
  • ✓

    Standardmäßig dauerhaft: progress-Karten, Karten mit Antwortfeld, der Ton error und violet-Karten mit Buttons schließen sich nie von selbst.

  • ✓

    Explizite Überschreibungen: sticky = true erzwingt Dauerhaftigkeit; duration_ms ersetzt die Standarddauer; life_bar schaltet den Lebensdauerbalken um.

  • ✓

    Undo dauert standardmäßig 7 Sekunden, der Ring folgt dem Timer.

Aktualisierung vor Ort und Fortschritt

update(id, …) schreibt Art, Ton, Titel und Text derselben Karte neu und startet ihre Lebensdauer neu — kein Zerstören, kein Neuaufbau, kein erneuter Auftritt. Treiben Sie eine Fortschrittskarte mit setProgress an, das nur die Fortschrittsebene berührt. Auch show mit einer vorhandenen id verwandelt die Karte vor Ort.

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

Benutzeraktionen verarbeiten

Der Notifier meldet über einen einzigen ui.widgets.NotificationListener. Ein NotificationEvent enthält die Karten-id, die Event-kind und das tag / den index des Buttons oder den Antwort-text.

EventKindWann
.actionEin Button wurde geklickt (per tag unterscheiden)
.undoUndo wurde geklickt; der Ring ist bereits zu einem ✓ geworden
.replyDas Antwortfeld wurde abgeschickt (Enter oder Send); text enthält den Inhalt
.dismissedDer Benutzer hat sie geschlossen (✕ oder Wegwischen)
.expiredSie ist abgelaufen
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 });

Stapeln und Hover

Mehrere Benachrichtigungen klappen zu einem Stapel zusammen: Die neueste liegt dem Anker am nächsten mit dem höchsten z; jede Ebene dahinter lugt 9px hervor und schrumpft um 3.8 %, bis zu drei Ebenen, mit einem Badge „N weitere · Hover zum Aufklappen“. Betritt der Zeiger die Stapelgrenzen (um 18px erweitert), klappt er mit 9px Abstand auf und alle Timer pausieren; beim Verlassen klappt er zu und die Timer laufen weiter.

STACK
Die Werte stammen aus Spec in notification/model.zig: 9px Überstand, 3.8 % Skalierung pro Ebene, 9px Abstand im aufgeklappten Zustand.
01
Alle löschen

Neben dem Badge sitzt ein Button zum Löschen aller Karten: Der erste Klick zeigt die Beschriftung, der zweite löscht tatsächlich, und beim Wegbewegen klappt er wieder ein. Im Code rufen Sie dismissAll() auf.

02
Wegwischen zum Schließen

Eine einzelne Karte lässt sich wegwischen, was dismissed meldet.

03
Fenstergröße ändern

Beim Ändern der Fenstergröße folgt der Stapel seinem Anker starr, ohne nachziehende Interpolation.

Positionen

setPosition ändert nur Anker, Wachstumsrichtung und Auftrittsversatz — Karten werden nicht neu aufgebaut, und eine Karte, die gerade abgeht, beendet ihren Abgang in der neuen Richtung.

POSITIONS
Dieselben vier Benachrichtigungen nacheinander an allen acht Positionen angedockt; ganzes Fenster im Bild, da die linken Positionen am Fensterrand anliegen.

Die Mitteilungszentrale gehört Ihnen

Verlauf, Nicht stören und ein Glocken-Einstieg — die Funktionen einer Mitteilungszentrale — sind App-Logik und liegen außerhalb von zenit: Protokollieren Sie den Verlauf dort, wo Sie show aufrufen, entscheiden Sie, ob überhaupt eine Karte erscheint, und erfahren Sie über den Listener von Schließen, Ablauf und Button-Aktionen. Die Zusammenfassungskarte im Storybook zeigt das Muster: Nach dem Ende von Nicht stören fasst eine quiet-Karte zusammen, was eingetroffen ist.

zenit · DoppellizenzKostenlos für Open-Source-Projekte unter GPL-3.0-only; Closed-Source- oder kommerzielle Produkte benötigen eine kommerzielle Lizenz.Kontakt zum Autor: zongyi.xzy#gmail.com (# durch @ ersetzen)zenit 5f9add5+wip 2026-09-30