Feat: Cambios Varios 2

This commit is contained in:
2026-01-05 10:30:04 -03:00
parent 8bc1308bc5
commit 0fa77e4a98
184 changed files with 11098 additions and 6348 deletions

View File

@@ -0,0 +1,69 @@
import React, { useState, useCallback } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { CheckCircle2, AlertCircle, X, Info } from 'lucide-react';
import { clsx } from 'clsx';
import { ToastContext, type Toast, type ToastType } from './use-toast';
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]);
const showToast = useCallback((message: string, type: ToastType) => {
const id = Math.random().toString(36).substring(2, 9);
setToasts((prev) => [...prev, { id, message, type }]);
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, 5000);
}, []);
const removeToast = (id: string) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
};
return (
<ToastContext.Provider value={{ showToast }}>
{children}
<div className="fixed bottom-8 right-8 z-[1000] flex flex-col gap-3 pointer-events-none">
<AnimatePresence mode="popLayout">
{toasts.map((toast) => (
<motion.div
key={toast.id}
layout
initial={{ opacity: 0, x: 50, scale: 0.8 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
exit={{ opacity: 0, x: 20, scale: 0.5, transition: { duration: 0.2 } }}
className={clsx(
"pointer-events-auto flex items-center gap-3 px-5 py-4 rounded-2xl shadow-2xl border backdrop-blur-md min-w-[300px] max-w-md",
toast.type === 'success' && "bg-emerald-50/90 border-emerald-100 text-emerald-900",
toast.type === 'error' && "bg-rose-50/90 border-rose-100 text-rose-900",
toast.type === 'info' && "bg-blue-50/90 border-blue-100 text-blue-900"
)}
>
<div className={clsx(
"p-2 rounded-xl",
toast.type === 'success' && "bg-emerald-500 text-white",
toast.type === 'error' && "bg-rose-500 text-white",
toast.type === 'info' && "bg-blue-500 text-white"
)}>
{toast.type === 'success' && <CheckCircle2 size={18} />}
{toast.type === 'error' && <AlertCircle size={18} />}
{toast.type === 'info' && <Info size={18} />}
</div>
<div className="flex-1">
<p className="text-sm font-black tracking-tight leading-tight">{toast.message}</p>
</div>
<button
onClick={() => removeToast(toast.id)}
className="p-1 hover:bg-black/5 rounded-lg transition-colors text-slate-400"
>
<X size={16} />
</button>
</motion.div>
))}
</AnimatePresence>
</div>
</ToastContext.Provider>
);
}

View File

@@ -0,0 +1,21 @@
import { createContext, useContext } from 'react';
export type ToastType = 'success' | 'error' | 'info';
export interface Toast {
id: string;
message: string;
type: ToastType;
}
export interface ToastContextType {
showToast: (message: string, type: ToastType) => void;
}
export const ToastContext = createContext<ToastContextType | undefined>(undefined);
export const useToast = () => {
const context = useContext(ToastContext);
if (!context) throw new Error('useToast must be used within ToastProvider');
return context;
};