1<script lang="ts">
2 import type { Snippet } from 'svelte';
3
4 interface Props {
5 isOpen: boolean;
6 onClose?: () => void;
7 title: string;
8 width?: string;
9 height?: string;
10 padding?: string;
11 showHeaderDivider?: boolean;
12 headerActions?: Snippet;
13 children: Snippet;
14 footer?: Snippet;
15 }
16
17 let {
18 isOpen = $bindable(false),
19 onClose = () => (isOpen = false),
20 title,
21 width = 'w-full max-w-md',
22 height = 'auto',
23 padding = 'p-4',
24 showHeaderDivider = false,
25 headerActions,
26 children,
27 footer
28 }: Props = $props();
29
30 const handleKeydown = (event: KeyboardEvent) => {
31 if (event.key === 'Escape') onClose();
32 };
33</script>
34
35{#if isOpen}
36 <div
37 class="fixed inset-0 z-50 flex items-center justify-center bg-(--nucleus-bg)/80 backdrop-blur-sm"
38 onclick={onClose}
39 onkeydown={handleKeydown}
40 role="button"
41 tabindex="-1"
42 >
43 <!-- svelte-ignore a11y_interactive_supports_focus -->
44 <!-- svelte-ignore a11y_click_events_have_key_events -->
45 <div
46 class="flex {height === 'auto'
47 ? ''
48 : 'h-[' +
49 height +
50 ']'} {width} shrink animate-fade-in-scale flex-col rounded-sm border-2 border-(--nucleus-accent) bg-(--nucleus-bg) shadow-2xl transition-all"
51 style={height !== 'auto' ? `height: ${height}` : ''}
52 onclick={(e) => e.stopPropagation()}
53 role="dialog"
54 >
55 <!-- Header -->
56 <div
57 class="flex items-center gap-4 {showHeaderDivider
58 ? 'border-b-2 border-(--nucleus-accent)/20'
59 : ''} {padding}"
60 >
61 <div>
62 <h2 class="text-2xl font-bold">{title}</h2>
63 <div class="mt-2 flex gap-2">
64 <div class="h-1 w-8 rounded-full bg-(--nucleus-accent)"></div>
65 <div class="h-1 w-9.5 rounded-full bg-(--nucleus-accent2)"></div>
66 </div>
67 </div>
68
69 {#if headerActions}
70 {@render headerActions()}
71 {/if}
72
73 <div class="grow"></div>
74
75 <!-- svelte-ignore a11y_consider_explicit_label -->
76 <button
77 onclick={onClose}
78 class="rounded-xl p-2 text-(--nucleus-fg)/40 transition-all hover:scale-110 hover:text-(--nucleus-fg)"
79 >
80 <svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
81 <path
82 stroke-linecap="round"
83 stroke-linejoin="round"
84 stroke-width="2.5"
85 d="M6 18L18 6M6 6l12 12"
86 />
87 </svg>
88 </button>
89 </div>
90
91 <!-- Content -->
92 <div class="{height === 'auto' ? '' : 'flex-1 overflow-y-auto'} {padding}">
93 {@render children()}
94 </div>
95
96 <!-- Footer -->
97 {#if footer}
98 {@render footer()}
99 {/if}
100 </div>
101 </div>
102{/if}