Path: blob/master/webhooks/webhook_preview/hooks/use-toast.ts
1091 views
"use client"12// Inspired by react-hot-toast library3import * as React from "react"45import type {6ToastActionElement,7ToastProps,8} from "@/components/ui/toast"910const TOAST_LIMIT = 111const TOAST_REMOVE_DELAY = 10000001213type ToasterToast = ToastProps & {14id: string15title?: React.ReactNode16description?: React.ReactNode17action?: ToastActionElement18}1920const actionTypes = {21ADD_TOAST: "ADD_TOAST",22UPDATE_TOAST: "UPDATE_TOAST",23DISMISS_TOAST: "DISMISS_TOAST",24REMOVE_TOAST: "REMOVE_TOAST",25} as const2627let count = 02829function genId() {30count = (count + 1) % Number.MAX_SAFE_INTEGER31return count.toString()32}3334type ActionType = typeof actionTypes3536type Action =37| {38type: ActionType["ADD_TOAST"]39toast: ToasterToast40}41| {42type: ActionType["UPDATE_TOAST"]43toast: Partial<ToasterToast>44}45| {46type: ActionType["DISMISS_TOAST"]47toastId?: ToasterToast["id"]48}49| {50type: ActionType["REMOVE_TOAST"]51toastId?: ToasterToast["id"]52}5354interface State {55toasts: ToasterToast[]56}5758const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()5960const addToRemoveQueue = (toastId: string) => {61if (toastTimeouts.has(toastId)) {62return63}6465const timeout = setTimeout(() => {66toastTimeouts.delete(toastId)67dispatch({68type: "REMOVE_TOAST",69toastId: toastId,70})71}, TOAST_REMOVE_DELAY)7273toastTimeouts.set(toastId, timeout)74}7576export const reducer = (state: State, action: Action): State => {77switch (action.type) {78case "ADD_TOAST":79return {80...state,81toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),82}8384case "UPDATE_TOAST":85return {86...state,87toasts: state.toasts.map((t) =>88t.id === action.toast.id ? { ...t, ...action.toast } : t89),90}9192case "DISMISS_TOAST": {93const { toastId } = action9495// ! Side effects ! - This could be extracted into a dismissToast() action,96// but I'll keep it here for simplicity97if (toastId) {98addToRemoveQueue(toastId)99} else {100state.toasts.forEach((toast) => {101addToRemoveQueue(toast.id)102})103}104105return {106...state,107toasts: state.toasts.map((t) =>108t.id === toastId || toastId === undefined109? {110...t,111open: false,112}113: t114),115}116}117case "REMOVE_TOAST":118if (action.toastId === undefined) {119return {120...state,121toasts: [],122}123}124return {125...state,126toasts: state.toasts.filter((t) => t.id !== action.toastId),127}128}129}130131const listeners: Array<(state: State) => void> = []132133let memoryState: State = { toasts: [] }134135function dispatch(action: Action) {136memoryState = reducer(memoryState, action)137listeners.forEach((listener) => {138listener(memoryState)139})140}141142type Toast = Omit<ToasterToast, "id">143144function toast({ ...props }: Toast) {145const id = genId()146147const update = (props: ToasterToast) =>148dispatch({149type: "UPDATE_TOAST",150toast: { ...props, id },151})152const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })153154dispatch({155type: "ADD_TOAST",156toast: {157...props,158id,159open: true,160onOpenChange: (open) => {161if (!open) dismiss()162},163},164})165166return {167id: id,168dismiss,169update,170}171}172173function useToast() {174const [state, setState] = React.useState<State>(memoryState)175176React.useEffect(() => {177listeners.push(setState)178return () => {179const index = listeners.indexOf(setState)180if (index > -1) {181listeners.splice(index, 1)182}183}184}, [state])185186return {187...state,188toast,189dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),190}191}192193export { useToast, toast }194195196