deno.land / x / netzo@0.5.16 / components / use-toast.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189import type { VNode } from "preact";import { useEffect, useState } from "preact/compat";import type { ToastActionElement, ToastProps } from "./toast.tsx";
const TOAST_LIMIT = 1;const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & { id: string; title?: VNode; description?: VNode; action?: ToastActionElement;};
const actionTypes = { ADD_TOAST: "ADD_TOAST", UPDATE_TOAST: "UPDATE_TOAST", DISMISS_TOAST: "DISMISS_TOAST", REMOVE_TOAST: "REMOVE_TOAST",} as const;
let count = 0;
function genId() { count = (count + 1) % Number.MAX_VALUE; return count.toString();}
type ActionType = typeof actionTypes;
type Action = | { type: ActionType["ADD_TOAST"]; toast: ToasterToast; } | { type: ActionType["UPDATE_TOAST"]; toast: Partial<ToasterToast>; } | { type: ActionType["DISMISS_TOAST"]; toastId?: ToasterToast["id"]; } | { type: ActionType["REMOVE_TOAST"]; toastId?: ToasterToast["id"]; };
interface State { toasts: ToasterToast[];}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => { if (toastTimeouts.has(toastId)) { return; }
const timeout = setTimeout(() => { toastTimeouts.delete(toastId); dispatch({ type: "REMOVE_TOAST", toastId: toastId, }); }, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout);};
export const reducer = (state: State, action: Action): State => { switch (action.type) { case "ADD_TOAST": return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), };
case "UPDATE_TOAST": return { ...state, toasts: state.toasts.map((t) => t.id === action.toast.id ? { ...t, ...action.toast } : t ), };
case "DISMISS_TOAST": { const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action, // but I'll keep it here for simplicity if (toastId) { addToRemoveQueue(toastId); } else { state.toasts.forEach((toast) => { addToRemoveQueue(toast.id); }); }
return { ...state, toasts: state.toasts.map((t) => t.id === toastId || toastId === undefined ? { ...t, open: false, } : t ), }; } case "REMOVE_TOAST": if (action.toastId === undefined) { return { ...state, toasts: [], }; } return { ...state, toasts: state.toasts.filter((t) => t.id !== action.toastId), }; }};
const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] };
function dispatch(action: Action) { memoryState = reducer(memoryState, action); listeners.forEach((listener) => { listener(memoryState); });}
type Toast = Omit<ToasterToast>;
function toast({ ...props }: Toast) { const id = genId();
const update = (props: ToasterToast) => dispatch({ type: "UPDATE_TOAST", toast: { ...props, id }, }); const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({ type: "ADD_TOAST", toast: { ...props, id, open: true, onOpenChange: (open) => { if (!open) dismiss(); }, }, });
return { id: id, dismiss, update, };}
function useToast() { const [state, setState] = useState<State>(memoryState);
useEffect(() => { listeners.push(setState); return () => { const index = listeners.indexOf(setState); if (index > -1) { listeners.splice(index, 1); } }; }, [state]);
return { ...state, toast, dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), };}
export { toast, useToast };
Version Info