File size: 2,089 Bytes
cf47645 f33fa43 cf47645 f33fa43 97c4991 cf47645 97c4991 cf47645 97c4991 cf47645 f33fa43 cf47645 9104321 cf47645 97c4991 f33fa43 cf47645 97c4991 cf47645 9104321 f33fa43 9104321 cf47645 f33fa43 cf47645 97c4991 cf47645 9104321 f33fa43 97c4991 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 89 |
<script lang="ts">
import { autoUpdate, computePosition } from "@floating-ui/dom";
import { Toaster } from "melt/builders";
import { type Snippet } from "svelte";
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 = {
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;
function float(node: HTMLElement) {
const triggerEl = document.getElementById(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 {
destroy: 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 }}
use: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>
|