Repository Public
1'use client';2import Link from 'next/link';3import { X } from 'lucide-react';4import { useToasts, type ToastTone } from '@/stores/toasts';5import { cn } from '@/lib/cn';67const BAR: Record<ToastTone, string> = { info: 'bg-accent', success: 'bg-success-vivid', warning: 'bg-warning-vivid', error: 'bg-error-vivid' };89export function Toaster() {10 const { toasts, dismiss } = useToasts();11 return (12 <div aria-live="polite" className="pointer-events-none fixed inset-x-3 bottom-[calc(env(safe-area-inset-bottom)+92px)] z-[90] mx-auto flex max-w-md flex-col gap-2 md:inset-x-auto md:bottom-14 md:right-4 md:mx-0 md:w-[360px]">13 {toasts.map((t) => {14 const body = (15 <>16 <p className="text-[13px] font-medium text-text">{t.title}</p>17 {t.body && <p className="mt-0.5 text-xs text-text-2">{t.body}</p>}18 </>19 );20 return (21 <div key={t.id} role="status" className="anim-pop pointer-events-auto relative flex overflow-hidden rounded-md border border-card-line bg-elevated shadow-float">22 <span className={cn('w-1 shrink-0', BAR[t.tone])} />23 {t.href ? (24 <Link href={t.href} onClick={() => dismiss(t.id)} className="min-w-0 flex-1 px-3 py-2.5">25 {body}26 </Link>27 ) : (28 <div className="min-w-0 flex-1 px-3 py-2.5">{body}</div>29 )}30 <button aria-label="Dismiss notification" onClick={() => dismiss(t.id)} className="m-1 inline-flex size-7 shrink-0 items-center justify-center rounded-sm text-muted hover:bg-hover hover:text-text pointer-coarse:size-11">31 <X className="size-3.5" />32 </button>33 </div>34 );35 })}36 </div>37 );38}39