ToastContext.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  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. const id = Date.now().toString();
  32. const newToast: ToastItem = { id, type, message, title, duration };
  33. setToasts(prev => {
  34. // 相同消息去重:如果已存在相同的消息(类型和内容相同),则先移除旧的
  35. const filtered = prev.filter(t => t.message !== message || t.type !== type);
  36. return [...filtered, newToast];
  37. });
  38. };
  39. const removeToast = (id: string) => {
  40. setToasts(prev => prev.filter(toast => toast.id !== id));
  41. };
  42. const showSuccess = (message: string, title?: string) => showToast('success', message, title);
  43. const showError = (message: string, title?: string) => showToast('error', message, title);
  44. const showWarning = (message: string, title?: string) => showToast('warning', message, title);
  45. const showInfo = (message: string, title?: string) => showToast('info', message, title);
  46. return (
  47. <ToastContext.Provider value={{ showToast, showSuccess, showError, showWarning, showInfo }}>
  48. {children}
  49. <div className="fixed bottom-0 right-0 z-[9999] p-4 space-y-3 pointer-events-none w-full max-w-sm flex flex-col items-end">
  50. {toasts.map((toast) => (
  51. <div
  52. key={toast.id}
  53. className="pointer-events-auto w-full"
  54. >
  55. <Toast
  56. type={toast.type}
  57. title={toast.title}
  58. message={toast.message}
  59. duration={toast.duration}
  60. onClose={() => removeToast(toast.id)}
  61. />
  62. </div>
  63. ))}
  64. </div>
  65. </ToastContext.Provider>
  66. );
  67. };