Unify UI workflows and streamline fleet management
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { tick, type Snippet } from 'svelte';
|
||||
import Button from './Button.svelte';
|
||||
|
||||
let { open = $bindable(false), title, description, busy = false, wide = false, children, actions }:
|
||||
{ open?: boolean; title: string; description?: string; busy?: boolean; wide?: boolean; children?: Snippet; actions?: Snippet } = $props();
|
||||
const id = $props.id();
|
||||
let dialog = $state<HTMLDialogElement>();
|
||||
let returnFocus: HTMLElement | null = null;
|
||||
$effect(() => {
|
||||
if (!dialog) return;
|
||||
if (open && !dialog.open) {
|
||||
returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
||||
dialog.showModal();
|
||||
} else if (!open && dialog.open) dialog.close();
|
||||
});
|
||||
function close() { if (!busy) open = false; }
|
||||
async function closed() {
|
||||
open = false;
|
||||
await tick();
|
||||
if (returnFocus?.isConnected) returnFocus.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog bind:this={dialog} class:wide aria-labelledby={`${id}-title`} aria-describedby={description ? `${id}-description` : undefined}
|
||||
oncancel={event => { if (busy) event.preventDefault(); }} onclose={closed}
|
||||
onclick={event => { if (event.target === dialog) { const r = dialog!.getBoundingClientRect(); if (event.clientX < r.left || event.clientX > r.right || event.clientY < r.top || event.clientY > r.bottom) close(); } }}>
|
||||
<header><div><h2 id={`${id}-title`}>{title}</h2>{#if description}<p id={`${id}-description`}>{description}</p>{/if}</div><Button variant="ghost" ariaLabel="Close dialog" disabled={busy} onclick={close}>×</Button></header>
|
||||
<div class="body">{@render children?.()}</div>
|
||||
{#if actions}<footer>{@render actions()}</footer>{/if}
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
dialog { width: min(560px, calc(100vw - 32px)); padding: 0; }
|
||||
dialog.wide { width: min(880px, calc(100vw - 32px)); }
|
||||
header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding: var(--space-6); border-bottom: 1px solid var(--border-subtle); }
|
||||
h2 { margin: 0; font-size: var(--text-xl); line-height: var(--leading-tight); }
|
||||
p { margin: var(--space-2) 0 0; color: var(--text-secondary); font-size: var(--text-sm); }
|
||||
.body { padding: var(--space-6); }
|
||||
footer { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: var(--space-3); padding: var(--space-4) var(--space-6); border-top: 1px solid var(--border-subtle); background: var(--surface); position: sticky; bottom: 0; z-index: 1; }
|
||||
@media (max-width: 600px) { header, .body { padding: var(--space-4); } footer { padding: var(--space-4); } h2 { font-size: var(--text-lg); } }
|
||||
</style>
|
||||
Reference in New Issue
Block a user