2026-01-15 11:31:09 +01:00

70 lines
2.0 KiB
TypeScript

import React, { useState, useCallback } from 'react'
import { Toast, type ToastVariant } from './Toast'
interface ToastMessage {
id: string
message: string
variant: ToastVariant
duration?: number
}
interface ToastContextValue {
showToast: (message: string, variant?: ToastVariant, duration?: number) => void
}
const ToastContext = React.createContext<ToastContextValue | undefined>(undefined)
function generateToastId(): string {
return `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
}
export function useToast(): ToastContextValue {
const context = React.useContext(ToastContext)
if (!context) {
throw new Error('useToast must be used within ToastProvider')
}
return context
}
export function ToastProvider({ children }: { children: React.ReactNode }): React.ReactElement {
const [toasts, setToasts] = useState<ToastMessage[]>([])
const showToast = useCallback((message: string, variant: ToastVariant = 'info', duration = 5000): void => {
const id = generateToastId()
setToasts((prev) => [...prev, { id, message, variant, duration }])
}, [])
const removeToast = useCallback((id: string): void => {
setToasts((prev) => prev.filter((toast) => toast.id !== id))
}, [])
const contextValue: ToastContextValue = {
showToast,
}
return (
<ToastContext.Provider value={contextValue}>
{children}
<div
className="fixed top-4 right-4 z-50 space-y-2 pointer-events-none"
role="region"
aria-live="polite"
aria-label="Notifications"
>
{toasts.map((toast) => (
<div key={toast.id} className="pointer-events-auto">
<Toast
variant={toast.variant}
{...(toast.duration !== undefined ? { duration: toast.duration } : {})}
onClose={() => removeToast(toast.id)}
aria-label={toast.message}
>
{toast.message}
</Toast>
</div>
))}
</div>
</ToastContext.Provider>
)
}