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>
|