chore: add project files and setup gitignore
This commit is contained in:
75
reserva-mesa-dashboard/components/ui/toast.tsx
Normal file
75
reserva-mesa-dashboard/components/ui/toast.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, createContext, useContext, useCallback } from "react";
|
||||
import { X, CheckCircle2, AlertCircle, Info } from "lucide-react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
type ToastType = "success" | "error" | "info";
|
||||
|
||||
interface Toast {
|
||||
id: string;
|
||||
message: string;
|
||||
type: ToastType;
|
||||
}
|
||||
|
||||
interface ToastContextType {
|
||||
toast: (message: string, type?: ToastType) => void;
|
||||
}
|
||||
|
||||
const ToastContext = createContext<ToastContextType>({
|
||||
toast: () => {},
|
||||
});
|
||||
|
||||
export const useToast = () => useContext(ToastContext);
|
||||
|
||||
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
||||
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||
|
||||
const toast = React.useCallback((message: string, type: ToastType = "info") => {
|
||||
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={{ toast }}>
|
||||
{children}
|
||||
<div className="fixed bottom-6 right-6 z-50 flex flex-col gap-3 pointer-events-none">
|
||||
<AnimatePresence>
|
||||
{toasts.map((t) => (
|
||||
<motion.div
|
||||
key={t.id}
|
||||
initial={{ opacity: 0, y: 20, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95, transition: { duration: 0.2 } }}
|
||||
className={`pointer-events-auto flex items-center gap-3 px-4 py-3 rounded-xl border shadow-lg backdrop-blur-md min-w-[300px] ${
|
||||
t.type === "success" ? "bg-green-500/10 border-green-500/20 text-green-500" :
|
||||
t.type === "error" ? "bg-destructive/10 border-destructive/20 text-destructive" :
|
||||
"bg-primary/10 border-primary/20 text-primary"
|
||||
}`}
|
||||
>
|
||||
{t.type === "success" && <CheckCircle2 className="h-5 w-5 shrink-0" />}
|
||||
{t.type === "error" && <AlertCircle className="h-5 w-5 shrink-0" />}
|
||||
{t.type === "info" && <Info className="h-5 w-5 shrink-0" />}
|
||||
|
||||
<p className="text-sm font-medium flex-1">{t.message}</p>
|
||||
|
||||
<button
|
||||
onClick={() => removeToast(t.id)}
|
||||
className="p-1 rounded-md hover:bg-black/5 transition-colors"
|
||||
>
|
||||
<X className="h-4 w-4 opacity-50" />
|
||||
</button>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user