fiddy/apps/web/components/notifications-toaster.tsx
2026-02-11 23:45:15 -08:00

56 lines
2.0 KiB
TypeScript

"use client";
import type { ReactNode } from "react";
export type NotificationTone = "info" | "success" | "danger";
export type NotificationItem = {
id: string;
title: string;
message?: string;
tone: NotificationTone;
closing: boolean;
};
type NotificationsToasterProps = {
items: NotificationItem[];
onDismiss: (id: string) => void;
};
function toneClasses(tone: NotificationTone) {
if (tone === "success") return "border-emerald-400/40 text-emerald-200";
if (tone === "danger") return "border-red-400/50 text-red-200";
return "border-accent-weak text-[color:var(--color-text)]";
}
export default function NotificationsToaster({ items, onDismiss }: NotificationsToasterProps) {
if (!items.length) return null;
return (
<div className="fixed bottom-4 right-4 z-[60] flex w-80 flex-col-reverse gap-2 pointer-events-none">
{items.map(item => (
<div
key={item.id}
className={`pointer-events-auto rounded-xl border bg-panel px-3 py-2 text-sm shadow-lg transition-all duration-300 ${toneClasses(item.tone)} ${item.closing ? "opacity-0 translate-y-2" : "opacity-100"}`}
role="status"
>
<div className="flex items-start justify-between gap-2">
<div>
<div className="font-semibold">{item.title}</div>
{item.message ? <div className="text-xs text-soft">{item.message}</div> : null}
</div>
<button
type="button"
className="rounded-md border border-accent-weak px-1.5 py-0.5 text-xs text-soft hover:border-accent"
onClick={() => onDismiss(item.id)}
aria-label="Dismiss notification"
>
Close
</button>
</div>
</div>
))}
</div>
);
}