diff options
Diffstat (limited to 'frontend/src/components/Modal.svelte')
| -rw-r--r-- | frontend/src/components/Modal.svelte | 63 |
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> |
