OwnGPT.v2 / client /src /components /UI /Toaster.jsx
parthib07's picture
Upload 199 files
212c959 verified
import * as Toast from '@radix-ui/react-toast'
import { AnimatePresence, motion } from 'framer-motion'
import { CheckCircle2, CircleAlert, Info, X } from 'lucide-react'
import { useAppStore } from '../../store/useAppStore'
const icons = {
success: CheckCircle2,
danger: CircleAlert,
info: Info,
}
export default function Toaster() {
const toasts = useAppStore((state) => state.toasts)
const removeToast = useAppStore((state) => state.removeToast)
return (
<Toast.Provider swipeDirection="right">
<AnimatePresence>
{toasts.map((toast) => {
const Icon = icons[toast.variant] || Info
return (
<Toast.Root
key={toast.id}
open
asChild
onOpenChange={(open) => {
if (!open) removeToast(toast.id)
}}
>
<motion.div
initial={{ opacity: 0, y: 16, scale: 0.96 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 16, scale: 0.96 }}
className="rounded-[24px] border border-border/70 bg-card/96 p-4 shadow-panel backdrop-blur-2xl"
>
<div className="flex items-start gap-3">
<div className="mt-0.5 rounded-2xl bg-secondary p-2 text-accent">
<Icon className="h-4 w-4" />
</div>
<div className="flex-1">
<Toast.Title className="text-sm font-semibold text-foreground">
{toast.title}
</Toast.Title>
{toast.description ? (
<Toast.Description className="mt-1 text-sm text-muted-foreground">
{toast.description}
</Toast.Description>
) : null}
</div>
<button
type="button"
onClick={() => removeToast(toast.id)}
className="rounded-full p-1 text-muted-foreground transition hover:bg-secondary hover:text-foreground"
>
<X className="h-4 w-4" />
</button>
</div>
</motion.div>
</Toast.Root>
)
})}
</AnimatePresence>
<Toast.Viewport className="fixed bottom-4 right-4 z-[70] flex w-[min(24rem,calc(100vw-2rem))] flex-col gap-3 outline-none" />
</Toast.Provider>
)
}