ToastContext.tsx 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import React, { createContext, useContext, useState, ReactNode } from 'react';
  2. import Toast, { ToastType } from '../components/Toast';
  3. interface ToastItem {
  4. id: string;
  5. type: ToastType;
  6. title?: string;
  7. message: string;
  8. duration?: number;
  9. }
  10. interface ToastContextType {
  11. showToast: (type: ToastType, message: string, title?: string, duration?: number) => void;
  12. showSuccess: (message: string, title?: string) => void;
  13. showError: (message: string, title?: string) => void;
  14. showWarning: (message: string, title?: string) => void;
  15. showInfo: (message: string, title?: string) => void;
  16. }
  17. const ToastContext = createContext<ToastContextType | undefined>(undefined);
  18. export const useToast = () => {
  19. const context = useContext(ToastContext);
  20. if (!context) {
  21. throw new Error('useToast must be used within a ToastProvider');
  22. }
  23. return context;
  24. };
  25. interface ToastProviderProps {
  26. children: ReactNode;
  27. }
  28. export const ToastProvider: React.FC<ToastProviderProps> = ({ children }) => {
  29. const [toasts, setToasts] = useState<ToastItem[]>([]);
  30. const showToast = (type: ToastType, message: string, title?: string, duration?: number) => {
  31. // Deduplication check
  32. const isDuplicate = toasts.some(t => t.message === message && t.type === type);
  33. if (isDuplicate) {
  34. return;
  35. }
  36. const id = Date.now().toString();
  37. const newToast: ToastItem = { id, type, message, title, duration };
  38. setToasts(prev => {
  39. const updated = [...prev, newToast];
  40. if (updated.length > 5) {
  41. return updated.slice(updated.length - 5);
  42. }
  43. return updated;
  44. });
  45. };
  46. const removeToast = (id: string) => {
  47. setToasts(prev => prev.filter(toast => toast.id !== id));
  48. };
  49. const showSuccess = (message: string, title?: string) => showToast('success', message, title);
  50. const showError = (message: string, title?: string) => showToast('error', message, title);
  51. const showWarning = (message: string, title?: string) => showToast('warning', message, title);
  52. const showInfo = (message: string, title?: string) => showToast('info', message, title);
  53. return (
  54. <ToastContext.Provider value={{ showToast, showSuccess, showError, showWarning, showInfo }}>
  55. {children}
  56. <div className="fixed bottom-4 right-4 z-[9999] flex flex-col-reverse gap-2 pointer-events-none p-4">
  57. {toasts.map((toast) => (
  58. <div
  59. key={toast.id}
  60. className="pointer-events-auto transition-all duration-300 ease-in-out"
  61. >
  62. <Toast
  63. type={toast.type}
  64. title={toast.title}
  65. message={toast.message}
  66. duration={toast.duration}
  67. onClose={() => removeToast(toast.id)}
  68. />
  69. </div>
  70. ))}
  71. </div>
  72. </ToastContext.Provider>
  73. );
  74. };