Files
hope-events/frontend/src/hooks/use-toast.ts
T
joshua 3d381944d2 Initial commit
Next.js + Express event management app for Hope Family Church.
2026-07-23 15:26:47 +02:00

111 lines
2.4 KiB
TypeScript

import * as React from "react"
import type { ToastActionElement, ToastProps } from "@/components/ui/toast"
// Based on shadcn/ui toast hook implementation
export type Toast = Omit<ToastProps, "id"> & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const TOAST_LIMIT = 5
const TOAST_REMOVE_DELAY = 1000
type State = {
toasts: Toast[]
}
type ToastInput = Omit<Toast, "id">
type Listener = (state: State) => void
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const state: State = { toasts: [] }
const listeners: Listener[] = []
function setState(newState: Partial<State>) {
Object.assign(state, newState)
listeners.forEach((l) => l(state))
}
function addToast(toast: ToastInput) {
const id = genId()
const newToast: Toast = {
...toast,
id,
open: true as any,
}
// Ensure limit
const nextToasts = [newToast, ...state.toasts].slice(0, TOAST_LIMIT)
setState({ toasts: nextToasts })
return id
}
function updateToast(id: string, update: Partial<Toast>) {
setState({
toasts: state.toasts.map((t) => (t.id === id ? { ...t, ...update } : t)),
})
}
function dismissToast(id?: string) {
if (id) {
queueRemoval(id)
updateToast(id, { open: false } as any)
} else {
state.toasts.forEach((t) => {
queueRemoval(t.id)
updateToast(t.id, { open: false } as any)
})
}
}
function removeToast(id?: string) {
if (id) {
setState({ toasts: state.toasts.filter((t) => t.id !== id) })
} else {
setState({ toasts: [] })
}
}
function queueRemoval(id: string) {
if (toastTimeouts.has(id)) return
const timeout = setTimeout(() => {
toastTimeouts.delete(id)
removeToast(id)
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(id, timeout)
}
export function useToast() {
const [localState, setLocalState] = React.useState<State>(state)
React.useEffect(() => {
listeners.push(setLocalState)
return () => {
const index = listeners.indexOf(setLocalState)
if (index > -1) listeners.splice(index, 1)
}
}, [])
return {
...localState,
toast: ({ ...props }: ToastInput) => addToast(props),
dismiss: (id?: string) => dismissToast(id),
remove: (id?: string) => removeToast(id),
}
}
export type { ToastActionElement, ToastProps }