veza/apps/web/src/components/feedback/ToastProvider.tsx
2025-12-12 21:34:34 -05:00

98 lines
2.2 KiB
TypeScript

import {
createContext,
useContext,
useState,
useCallback,
ReactNode,
} from 'react';
import { Toast, ToastComponent } from './Toast';
import { cn } from '@/lib/utils';
interface ToastContextValue {
toasts: Toast[];
addToast: (toast: Omit<Toast, 'id'>) => void;
removeToast: (id: string) => void;
}
const ToastContext = createContext<ToastContextValue | undefined>(undefined);
export function useToastContext() {
const context = useContext(ToastContext);
if (!context) {
throw new Error('useToastContext must be used within ToastProvider');
}
return context;
}
export interface ToastProviderProps {
children: ReactNode;
position?:
| 'top-right'
| 'top-left'
| 'bottom-right'
| 'bottom-left'
| 'top-center'
| 'bottom-center';
className?: string;
}
const POSITION_CLASSES = {
'top-right': 'top-4 right-4',
'top-left': 'top-4 left-4',
'bottom-right': 'bottom-4 right-4',
'bottom-left': 'bottom-4 left-4',
'top-center': 'top-4 left-1/2 -translate-x-1/2',
'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2',
};
/**
* Provider pour gérer la queue des toasts.
*/
export function ToastProvider({
children,
position = 'top-right',
className,
}: ToastProviderProps) {
const [toasts, setToasts] = useState<Toast[]>([]);
const addToast = useCallback((toast: Omit<Toast, 'id'>) => {
const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const newToast: Toast = {
...toast,
id,
};
setToasts((prev) => [...prev, newToast]);
}, []);
const removeToast = useCallback((id: string) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
}, []);
const value: ToastContextValue = {
toasts,
addToast,
removeToast,
};
return (
<ToastContext.Provider value={value}>
{children}
<div
className={cn(
'fixed z-50 flex flex-col gap-2',
POSITION_CLASSES[position],
className,
)}
>
{toasts.map((toast) => (
<ToastComponent
key={toast.id}
toast={toast}
onDismiss={removeToast}
/>
))}
</div>
</ToastContext.Provider>
);
}