refactor: Reorganize frontend by moving UI components, hooks, and utilities to new components, logic, features, and lib directories
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { createSafeContext } from '../../utils/createSafeContext';
|
||||
|
||||
export type ToastVariant = 'info' | 'success' | 'error';
|
||||
|
||||
interface ToastMessage {
|
||||
id: string;
|
||||
message: string;
|
||||
variant: ToastVariant;
|
||||
timestamp: number;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
interface StatusToastContextValue {
|
||||
toasts: ToastMessage[];
|
||||
showToast: (message: string, variant?: ToastVariant, duration?: number) => void;
|
||||
removeToast: (id: string) => void;
|
||||
}
|
||||
|
||||
const [StatusToastContext, useStatusToast] = createSafeContext<StatusToastContextValue>('StatusToast');
|
||||
|
||||
const DEFAULT_DURATIONS: Record<ToastVariant, number> = {
|
||||
success: 3000,
|
||||
info: 5000,
|
||||
error: 8000,
|
||||
};
|
||||
|
||||
const MAX_TOASTS = 3;
|
||||
|
||||
export const StatusToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [toasts, setToasts] = useState<ToastMessage[]>([]);
|
||||
const timeoutRefs = useRef<Map<string, number>>(new Map());
|
||||
|
||||
const removeToast = useCallback((id: string) => {
|
||||
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
||||
|
||||
// Clear timeout if it exists
|
||||
const timeout = timeoutRefs.current.get(id);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeoutRefs.current.delete(id);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const showToast = useCallback((message: string, variant: ToastVariant = 'info', duration?: number) => {
|
||||
const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
const finalDuration = duration ?? DEFAULT_DURATIONS[variant];
|
||||
|
||||
const newToast: ToastMessage = {
|
||||
id,
|
||||
message,
|
||||
variant,
|
||||
timestamp: Date.now(),
|
||||
duration: finalDuration,
|
||||
};
|
||||
|
||||
setToasts((prev) => {
|
||||
const updated = [...prev, newToast];
|
||||
|
||||
// If we exceed max toasts, remove the oldest ones
|
||||
if (updated.length > MAX_TOASTS) {
|
||||
const removed = updated.slice(0, updated.length - MAX_TOASTS);
|
||||
removed.forEach((toast) => {
|
||||
const timeout = timeoutRefs.current.get(toast.id);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeoutRefs.current.delete(toast.id);
|
||||
}
|
||||
});
|
||||
return updated.slice(-MAX_TOASTS);
|
||||
}
|
||||
|
||||
return updated;
|
||||
});
|
||||
|
||||
// Set auto-dismiss timeout - trigger fade then remove
|
||||
const timeout = setTimeout(() => {
|
||||
removeToast(id);
|
||||
}, finalDuration);
|
||||
|
||||
timeoutRefs.current.set(id, timeout);
|
||||
}, [removeToast]);
|
||||
|
||||
// Cleanup all timeouts on unmount
|
||||
useEffect(() => {
|
||||
const timeouts = timeoutRefs.current;
|
||||
return () => {
|
||||
timeouts.forEach((timeout) => clearTimeout(timeout));
|
||||
timeouts.clear();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const value: StatusToastContextValue = {
|
||||
toasts,
|
||||
showToast,
|
||||
removeToast,
|
||||
};
|
||||
|
||||
return (
|
||||
<StatusToastContext.Provider value={value}>
|
||||
{children}
|
||||
</StatusToastContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export { useStatusToast };
|
||||
Reference in New Issue
Block a user