Lint issues fixed: 1. Removed unused error variable in catch block 2. Changed 'ActionTypes' type to enum 3. Removed unnecessary interface extensions 4. Removed unused export component 5. Added the missing dependencies to useMemo 6. Fixed the inline function issue by changing it to useMemo
192 lines
3.9 KiB
TypeScript
192 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
// Inspired by react-hot-toast library
|
|
import * as React from "react";
|
|
|
|
import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
|
|
|
|
const TOAST_LIMIT = 1;
|
|
const TOAST_REMOVE_DELAY = 1000000;
|
|
|
|
type ToasterToast = ToastProps & {
|
|
id: string;
|
|
title?: React.ReactNode;
|
|
description?: React.ReactNode;
|
|
action?: ToastActionElement;
|
|
};
|
|
|
|
let count = 0;
|
|
|
|
function genId() {
|
|
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
|
return count.toString();
|
|
}
|
|
|
|
const enum ActionType {
|
|
ADD_TOAST = "ADD_TOAST",
|
|
UPDATE_TOAST = "UPDATE_TOAST",
|
|
DISMISS_TOAST = "DISMISS_TOAST",
|
|
REMOVE_TOAST = "REMOVE_TOAST",
|
|
}
|
|
|
|
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: ActionType.REMOVE_TOAST,
|
|
toastId: toastId,
|
|
});
|
|
}, TOAST_REMOVE_DELAY);
|
|
|
|
toastTimeouts.set(toastId, timeout);
|
|
};
|
|
|
|
export const reducer = (state: State, action: Action): State => {
|
|
switch (action.type) {
|
|
case ActionType.ADD_TOAST:
|
|
return {
|
|
...state,
|
|
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
};
|
|
|
|
case ActionType.UPDATE_TOAST:
|
|
return {
|
|
...state,
|
|
toasts: state.toasts.map((t) =>
|
|
t.id === action.toast.id ? { ...t, ...action.toast } : t,
|
|
),
|
|
};
|
|
|
|
case ActionType.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 ActionType.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, "id">;
|
|
|
|
function toast({ ...props }: Toast) {
|
|
const id = genId();
|
|
|
|
const update = (props: ToasterToast) =>
|
|
dispatch({
|
|
type: ActionType.UPDATE_TOAST,
|
|
toast: { ...props, id },
|
|
});
|
|
const dismiss = () =>
|
|
dispatch({ type: ActionType.DISMISS_TOAST, toastId: id });
|
|
|
|
dispatch({
|
|
type: ActionType.ADD_TOAST,
|
|
toast: {
|
|
...props,
|
|
id,
|
|
open: true,
|
|
onOpenChange: (open) => {
|
|
if (!open) dismiss();
|
|
},
|
|
},
|
|
});
|
|
|
|
return {
|
|
id: id,
|
|
dismiss,
|
|
update,
|
|
};
|
|
}
|
|
|
|
function useToast() {
|
|
const [state, setState] = React.useState<State>(memoryState);
|
|
|
|
React.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: ActionType.DISMISS_TOAST, toastId }),
|
|
};
|
|
}
|
|
|
|
export { useToast, toast };
|