WaledRashed24/comics
0
1// Inspired by react-hot-toast library2import * as React from "react"3 4import type {5 ToastActionElement,6 ToastProps,7} from "@/components/ui/toast"8 9const TOAST_LIMIT = 110const TOAST_REMOVE_DELAY = 100000011 12type ToasterToast = ToastProps & {13 id: string14 title?: React.ReactNode15 description?: React.ReactNode16 action?: ToastActionElement17}18 19const actionTypes = {20 ADD_TOAST: "ADD_TOAST",21 UPDATE_TOAST: "UPDATE_TOAST",22 DISMISS_TOAST: "DISMISS_TOAST",23 REMOVE_TOAST: "REMOVE_TOAST",24} as const25 26let count = 027 28function genId() {29 count = (count + 1) % Number.MAX_VALUE30 return count.toString()31}32 33type ActionType = typeof actionTypes34 35type Action =36 | {37 type: ActionType["ADD_TOAST"]38 toast: ToasterToast39 }40 | {41 type: ActionType["UPDATE_TOAST"]42 toast: Partial<ToasterToast>43 }44 | {45 type: ActionType["DISMISS_TOAST"]46 toastId?: ToasterToast["id"]47 }48 | {49 type: ActionType["REMOVE_TOAST"]50 toastId?: ToasterToast["id"]51 }52 53interface State {54 toasts: ToasterToast[]55}56 57const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()58 59const addToRemoveQueue = (toastId: string) => {60 if (toastTimeouts.has(toastId)) {61 return62 }63 64 const timeout = setTimeout(() => {65 toastTimeouts.delete(toastId)66 dispatch({67 type: "REMOVE_TOAST",68 toastId: toastId,69 })70 }, TOAST_REMOVE_DELAY)71 72 toastTimeouts.set(toastId, timeout)73}74 75export const reducer = (state: State, action: Action): State => {76 switch (action.type) {77 case "ADD_TOAST":78 return {79 ...state,80 toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),81 }82 83 case "UPDATE_TOAST":84 return {85 ...state,86 toasts: state.toasts.map((t) =>87 t.id === action.toast.id ? { ...t, ...action.toast } : t88 ),89 }90 91 case "DISMISS_TOAST": {92 const { toastId } = action93 94 // ! Side effects ! - This could be extracted into a dismissToast() action,95 // but I'll keep it here for simplicity96 if (toastId) {97 addToRemoveQueue(toastId)98 } else {99 state.toasts.forEach((toast) => {100 addToRemoveQueue(toast.id)101 })102 }103 104 return {105 ...state,106 toasts: state.toasts.map((t) =>107 t.id === toastId || toastId === undefined108 ? {109 ...t,110 open: false,111 }112 : t113 ),114 }115 }116 case "REMOVE_TOAST":117 if (action.toastId === undefined) {118 return {119 ...state,120 toasts: [],121 }122 }123 return {124 ...state,125 toasts: state.toasts.filter((t) => t.id !== action.toastId),126 }127 }128}129 130const listeners: Array<(state: State) => void> = []131 132let memoryState: State = { toasts: [] }133 134function dispatch(action: Action) {135 memoryState = reducer(memoryState, action)136 listeners.forEach((listener) => {137 listener(memoryState)138 })139}140 141type Toast = Omit<ToasterToast, "id">142 143function toast({ ...props }: Toast) {144 const id = genId()145 146 const update = (props: ToasterToast) =>147 dispatch({148 type: "UPDATE_TOAST",149 toast: { ...props, id },150 })151 const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })152 153 dispatch({154 type: "ADD_TOAST",155 toast: {156 ...props,157 id,158 open: true,159 onOpenChange: (open) => {160 if (!open) dismiss()161 },162 },163 })164 165 return {166 id: id,167 dismiss,168 update,169 }170}171 172function useToast() {173 const [state, setState] = React.useState<State>(memoryState)174 175 React.useEffect(() => {176 listeners.push(setState)177 return () => {178 const index = listeners.indexOf(setState)179 if (index > -1) {180 listeners.splice(index, 1)181 }182 }183 }, [state])184 185 return {186 ...state,187 toast,188 dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),189 }190}191 192export { useToast, toast }193 