---
title: "通知 — zenit Zig UI ドキュメント"
description: "Notifier を使うと、作業を妨げないウィンドウ内通知（成功、エラー、進捗、取り消し、メッセージ）を表示できます。"
url: https://zenit.z.express/ja/docs/advanced/notifications
language: ja
alternate_en: https://zenit.z.express/docs/advanced/notifications.md
alternate_zh: https://zenit.z.express/zh/docs/advanced/notifications.md
alternate_es: https://zenit.z.express/es/docs/advanced/notifications.md
alternate_ko: https://zenit.z.express/ko/docs/advanced/notifications.md
alternate_fr: https://zenit.z.express/fr/docs/advanced/notifications.md
alternate_de: https://zenit.z.express/de/docs/advanced/notifications.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# アプリ内通知

Notifier を使うと、作業を妨げないウィンドウ内通知（成功、エラー、進捗、取り消し、メッセージ）を表示できます。スタック、ホバーでの展開、8 つの表示位置はすべて Notifier が管理します。

## Notifier とは

`ui.widgets.Notifier` はウィンドウ単位のサービスです。一度初期化すれば、どこからでも `show` を呼べます。カードはウィンドウのオーバーレイ portal の toast 層（z 2000、ダイアログより上、tooltip より下）に置かれ、ページのレイアウトには関与しません。各カードは固定のノードを持つため、1 枚を削除しても他のカードが再構築されたり入場アニメーションが再生されたりすることはありません。以前の ToastManager を置き換えるものです。

[Video](https://zenit.z.express/media/stories/notification.mp4?v=5f8cadb85b)

実際の Storybook：成功、エラー、警告、進捗、要操作、人物メッセージ、取り消し、控えめ、ダイジェストを順に表示し、続けて 4 件を連続表示してスタックにし、ホバーで展開してから中央の 1 件を閉じます。

フィールドごとの API（`Notification`、`NotifierOptions`、全メソッドのシグネチャ）は、コンポーネントサイトの [Notification ページ](https://zenit.z.express/ja/components/notification)を参照してください。

## Notifier の作成

ルートのマウント関数で一度だけ作成し、通知を出す必要があるコード（たとえば `cx.bindState` で保持するアプリ状態）にポインターを渡します。Notifier は渡した Scope が所有し、Scope とともに破棄されます。

`app.zig`

```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 のフィールド | 役割 |
| --- | --- |
| `position` | 8 つの表示位置のいずれか。既定は .bottom\_center。実行時に setPosition で切り替え可能 |
| `max_visible` | 展開時に表示する最大件数（2–6、既定 4） |
| `expand_on_hover` | false のとき、ホバーはタイマーを止めるだけで展開しない |
| `content_insets` | ウィンドウ端からのインセット（タイトルバー、ツールバー、ステータスバー）。22px の配置マージンはここから数える |
| `width` | カードの幅。既定 392 |
| `strings` | UI 文言（"now"、"Undo"、"Reply…"、折りたたみ時のバッジなど）。ローカライズ時に差し替える |
| `listener` | ユーザー操作と期限切れのコールバック。setListener でも設定可能 |

## 通知を表示する

`show` は `ui.widgets.Notification` の値を受け取り、その id を返します。文字列はすべて Notifier がコピーして保持するので、呼び出し側のバッファはすぐに再利用できます。画面上が 6 件を超えると、最も古いカードが通常の退場アニメーションで消えます。

`notify.zig`

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

> NOTE
> 
> **ボタンは最大 2 つ。** ボタンは区切り線の下に等幅で並び、ニュートラルな塗りを使います。primary は塗りと字の太さが強くなるだけで、トーンの色は付きません。

## 種類・トーン・表示時間

カードの見た目は `kind`（レイアウト）と `tone`（意味色）の 2 軸で決まります。先頭のアイコンは両者から導かれますが、`lead` / `glyph` で明示的に指定することもできます。

| kind | レイアウト |
| --- | --- |
| `.default` | アイコン + タイトル + 本文。ボタン、返信欄、アバターは任意 |
| `.progress` | スピナー + 確定進捗バー + n / N のカウント。寿命バーは表示しない |
| `.undo` | 残り秒数を中央に表示するカウントダウンリング + 既定の「Undo」ボタン |

| tone | 用途 | 既定の表示時間 |
| --- | --- | --- |
| `.success` | 操作の完了 | 3.6 s |
| `.@"error"` | 対応が必要な失敗 | 常駐 |
| `.warning` | 注意が必要だが妨げない | 5.6 s |
| `.info` | 一般的な情報（既定） | 4.2 s |
| `.violet` | 人物、招待などのソーシャルなメッセージ | 4.2 s。ボタン付きなら常駐 |
| `.quiet` | 控えめなステータス（アイコンの代わりに小さな点） | 2.8 s |

-   **既定で常駐：**progress カード、返信欄付きのカード、error トーン、ボタン付きの violet カードは自動では消えません。
    
-   **明示的な上書き：**`sticky = true` で常駐を強制、`duration_ms` で既定の時間を置き換え、`life_bar` で下部の寿命バーの表示を切り替えます。
    
-   **undo** の既定は 7 秒で、カウントダウンリングはタイマーと同期します。
    

## その場での更新と進捗

`update(id, …)` は同じカードの種類、トーン、タイトル、本文を書き換え、寿命をリセットします。破棄も再構築も入場の再生もありません。進捗カードは `setProgress` で進め、これは進捗レイヤーだけを更新します。既存の `id` で `show` を呼んでも、その場で変換されます。

`upload.zig`

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

## ユーザー操作の処理

Notifier は 1 つの `ui.widgets.NotificationListener` を通じてイベントを通知します。`NotificationEvent` にはカードの `id`、イベントの `kind`、そしてボタンの `tag` / `index` または返信内容の `text` が含まれます。

| EventKind | 発生するタイミング |
| --- | --- |
| `.action` | ボタンがクリックされた（tag で区別） |
| `.undo` | Undo がクリックされた。リングはその場で ✓ に変わっている |
| `.reply` | 返信欄が送信された（Enter または Send）。内容は text |
| `.dismissed` | ユーザーが閉じた（✕ またはスワイプで払う） |
| `.expired` | 時間切れで閉じた |

`uploads.zig`

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

> TIP
> 
> **返信の送信に失敗したら？** `notifier.replyFailed(id, title, body)` を呼ぶと、同じカードがその場で失敗状態に変わり、ユーザーの入力は失われません。

## スタックとホバー

複数の通知は 1 つのスタックに折りたたまれます。最新のカードがアンカーに最も近く z 順が最上位で、後ろの各層は 9px ずつはみ出して 3.8% ずつ縮小し、最大 3 層まで見え、「あと N 件 · ホバーで展開」のバッジが付きます。ポインターがスタックの外接矩形（18px 拡張）に入ると 9px 間隔で展開し、すべてのタイマーが一時停止します。離れると折りたたまれ、タイマーが再開します。

STACK

数値は notification/model.zig の Spec から：はみ出し 9px、層ごとの縮小 3.8%、展開時の間隔 9px。

**すべて消去**

バッジの横に「すべて消去」ボタンがあります。1 回目のクリックでラベルが現れ、2 回目で実際に消去し、ポインターを離すと元に戻ります。コードでは dismissAll() を呼びます。

**スワイプで閉じる**

1 枚のカードをドラッグして払いのけると閉じ、dismissed イベントが発生します。

**ウィンドウのリサイズ**

ウィンドウサイズが変わると、スタックはアンカーにぴったり追従し、補間による遅れは生じません。

## 表示位置

`setPosition` が変えるのはアンカー、伸びる方向、入場時のオフセットだけで、カードは再構築されません。退場中のカードは新しい方向で退場を終えます。

POSITIONS

[Video](https://zenit.z.express/media/stories/notification-positions.mp4?v=2df7ff87b6)

同じ 4 件の通知を 8 つの位置に順に配置しています。左側の位置はウィンドウの左端に接するため、ウィンドウ全体を映しています。

## 通知センターはアプリ側の責務

履歴、おやすみモード、ベルの入口といった「通知センター」の機能はアプリのロジックであり、zenit には含まれません。show を呼ぶ場所で履歴を記録し、そもそもカードを出すかどうかを判断し、閉じる・期限切れ・ボタン操作は Listener で受け取ります。Storybook の「ダイジェスト」カードがこのパターンの例で、おやすみモードの終了後に 1 枚の quiet カードで届いた内容をまとめます。

> WARNING
> 
> **アクセシビリティは未検証です。** zenit の他のコンポーネントと同様、通知カードの VoiceOver 検証はまだ完了していません。重要な情報を伝える唯一の手段にしないでください。
