65 lines
1.9 KiB
TypeScript
65 lines
1.9 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">
|
|
{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>
|
|
)
|
|
}
|