Toast / snackbar
Spawn a toast: they stack, newest on top, and auto-dismiss
// Web: React, consuming @dizyx/nockerl-tokens. NockerlToast ships from// @dizyx/nockerl-react: ONE transient floating card. The solid lifted surface,// the leading filled status disc whose ring drains as the countdown, hover/focus// pause, and Esc-to-dismiss are all in the package. NockerlToast is the single// toast; the app owns the corner viewport + the stack (spawn / remove / cap).import { useCallback, useRef, useState } from 'react';import { NockerlToast, type NockerlToastProps } from '@dizyx/nockerl-react';
// The queued DATA (the toast fields minus the render-time callbacks) plus an id.type ToastData = Omit<NockerlToastProps, 'onClose' | 'onAction'>;interface Queued extends ToastData { id: number;}
function useToasts() { const [toasts, setToasts] = useState<Queued[]>([]); const idRef = useRef(0); const remove = useCallback((id: number) => setToasts((l) => l.filter((t) => t.id !== id)), []); const show = useCallback((t: ToastData) => { const id = ++idRef.current; setToasts((l) => [...l, { ...t, id }].slice(-4)); // cap the visible stack at 4 }, []); return { toasts, show, remove };}
function SessionActions() { const { toasts, show, remove } = useToasts(); return ( <> <button onClick={() => show({ intent: 'info', message: 'A new session opened on api-server.', duration: 5000 })}> Open </button> <button onClick={() => show({ intent: 'error', title: 'Message failed to send', message: 'The gateway returned a 502.', actionLabel: 'Undo', duration: 0, // 0 → persistent: stays until dismissed }) } > Send </button>
{/* Mount the stack in a corner viewport (bottom-right here); NockerlToast is one card. */} <div className="toast-viewport"> {toasts.map((t) => ( <NockerlToast key={t.id} {...t} onClose={() => remove(t.id)} onAction={resend} /> ))} </div> </> );}// Android: Jetpack Compose. NO toast/snackbar ships today (the app surfaces// transient state as inline connection "pills" + the Inbox sheet). This is the// proposed com.nockerl.app.core.ui.NockerlSnackbarHost, built on NockerlSurface +// the status tokens: the Material SnackbarHostState shape, Nockerl material.import com.nockerl.app.core.ui.NockerlSnackbarHostimport com.nockerl.app.core.ui.NockerlSnackbarIntentimport com.nockerl.app.core.ui.rememberNockerlSnackbarState
val snackbar = rememberNockerlSnackbarState()val scope = rememberCoroutineScope()
Scaffold(snackbarHost = { NockerlSnackbarHost(snackbar) }) { padding -> Content(Modifier.padding(padding)) { scope.launch { val result = snackbar.show( message = "The gateway returned a 502.", intent = NockerlSnackbarIntent.ERROR, title = "Message failed to send", actionLabel = "Undo", duration = NockerlSnackbarDuration.INDEFINITE, // persistent ) if (result == NockerlSnackbarResult.ACTION) viewModel.resend() } }}// macOS: SwiftUI (Voice). The RecordingHUD (NockerlVoice/UI/RecordingHUD.swift)// IS the shipped transient floating overlay: a non-activating NSPanel that// auto-hides via scheduleHide(after:) and never steals focus. This is the// proposed NockerlToast that generalises it into a stackable, severity-aware HUD.@StateObject private var toasts = NockerlToastCenter.shared
toasts.show( "The gateway returned a 502.", intent: .error, title: "Message failed to send", action: .init(label: "Undo") { resend() }, duration: .persistent // .seconds(5) auto-dismisses; .persistent waits)
// The host overlay, mounted once near the app root, anchors the stack to a corner.ContentView() .nockerlToastHost(center: toasts, position: .bottomTrailing)Parameters
Section titled “Parameters”NockerlToast renders ONE toast from a NockerlToastProps object plus an onClose
(timer / Esc / close-X) and an optional onAction (the action button). The corner
viewport + the stack (spawn / remove / cap) are the app’s. Mount the cards where you
want them; the toast owns its own countdown, hover/focus pause, and Esc dismissal.
| Prop | Type | Default | Description |
|---|---|---|---|
message * | string | Body copy that carries the toast's accessible text (rendered as HTML). | |
onClose * | () => void | Dismiss handler, fired when the timer elapses, Esc is pressed, or the close (X) is clicked. | |
intent | NockerlToastIntent | 'info' | Drives the status color, the default icon, and the live role. |
title | string | Optional bold heading above the message (one short line). | |
icon | boolean | true | Show the leading status disc (defaults to on). |
actionLabel | string | Optional quiet text button in the status color (right-aligned; e.g. Undo). | |
onAction | () => void | Action handler (ignored when no actionLabel). | |
duration | number | Auto-dismiss after this many ms. 0 / undefined → PERSISTENT (no timeout); the toast then shows a "pinned" marker instead of the ring and waits for close. |
NockerlSnackbarHost is the proposed Compose host: a corner-anchored stack of
NockerlSurfaces (panel radius + the lit-from-above material) washed with the
intent’s status color, the same errorContainer treatment generalised. State is
driven by rememberNockerlSnackbarState(), mirroring SnackbarHostState.
| Parameter | Type | Default | Description |
|---|---|---|---|
message * | String | Body copy, rendered with bodyMedium on the card foreground. | |
intent | NockerlSnackbarIntent | INFO | Status intent: INFO · SUCCESS · WARNING · ERROR. INFO is the only cyan; the rest are warm status tokens. |
title | String? | null | Optional heading (one line) above the message, in the status color. |
showIcon | Boolean | true | Show the leading status icon (top-aligned). Color is paired with the icon + text, never alone. |
actionLabel | String? | null | Optional action: a GHOST NockerlButton in the status color. Tapping it resumes the suspend show() with ACTION. |
duration | NockerlSnackbarDuration | SHORT | Auto-dismiss window: SHORT · LONG · INDEFINITE. INDEFINITE is persistent (a pinned marker; waits for close). A countdown rides the bottom edge. |
withDismissAction | Boolean | true | Show a trailing dismiss NockerlIconButton, a separate focusable target. Resumes show() with DISMISSED. |
NockerlToast is the proposed SwiftUI generalisation of the shipped RecordingHUD:
a non-activating, focus-safe overlay surface tinted by the intent’s NockerlTheme
status token, stacked in a corner by .nockerlToastHost(center:position:) and
auto-dismissed on a timer.
| Style | Type | Default | Description |
|---|---|---|---|
show(_:intent:) | Void | Enqueue a toast: the message + intent (.info / .success / .warning / .error). .info uses NockerlTheme.accent (cyan); the rest use warm status tokens. | |
title: | String? | Optional heading above the message, in the status color. Defaults to nil. | |
showIcon: | Bool | Leading SF Symbol in the status color (e.g. exclamationmark.triangle.fill), top-aligned. Defaults to true. | |
action: | ToastAction? | Optional (label, handler) rendered as a quiet trailing .nockerlGhost button (e.g. Undo), right-aligned. Defaults to nil. | |
duration: | ToastDuration | .seconds(_) auto-dismisses (a bottom-edge countdown; hover pauses, echoing the HUD scheduleHide); .persistent waits for close. Defaults to .seconds(5). | |
.nockerlToastHost(center:position:) | View | Mounts the stack overlay near the app root and anchors it to a corner (.bottomTrailing by default, like the HUD). Non-activating, so it never steals focus. |