@@ -80,7 +105,7 @@ function Toast({ notification }: { notification: Notification }) {
type="button"
className={styles.close}
aria-label="Dismiss notification"
- onClick={() => dismiss(id)}
+ onClick={handleDismiss}
>
@@ -90,11 +115,40 @@ function Toast({ notification }: { notification: Notification }) {
export function Toaster() {
const notifications = useNotificationStore((s) => s.notifications);
- if (notifications.length === 0) return null;
+ const dismiss = useNotificationStore((s) => s.dismiss);
+
+ // Local set of ids currently playing their exit animation. Each call to
+ // startExit marks the id, then after TOAST_EXIT_MS removes it from the store.
+ // Multiple toasts can be leaving simultaneously.
+ const [exiting, setExiting] = useState
>(new Set());
+
+ const startExit = useCallback(
+ (id: string) => {
+ setExiting((prev) => {
+ if (prev.has(id)) return prev; // already leaving — no-op
+ const next = new Set(prev);
+ next.add(id);
+ return next;
+ });
+ setTimeout(() => {
+ dismiss(id);
+ // Clean the exiting set so stale ids don't accumulate (the store entry
+ // is gone, but the Set would grow unboundedly on repeated notify/dismiss).
+ setExiting((prev) => {
+ const next = new Set(prev);
+ next.delete(id);
+ return next;
+ });
+ }, TOAST_EXIT_MS);
+ },
+ [dismiss],
+ );
+
+ if (notifications.length === 0 && exiting.size === 0) return null;
return (
{notifications.map((n) => (
-
+
))}
);