54 lines
2.2 KiB
TypeScript
54 lines
2.2 KiB
TypeScript
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<string | number, () => 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(
|
|
() => <MapNoticeContent {...options} tone={tone} />,
|
|
{
|
|
id, duration, position, dismissible,
|
|
cancel: dismissible
|
|
? { label: <MapNoticeCloseIcon />, 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);
|
|
}
|