Setup project structure, dependencies, and basic configuration for the ComFi application. Includes initial setup for Vite, React, TypeScript, Tailwind CSS, and essential development tools. Defines core types and provides a basic README for local development.
83 lines
3.3 KiB
TypeScript
83 lines
3.3 KiB
TypeScript
|
|
import React, { createContext, useContext, useState, useCallback } from 'react';
|
|
import { Toast } from '../types';
|
|
import { X, CheckCircle2, AlertCircle, Info, AlertTriangle } from 'lucide-react';
|
|
|
|
interface ToastContextData {
|
|
addToast: (toast: Omit<Toast, 'id'>) => void;
|
|
removeToast: (id: string) => void;
|
|
}
|
|
|
|
const ToastContext = createContext<ToastContextData>({} as ToastContextData);
|
|
|
|
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
|
|
const addToast = useCallback(({ type, title, message, duration = 4000 }: Omit<Toast, 'id'>) => {
|
|
const id = Math.random().toString(36).substring(2, 9);
|
|
const newToast = { id, type, title, message, duration };
|
|
|
|
setToasts((state) => [...state, newToast]);
|
|
|
|
if (duration > 0) {
|
|
setTimeout(() => {
|
|
removeToast(id);
|
|
}, duration);
|
|
}
|
|
}, []);
|
|
|
|
const removeToast = useCallback((id: string) => {
|
|
setToasts((state) => state.filter((toast) => toast.id !== id));
|
|
}, []);
|
|
|
|
return (
|
|
<ToastContext.Provider value={{ addToast, removeToast }}>
|
|
{children}
|
|
{/* Toast Container Rendered Here Globally */}
|
|
<div className="fixed top-4 right-4 z-[9999] flex flex-col gap-2 pointer-events-none">
|
|
{toasts.map((toast) => (
|
|
<div
|
|
key={toast.id}
|
|
className={`pointer-events-auto min-w-[300px] max-w-sm rounded-xl p-4 shadow-xl border flex items-start gap-3 animate-slide-in-right transform transition-all duration-300 ${
|
|
toast.type === 'success' ? 'bg-white border-green-100' :
|
|
toast.type === 'error' ? 'bg-white border-red-100' :
|
|
toast.type === 'warning' ? 'bg-white border-amber-100' :
|
|
'bg-white border-blue-100'
|
|
}`}
|
|
>
|
|
<div className={`mt-0.5 ${
|
|
toast.type === 'success' ? 'text-green-500' :
|
|
toast.type === 'error' ? 'text-red-500' :
|
|
toast.type === 'warning' ? 'text-amber-500' :
|
|
'text-blue-500'
|
|
}`}>
|
|
{toast.type === 'success' && <CheckCircle2 size={20} />}
|
|
{toast.type === 'error' && <AlertCircle size={20} />}
|
|
{toast.type === 'warning' && <AlertTriangle size={20} />}
|
|
{toast.type === 'info' && <Info size={20} />}
|
|
</div>
|
|
<div className="flex-1">
|
|
<h4 className={`text-sm font-bold ${
|
|
toast.type === 'success' ? 'text-green-800' :
|
|
toast.type === 'error' ? 'text-red-800' :
|
|
toast.type === 'warning' ? 'text-amber-800' :
|
|
'text-blue-800'
|
|
}`}>{toast.title}</h4>
|
|
{toast.message && <p className="text-xs text-slate-500 mt-1 leading-relaxed">{toast.message}</p>}
|
|
</div>
|
|
<button onClick={() => removeToast(toast.id)} className="text-slate-400 hover:text-slate-600">
|
|
<X size={16} />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</ToastContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useToast = () => {
|
|
const context = useContext(ToastContext);
|
|
if (!context) throw new Error('useToast must be used within a ToastProvider');
|
|
return context;
|
|
};
|