chat-ui / src /lib /components /Modal.svelte
coyotte508
A new start
fc69895
<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;
/** When false, clicking backdrop will not close the modal */
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) {
// close on ESC
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();
// Ensure Escape closes even if focus isn't within modal
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>