All checks were successful
Build & Deploy Costco Grocery List / build (push) Successful in 1m10s
Build & Deploy Costco Grocery List / verify-images (push) Successful in 3s
Build & Deploy Costco Grocery List / deploy (push) Successful in 11s
Build & Deploy Costco Grocery List / notify (push) Successful in 1s
106 lines
2.7 KiB
JavaScript
106 lines
2.7 KiB
JavaScript
import { createContext, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
|
const MAX_ACTION_TOASTS = 5;
|
|
const DEFAULT_DURATION_MS = {
|
|
success: 3500,
|
|
info: 3500,
|
|
error: 5000,
|
|
};
|
|
|
|
export const ActionToastContext = createContext(null);
|
|
|
|
function createToastId() {
|
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
return crypto.randomUUID();
|
|
}
|
|
return `toast_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
}
|
|
|
|
export function ActionToastProvider({ children }) {
|
|
const [toasts, setToasts] = useState([]);
|
|
const timerByIdRef = useRef(new Map());
|
|
|
|
const dismiss = useCallback((id) => {
|
|
const timer = timerByIdRef.current.get(id);
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timerByIdRef.current.delete(id);
|
|
}
|
|
|
|
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
|
}, []);
|
|
|
|
const pushToast = useCallback(
|
|
(variant, title, message, options = {}) => {
|
|
const id = createToastId();
|
|
const durationMs = options.durationMs ?? DEFAULT_DURATION_MS[variant] ?? 3500;
|
|
const nextToast = {
|
|
id,
|
|
variant,
|
|
title: String(title || ""),
|
|
message: String(message || ""),
|
|
createdAt: Date.now(),
|
|
durationMs,
|
|
};
|
|
|
|
setToasts((prev) => {
|
|
const next = [...prev, nextToast];
|
|
if (next.length > MAX_ACTION_TOASTS) {
|
|
const oldest = next.shift();
|
|
if (oldest) {
|
|
const timer = timerByIdRef.current.get(oldest.id);
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timerByIdRef.current.delete(oldest.id);
|
|
}
|
|
}
|
|
}
|
|
return next;
|
|
});
|
|
|
|
if (durationMs > 0) {
|
|
const timer = setTimeout(() => dismiss(id), durationMs);
|
|
timerByIdRef.current.set(id, timer);
|
|
}
|
|
|
|
return id;
|
|
},
|
|
[dismiss]
|
|
);
|
|
|
|
const success = useCallback(
|
|
(title, message, options) => pushToast("success", title, message, options),
|
|
[pushToast]
|
|
);
|
|
const error = useCallback(
|
|
(title, message, options) => pushToast("error", title, message, options),
|
|
[pushToast]
|
|
);
|
|
const info = useCallback(
|
|
(title, message, options) => pushToast("info", title, message, options),
|
|
[pushToast]
|
|
);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
for (const timer of timerByIdRef.current.values()) {
|
|
clearTimeout(timer);
|
|
}
|
|
timerByIdRef.current.clear();
|
|
};
|
|
}, []);
|
|
|
|
const value = useMemo(
|
|
() => ({
|
|
toasts,
|
|
success,
|
|
error,
|
|
info,
|
|
dismiss,
|
|
}),
|
|
[toasts, success, error, info, dismiss]
|
|
);
|
|
|
|
return <ActionToastContext.Provider value={value}>{children}</ActionToastContext.Provider>;
|
|
}
|