summaryrefslogtreecommitdiff
path: root/frontend/src/components/Modal.svelte
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/Modal.svelte')
-rw-r--r--frontend/src/components/Modal.svelte63
1 files changed, 63 insertions, 0 deletions
diff --git a/frontend/src/components/Modal.svelte b/frontend/src/components/Modal.svelte
new file mode 100644
index 0000000..c9f334c
--- /dev/null
+++ b/frontend/src/components/Modal.svelte
@@ -0,0 +1,63 @@
+<script lang="ts">
+ import type { Snippet } from 'svelte';
+
+ interface Props {
+ title: string;
+ onClose: () => void;
+ wide?: boolean;
+ children?: Snippet;
+ }
+ let { title, onClose, wide = false, children }: Props = $props();
+
+ function onBackdrop(e: MouseEvent) {
+ if (e.target === e.currentTarget) onClose();
+ }
+
+ function onKeydown(e: KeyboardEvent) {
+ if (e.key === 'Escape') onClose();
+ }
+
+ let container: HTMLDivElement;
+ $effect(() => {
+ container?.focus();
+ });
+</script>
+
+<div
+ class="overlay"
+ onclick={onBackdrop}
+ role="presentation"
+ onkeydown={onKeydown}
+ tabindex="-1"
+ bind:this={container}
+>
+ <div class="dialog card" role="dialog" aria-modal="true" aria-label={title}>
+ <div class="spread">
+ <h2>{title}</h2>
+ <button class="btn ghost" onclick={onClose} aria-label="关闭">✕</button>
+ </div>
+ <div class="content">
+ {@render children?.()}
+ </div>
+ </div>
+</div>
+
+<style>
+ .overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 30, 35, 0.35);
+ display: grid;
+ place-items: center;
+ z-index: 50;
+ padding: 1rem;
+ }
+ .dialog {
+ width: min(760px, 100%);
+ max-height: 88vh;
+ overflow: auto;
+ padding: 1.1rem 1.2rem;
+ }
+
+ .content { margin-top: 0.6rem; }
+</style>