| | <script lang="ts"> |
| | import { onDestroy, onMount } from "svelte"; |
| | import { cubicOut } from "svelte/easing"; |
| | import { fade, fly } from "svelte/transition"; |
| | import Portal from "./Portal.svelte"; |
| | import { browser } from "$app/environment"; |
| | import CarbonClose from "~icons/carbon/close"; |
| | |
| | interface Props { |
| | width?: string; |
| | closeButton?: boolean; |
| | disableFly?: boolean; |
| | |
| | closeOnBackdrop?: boolean; |
| | onclose?: () => void; |
| | children?: import("svelte").Snippet; |
| | } |
| | |
| | let { |
| | width = "max-w-sm", |
| | children, |
| | closeButton = false, |
| | disableFly = false, |
| | closeOnBackdrop = true, |
| | onclose, |
| | }: Props = $props(); |
| | |
| | let backdropEl: HTMLDivElement | undefined = $state(); |
| | let modalEl: HTMLDivElement | undefined = $state(); |
| | |
| | function handleKeydown(event: KeyboardEvent) { |
| | |
| | if (event.key === "Escape") { |
| | event.preventDefault(); |
| | onclose?.(); |
| | } |
| | } |
| | |
| | function handleBackdropClick(event: MouseEvent) { |
| | if (window?.getSelection()?.toString()) { |
| | return; |
| | } |
| | if (event.target === backdropEl && closeOnBackdrop) { |
| | onclose?.(); |
| | } |
| | } |
| | |
| | onMount(() => { |
| | document.getElementById("app")?.setAttribute("inert", "true"); |
| | modalEl?.focus(); |
| | |
| | window.addEventListener("keydown", handleKeydown, { capture: true }); |
| | }); |
| | |
| | onDestroy(() => { |
| | if (!browser) return; |
| | document.getElementById("app")?.removeAttribute("inert"); |
| | window.removeEventListener("keydown", handleKeydown, { capture: true }); |
| | }); |
| | </script> |
| | |
| | <Portal> |
| | <div |
| | role="presentation" |
| | tabindex="-1" |
| | bind:this={backdropEl} |
| | onclick={(e) => { |
| | e.stopPropagation(); |
| | handleBackdropClick(e); |
| | }} |
| | transition:fade|local={{ easing: cubicOut, duration: 300 }} |
| | class="fixed inset-0 z-40 flex items-center justify-center bg-black/80 backdrop-blur-sm dark:bg-black/50" |
| | > |
| | {#if disableFly} |
| | <div |
| | role="dialog" |
| | tabindex="-1" |
| | bind:this={modalEl} |
| | onkeydown={handleKeydown} |
| | class={[ |
| | "scrollbar-custom relative mx-auto max-h-[95dvh] max-w-[90dvw] overflow-y-auto overflow-x-hidden rounded-2xl bg-white shadow-2xl outline-none dark:bg-gray-800 dark:text-gray-200", |
| | width, |
| | ]} |
| | > |
| | {#if closeButton} |
| | <button class="absolute right-4 top-4 z-50" onclick={() => onclose?.()}> |
| | <CarbonClose class="size-6 text-gray-700 dark:text-gray-300" /> |
| | </button> |
| | {/if} |
| | {@render children?.()} |
| | </div> |
| | {:else} |
| | <div |
| | role="dialog" |
| | tabindex="-1" |
| | bind:this={modalEl} |
| | onkeydown={handleKeydown} |
| | in:fly={{ y: 100 }} |
| | class={[ |
| | "scrollbar-custom relative mx-auto max-h-[95dvh] max-w-[90dvw] overflow-y-auto overflow-x-hidden rounded-2xl bg-white shadow-2xl outline-none dark:bg-gray-800 dark:text-gray-200", |
| | width, |
| | ]} |
| | > |
| | {#if closeButton} |
| | <button class="absolute right-4 top-4 z-50" onclick={() => onclose?.()}> |
| | <CarbonClose class="size-6 text-gray-700 dark:text-gray-300" /> |
| | </button> |
| | {/if} |
| | {@render children?.()} |
| | </div> |
| | {/if} |
| | </div> |
| | </Portal> |
| | |