85 lines
2.1 KiB
TypeScript
85 lines
2.1 KiB
TypeScript
import * as React from 'react'
|
|
|
|
import type { ToastActionElement, ToastProps } from '@/components/ui/toast'
|
|
|
|
const TOAST_LIMIT = 10
|
|
const TOAST_REMOVE_DELAY = 1000
|
|
|
|
type ToasterToast = ToastProps & {
|
|
id: string
|
|
title?: React.ReactNode
|
|
description?: React.ReactNode
|
|
action?: ToastActionElement
|
|
}
|
|
|
|
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
|
|
|
const addToRemoveQueue = (toastId: string) => {
|
|
if (toastTimeouts.has(toastId)) return
|
|
const timeout = setTimeout(() => {
|
|
toastTimeouts.delete(toastId)
|
|
toastState.remove(toastId)
|
|
}, TOAST_REMOVE_DELAY)
|
|
toastTimeouts.set(toastId, timeout)
|
|
}
|
|
|
|
const createToastState = () => {
|
|
const listeners = new Set<(toasts: ToasterToast[]) => void>()
|
|
let toasts: ToasterToast[] = []
|
|
|
|
const notify = () => {
|
|
listeners.forEach((listener) => listener(toasts))
|
|
}
|
|
|
|
return {
|
|
subscribe: (listener: (toasts: ToasterToast[]) => void) => {
|
|
listeners.add(listener)
|
|
return () => {
|
|
listeners.delete(listener)
|
|
}
|
|
},
|
|
add: (toast: ToasterToast) => {
|
|
toasts = [toast, ...toasts].slice(0, TOAST_LIMIT)
|
|
notify()
|
|
},
|
|
update: (toastId: string, toast: Partial<ToasterToast>) => {
|
|
toasts = toasts.map((item) => (item.id === toastId ? { ...item, ...toast } : item))
|
|
notify()
|
|
},
|
|
dismiss: (toastId: string) => {
|
|
addToRemoveQueue(toastId)
|
|
},
|
|
remove: (toastId: string) => {
|
|
toasts = toasts.filter((item) => item.id !== toastId)
|
|
notify()
|
|
},
|
|
getSnapshot: () => toasts,
|
|
}
|
|
}
|
|
|
|
const toastState = createToastState()
|
|
|
|
export const useToast = () => {
|
|
const [toasts, setToasts] = React.useState<ToasterToast[]>(toastState.getSnapshot())
|
|
|
|
React.useEffect(() => toastState.subscribe(setToasts), [])
|
|
|
|
const toast = React.useCallback((props: Omit<ToasterToast, 'id'>) => {
|
|
const id = crypto.randomUUID()
|
|
toastState.add({ id, ...props })
|
|
return id
|
|
}, [])
|
|
|
|
const dismiss = React.useCallback((toastId: string) => {
|
|
toastState.dismiss(toastId)
|
|
}, [])
|
|
|
|
return {
|
|
toast,
|
|
dismiss,
|
|
toasts,
|
|
}
|
|
}
|
|
|
|
export type { ToastProps, ToasterToast }
|