inference-playground / src /lib /components /local-toasts.svelte
Thomas G. Lopes
add copy project option
cf47645
raw
history blame
1.8 kB
<script lang="ts">
import { onDestroy, type Snippet } from "svelte";
import { computePosition, autoUpdate } from "@floating-ui/dom";
import { fly } from "svelte/transition";
interface Props {
children: Snippet<[{ addToast: typeof addToast; trigger: typeof trigger }]>;
closeDelay?: number;
}
const { children, closeDelay = 2000 }: Props = $props();
const id = $props.id();
const trigger = {
id,
} as const;
type Toast = {
content: string;
id: string;
};
let toasts = $state<Toast[]>([]);
let timeouts: ReturnType<typeof window.setTimeout>[] = [];
function addToast(content: string) {
const id = crypto.randomUUID();
const timeout = setTimeout(() => {
toasts = toasts.filter(t => t.id !== id);
timeouts = timeouts.filter(t => t !== timeout);
}, closeDelay);
toasts.push({ content, id });
timeouts.push(timeout);
}
onDestroy(() => {
timeouts.forEach(t => clearTimeout(t));
});
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),
};
}
</script>
{@render children({ trigger, addToast })}
{#each toasts as toast (toast.id)}
<div
class="rounded-full border border-blue-400 bg-gradient-to-b from-blue-500 to-blue-600 px-2 py-1 text-xs"
in:fly={{ y: 10 }}
out:fly={{ y: -4 }}
use:float
>
{toast.content}
</div>
{/each}
<style>
div {
/* Float on top of the UI */
position: absolute;
/* Avoid layout interference */
width: max-content;
top: 0;
left: 0;
}
</style>