feat: Initialize ComFi project with Vite
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.
This commit is contained in:
82
contexts/ToastContext.tsx
Normal file
82
contexts/ToastContext.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user