File size: 2,204 Bytes
cf47645
f33fa43
 
 
fbef5e8
cf47645
 
 
f33fa43
97c4991
cf47645
 
97c4991
cf47645
 
 
97c4991
427a3a2
cf47645
 
f33fa43
cf47645
9104321
cf47645
 
97c4991
f33fa43
 
cf47645
 
97c4991
 
fbef5e8
427a3a2
cf47645
 
 
 
 
 
 
 
 
 
 
 
 
fbef5e8
 
9104321
f33fa43
9104321
 
 
 
cf47645
 
f33fa43
cf47645
97c4991
cf47645
9104321
f33fa43
97c4991
cf47645
 
fbef5e8
cf47645
97c4991
 
 
 
 
cf47645
 
 
 
9104321
cf47645
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<script lang="ts">
	import { autoUpdate, computePosition } from "@floating-ui/dom";
	import { Toaster } from "melt/builders";
	import { type Snippet } from "svelte";
	import { type Attachment } from "svelte/attachments";
	import { fly } from "svelte/transition";

	interface Props {
		children: Snippet<[{ addToast: typeof toaster.addToast; trigger: typeof trigger }]>;
		toast?: Snippet<[{ toast: (typeof toaster.toasts)[0]; float: typeof float }]>;
		closeDelay?: number;
	}
	const { children, closeDelay = 2000, toast: toastSnippet }: Props = $props();

	const id = $props.id();

	export const trigger = {
		"data-local-toast-trigger": id,
	} as const;

	type ToastData = {
		content: string;
		variant: "info" | "danger";
	};

	export const toaster = new Toaster<ToastData>({
		hover: null,
		closeDelay: () => closeDelay,
	});

	export const addToast = toaster.addToast;

	const float: Attachment<HTMLElement> = function (node) {
		const triggerEl = document.querySelector(`[data-local-toast-trigger=${id}]`);
		if (!triggerEl) return;

		const compute = () =>
			computePosition(triggerEl, node, {
				placement: "top",
				strategy: "absolute",
			}).then(({ x, y }) => {
				Object.assign(node.style, {
					left: `${x}px`,
					top: `${y - 8}px`,
				});
			});

		return autoUpdate(triggerEl, node, compute);
	};

	const classMap: Record<ToastData["variant"], string> = {
		info: "border border-blue-400 bg-gradient-to-b from-blue-500 to-blue-600",

		danger: "border border-red-400 bg-gradient-to-b from-red-500 to-red-600",
	};
</script>

{@render children({ trigger, addToast: toaster.addToast })}

{#each toaster.toasts.slice(toaster.toasts.length - 1) as toast (toast.id)}
	<div
		data-local-toast
		data-variant={toast.data.variant}
		class={[!toastSnippet && `${classMap[toast.data.variant]} rounded-full px-2 py-1 text-xs`]}
		in:fly={{ y: 10 }}
		out:fly={{ y: -4 }}
		{@attach float}
	>
		{#if toastSnippet}
			{@render toastSnippet({ toast, float })}
		{:else}
			{toast.data.content}
		{/if}
	</div>
{/each}

<style>
	[data-local-toast] {
		/* Float on top of the UI */
		position: absolute;

		/* Avoid layout interference */
		width: max-content;
		top: 0;
		left: 0;
	}
</style>