beerohan
Rebuild frontend in React, add doc/session management, fix broken deps
dd4a03a
Raw History Blame Contribute Delete
1.89 kB
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react'
import type { ReactNode } from 'react'
import './toast.css'
export type ToastType = 'success' | 'error' | 'info'
interface ToastItem {
id: string
message: string
type: ToastType
visible: boolean
}
interface ToastContextValue {
showToast: (message: string, type?: ToastType, duration?: number) => void
}
const ToastContext = createContext<ToastContextValue | undefined>(undefined)
export function ToastProvider({ children }: { children: ReactNode }) {
const [toasts, setToasts] = useState<ToastItem[]>([])
const counterRef = useRef(0)
const showToast = useCallback((message: string, type: ToastType = 'info', duration = 4000) => {
const id = `toast_${Date.now()}_${counterRef.current++}`
setToasts((prev) => [...prev, { id, message, type, visible: false }])
// Trigger enter animation on next frame.
requestAnimationFrame(() => {
setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, visible: true } : t)))
})
setTimeout(() => {
setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, visible: false } : t)))
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== id))
}, 300)
}, duration)
}, [])
const value = useMemo(() => ({ showToast }), [showToast])
return (
<ToastContext.Provider value={value}>
{children}
<div className="toast-container" aria-live="polite">
{toasts.map((t) => (
<div key={t.id} className={`toast toast-${t.type}${t.visible ? ' show' : ''}`}>
{t.message}
</div>
))}
</div>
</ToastContext.Provider>
)
}
export function useToast(): ToastContextValue {
const ctx = useContext(ToastContext)
if (!ctx) {
throw new Error('useToast must be used within a ToastProvider')
}
return ctx
}