an atproto based link aggregator
1<script lang="ts">
2 interface Props {
3 open: boolean;
4 onclose: () => void;
5 title?: string;
6 }
7
8 let { open, onclose, title, children }: Props & { children: import('svelte').Snippet } = $props();
9
10 function handleKeydown(e: KeyboardEvent) {
11 if (e.key === 'Escape') {
12 onclose();
13 }
14 }
15
16 function handleBackdropClick(e: MouseEvent) {
17 if (e.target === e.currentTarget) {
18 onclose();
19 }
20 }
21</script>
22
23<svelte:window onkeydown={handleKeydown} />
24
25{#if open}
26 <!-- svelte-ignore a11y_click_events_have_key_events -->
27 <!-- svelte-ignore a11y_no_static_element_interactions -->
28 <div
29 class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
30 onclick={handleBackdropClick}
31 >
32 <div
33 class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto"
34 role="dialog"
35 aria-modal="true"
36 aria-labelledby={title ? 'modal-title' : undefined}
37 >
38 {#if title}
39 <div
40 class="flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-gray-700"
41 >
42 <h2 id="modal-title" class="text-lg font-semibold text-gray-900 dark:text-gray-100">
43 {title}
44 </h2>
45 <button
46 type="button"
47 onclick={onclose}
48 class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
49 aria-label="Close"
50 >
51 <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52 <path
53 stroke-linecap="round"
54 stroke-linejoin="round"
55 stroke-width="2"
56 d="M6 18L18 6M6 6l12 12"
57 />
58 </svg>
59 </button>
60 </div>
61 {/if}
62 <div class="p-4">
63 {@render children()}
64 </div>
65 </div>
66 </div>
67{/if}