Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
124 lines
3.0 KiB
TypeScript
124 lines
3.0 KiB
TypeScript
import React, {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from 'react';
|
|
|
|
export type ToastLevel = 'info' | 'success' | 'warn' | 'error';
|
|
|
|
export interface Toast {
|
|
id: string;
|
|
level: ToastLevel;
|
|
message: string;
|
|
detail?: string;
|
|
createdAt: number;
|
|
durationMs: number;
|
|
}
|
|
|
|
export interface ShowToastOptions {
|
|
level?: ToastLevel;
|
|
detail?: string;
|
|
/** Override default auto-dismiss; 0 = stay until dismissed */
|
|
durationMs?: number;
|
|
}
|
|
|
|
export const TOAST_DURATION_MS: Record<ToastLevel, number> = {
|
|
info: 5000,
|
|
success: 5000,
|
|
warn: 6000,
|
|
error: 10000,
|
|
};
|
|
|
|
export const MAX_VISIBLE_TOASTS = 5;
|
|
|
|
let toastCounter = 0;
|
|
function nextToastId(): string {
|
|
toastCounter += 1;
|
|
return `toast-${toastCounter}`;
|
|
}
|
|
|
|
export interface ToastContextValue {
|
|
toasts: Toast[];
|
|
showToast: (message: string, options?: ShowToastOptions) => string;
|
|
dismissToast: (id: string) => void;
|
|
clearAllToasts: () => void;
|
|
}
|
|
|
|
const ToastContext = createContext<ToastContextValue>({
|
|
toasts: [],
|
|
showToast: () => '',
|
|
dismissToast: () => {},
|
|
clearAllToasts: () => {},
|
|
});
|
|
|
|
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
const timersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
|
|
|
const dismissToast = useCallback((id: string) => {
|
|
const timer = timersRef.current.get(id);
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timersRef.current.delete(id);
|
|
}
|
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
}, []);
|
|
|
|
const clearAllToasts = useCallback(() => {
|
|
for (const timer of timersRef.current.values()) clearTimeout(timer);
|
|
timersRef.current.clear();
|
|
setToasts([]);
|
|
}, []);
|
|
|
|
const scheduleDismiss = useCallback(
|
|
(toast: Toast) => {
|
|
if (toast.durationMs <= 0) return;
|
|
const timer = setTimeout(() => dismissToast(toast.id), toast.durationMs);
|
|
timersRef.current.set(toast.id, timer);
|
|
},
|
|
[dismissToast],
|
|
);
|
|
|
|
const showToast = useCallback(
|
|
(message: string, options?: ShowToastOptions) => {
|
|
const level = options?.level ?? 'info';
|
|
const durationMs = options?.durationMs ?? TOAST_DURATION_MS[level];
|
|
const toast: Toast = {
|
|
id: nextToastId(),
|
|
level,
|
|
message,
|
|
detail: options?.detail,
|
|
createdAt: Date.now(),
|
|
durationMs,
|
|
};
|
|
setToasts((prev) => [toast, ...prev].slice(0, MAX_VISIBLE_TOASTS));
|
|
scheduleDismiss(toast);
|
|
return toast.id;
|
|
},
|
|
[scheduleDismiss],
|
|
);
|
|
|
|
useEffect(() => {
|
|
const timers = timersRef.current;
|
|
return () => {
|
|
for (const timer of timers.values()) clearTimeout(timer);
|
|
timers.clear();
|
|
};
|
|
}, []);
|
|
|
|
const value = useMemo(
|
|
() => ({ toasts, showToast, dismissToast, clearAllToasts }),
|
|
[toasts, showToast, dismissToast, clearAllToasts],
|
|
);
|
|
|
|
return <ToastContext.Provider value={value}>{children}</ToastContext.Provider>;
|
|
}
|
|
|
|
export function useToast(): ToastContextValue {
|
|
return useContext(ToastContext);
|
|
}
|