| 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> |
| ) |
| } |
|
|