Files
2026-09-19 14:52:36 +00:00

51 lines
1.5 KiB
TypeScript

'use client';
import { useCallback, useRef, useState } from 'react';
// ISS-008 (v0.17.0): transient toast notifications — a bottom-right stack for action errors
// and game events derived from snapshot diffs. Each toast auto-dismisses after TOAST_TTL_MS;
// the queue is capped at MAX_TOASTS (oldest dropped) so an event burst can't fill the corner.
export type ToastKind = 'error' | 'info';
export interface Toast {
id: number;
kind: ToastKind;
message: string;
}
export const TOAST_TTL_MS = 5_000;
export const MAX_TOASTS = 4;
// Pure core (unit-tested): append a toast, dropping the oldest beyond the cap.
export function pushToast(toasts: Toast[], toast: Toast): Toast[] {
return [...toasts, toast].slice(-MAX_TOASTS);
}
export interface UseToastsResult {
toasts: Toast[];
push: (kind: ToastKind, message: string) => void;
dismiss: (id: number) => void;
}
export function useToasts(): UseToastsResult {
const [toasts, setToasts] = useState<Toast[]>([]);
const nextIdRef = useRef(1);
// Functional update — a late timer for an already-dismissed/dropped id is a no-op.
const dismiss = useCallback((id: number) => {
setToasts((current) => current.filter((t) => t.id !== id));
}, []);
const push = useCallback(
(kind: ToastKind, message: string) => {
const toast: Toast = { id: nextIdRef.current++, kind, message };
setToasts((current) => pushToast(current, toast));
setTimeout(() => dismiss(toast.id), TOAST_TTL_MS);
},
[dismiss],
);
return { toasts, push, dismiss };
}