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