test_task_crm/frontend/src/components/ui/use-toast.ts

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 }