268e12a5eb
Without padding, max-w-md (448px) modals touch the viewport edges on a 375px screen. p-4 on the overlay clamps them inside the safe area. Covers all routes that use the shared <Modal> component (discover track-confirm, quarantine typed-DELETE, users password-reset, etc.). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
59 lines
1.6 KiB
Svelte
59 lines
1.6 KiB
Svelte
<script lang="ts">
|
|
import type { Snippet } from 'svelte';
|
|
|
|
let {
|
|
title,
|
|
open,
|
|
onClose,
|
|
children
|
|
}: {
|
|
title: string;
|
|
open: boolean;
|
|
onClose: () => void;
|
|
children: Snippet;
|
|
} = $props();
|
|
|
|
let dialog: HTMLDivElement | undefined = $state();
|
|
let previousFocus: HTMLElement | null = null;
|
|
|
|
$effect(() => {
|
|
if (open) {
|
|
previousFocus = document.activeElement as HTMLElement | null;
|
|
queueMicrotask(() => {
|
|
const first = dialog?.querySelector<HTMLElement>(
|
|
'button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
);
|
|
first?.focus();
|
|
});
|
|
} else if (previousFocus) {
|
|
previousFocus.focus();
|
|
previousFocus = null;
|
|
}
|
|
});
|
|
|
|
function onKeyDown(e: KeyboardEvent) {
|
|
if (open && e.key === 'Escape') onClose();
|
|
}
|
|
</script>
|
|
|
|
<svelte:window onkeydown={onKeyDown} />
|
|
|
|
{#if open}
|
|
<div class="fixed inset-0 z-50 flex items-center justify-center p-4" role="presentation">
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<!-- Backdrop click closes modal; ESC handled at window level above. -->
|
|
<div class="absolute inset-0 bg-black/50" onclick={onClose}></div>
|
|
<div
|
|
bind:this={dialog}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="modal-title"
|
|
class="relative z-10 w-full max-w-md rounded-md border border-border bg-surface p-6 shadow-lg"
|
|
>
|
|
<h2 id="modal-title" class="mb-4 text-lg font-medium text-text-primary">{title}</h2>
|
|
{@render children()}
|
|
</div>
|
|
</div>
|
|
{/if}
|