File size: 3,073 Bytes
03a0d62
4331e77
03a0d62
89b5db9
660f054
 
0289d3a
4489403
21b8785
 
0289d3a
4331e77
 
 
 
21b8785
 
 
4331e77
 
 
 
 
 
 
 
660f054
21b8785
 
660f054
 
 
 
 
4331e77
660f054
 
 
 
976588c
 
 
4331e77
 
660f054
 
 
 
 
21b8785
4331e77
 
660f054
 
 
 
21b8785
4331e77
660f054
03a0d62
 
660f054
 
 
 
 
21b8785
 
 
 
a8bee07
0289d3a
660f054
4331e77
 
 
 
 
 
 
17d4d70
4331e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17d4d70
4331e77
 
 
 
 
 
 
 
 
 
 
03a0d62
660f054
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<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>