import { toast } from "sonner"; import { cn } from "@/shared/ui/cn"; import { MAP_ICON_CELL_RADIUS_CLASS_NAME } from "./map-control-styles"; import { MapNoticeCloseIcon, MapNoticeContent } from "./notice-presentation"; import type { MapNoticeOptions, MapNoticePosition } from "./notice-types"; const DEFAULT_POSITION: MapNoticePosition = "top-center"; const pendingNotices = new Map void>(); let nextNoticeId = 0; export function showMapNotice(options: MapNoticeOptions) { const id = options.id ?? `map-notice:${++nextNoticeId}`; const tone = options.tone ?? "info"; const position = options.position ?? DEFAULT_POSITION; const hasAction = Boolean(options.actionLabel && options.onAction); // Actionable notices must remain available even when a caller supplies a timeout. const duration = hasAction ? Infinity : options.duration ?? (tone === "info" || tone === "success" ? 3200 : Infinity); const dismissible = hasAction || (options.dismissible ?? tone !== "loading"); const publish = () => toast.custom( () => , { id, duration, position, dismissible, cancel: dismissible ? { label: , onClick: (event) => event.stopPropagation() } : undefined, classNames: { cancelButton: cn( "pointer-events-auto absolute right-2 top-2 z-10 grid h-8 w-8 place-items-center border border-transparent bg-transparent p-0 text-[var(--ink-muted)] after:absolute after:-inset-1 after:content-[''] hover:border-[var(--surface-divider)] hover:bg-[var(--surface-reading)] hover:text-slate-700", MAP_ICON_CELL_RADIUS_CLASS_NAME ) }, className: "w-full" } ); // Publish after React finishes effects: Sonner renews its subscription during // those effects and can otherwise miss an alert while map sources update. pendingNotices.set(id, publish); queueMicrotask(() => { if (pendingNotices.get(id) !== publish) return; pendingNotices.delete(id); publish(); }); return id; } export function dismissMapNotice(id?: string | number) { if (id === undefined) pendingNotices.clear(); else pendingNotices.delete(id); toast.dismiss(id); }