Files
EventSnap/frontend/src/lib/components/Modal.svelte
MechaCat02 f243bfe89a feat(ui): elegant wedding white/silver/gold redesign
Reskin the whole app via design tokens in tailwind-theme.css (remapped
color ramps) plus a define-once component layer in lib/styles/components.css
(.btn, .card, .input, .chip, .badge, .sheet, …). Buttons are muted/outlined
gold rather than flat fills. Self-host Inter + Fraunces (woff2) under the
existing font-src 'self' CSP. Restyle the shared components and the account,
admin, host, join, recover and upload screens against the new tokens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 17:28:42 +02:00

58 lines
1.9 KiB
Svelte

<script lang="ts">
import { focusTrap } from '$lib/actions/focus-trap';
import { scrollLock } from '$lib/actions/scroll-lock';
import { modalInert } from '$lib/actions/modal-inert';
import type { Snippet } from 'svelte';
// Accessible name is REQUIRED. Pass `titleId` when the dialog renders its own
// visible heading (the common case — link aria-labelledby to that heading's id),
// or `ariaLabel` for dialogs without a visible title (e.g. an image preview).
// Failing to provide either leaves the dialog without an accessible name,
// which screen readers announce as just "dialog" — useless. The runtime
// warning below catches missed cases during dev.
interface Props {
open: boolean;
titleId?: string;
ariaLabel?: string;
onClose: () => void;
closeOnBackdrop?: boolean;
children: Snippet;
}
let { open, titleId, ariaLabel, onClose, closeOnBackdrop = true, children }: Props = $props();
$effect(() => {
if (open && !titleId && !ariaLabel && typeof console !== 'undefined') {
console.warn('<Modal> opened without titleId or ariaLabel — dialog has no accessible name.');
}
});
</script>
{#if open}
<!-- display:contents wrapper: keeps the backdrop + dialog visually unchanged
while giving `modalInert` a single node whose siblings (the background
page, nav, etc.) get inerted for assistive tech. -->
<div class="contents" use:modalInert>
<button
type="button"
class="fixed inset-0 z-50 bg-black/50"
aria-label="Schließen"
tabindex="-1"
onclick={closeOnBackdrop ? onClose : () => {}}
></button>
<div
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center p-4"
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
aria-label={titleId ? undefined : ariaLabel}
use:focusTrap={{ onclose: onClose }}
use:scrollLock
>
<div class="card pointer-events-auto w-full max-w-sm p-6 shadow-xl">
{@render children()}
</div>
</div>
</div>
{/if}