Use native HTML <dialog> element for modals

Replaced CSS-based modal with native HTML <dialog> element.
Cleaner code, better accessibility, built-in backdrop handling.
This commit is contained in:
Christoph Haas 2026-03-22 23:56:56 +01:00
parent a8aaa08d29
commit 5c3e4ce7d8

View file

@ -16,7 +16,7 @@
let domains = $state<Domain[]>([]); let domains = $state<Domain[]>([]);
let user = $state<UserInfo | null>(null); let user = $state<UserInfo | null>(null);
let loading = $state(true); let loading = $state(true);
let showModal = $state(false); let dialogEl = $state<HTMLDialogElement | null>(null);
let newDomain = $state({ name: '' }); let newDomain = $state({ name: '' });
let createError = $state(''); let createError = $state('');
@ -48,6 +48,16 @@
} }
} }
function openModal() {
createError = '';
newDomain = { name: '' };
dialogEl?.showModal();
}
function closeModal() {
dialogEl?.close();
}
async function createDomain() { async function createDomain() {
createError = ''; createError = '';
try { try {
@ -58,8 +68,7 @@
}); });
const data = await res.json(); const data = await res.json();
if (res.ok) { if (res.ok) {
showModal = false; closeModal();
newDomain = { name: '' };
await loadData(); await loadData();
} else { } else {
createError = data.error || 'Failed to create domain'; createError = data.error || 'Failed to create domain';
@ -93,7 +102,7 @@
<div class="header"> <div class="header">
<h2>Domains</h2> <h2>Domains</h2>
{#if isAdmin()} {#if isAdmin()}
<button onclick={() => showModal = true}>Add Domain</button> <button onclick={openModal}>Add Domain</button>
{/if} {/if}
</div> </div>
@ -139,25 +148,22 @@
</table> </table>
{/if} {/if}
{#if showModal} <dialog bind:this={dialogEl}>
<button class="modal-backdrop" onclick={() => { showModal = false; createError = ''; }} aria-label="Close modal"></button> <h3>Add Domain</h3>
<div class="modal"> <form onsubmit={(e) => { e.preventDefault(); createDomain(); }}>
<h3>Add Domain</h3> <label>
<form onsubmit={(e) => { e.preventDefault(); createDomain(); }}> Domain name:
<label> <input type="text" bind:value={newDomain.name} placeholder="example.org" required />
Domain name: </label>
<input type="text" bind:value={newDomain.name} placeholder="example.org" required /> {#if createError}
</label> <p class="error">{createError}</p>
{#if createError} {/if}
<p class="error">{createError}</p> <div class="actions">
{/if} <button type="button" class="secondary" onclick={closeModal}>Cancel</button>
<div class="actions"> <button type="submit">Create</button>
<button type="button" onclick={() => { showModal = false; createError = ''; }}>Cancel</button> </div>
<button type="submit">Create</button> </form>
</div> </dialog>
</form>
</div>
{/if}
<style> <style>
.header { .header {
@ -236,54 +242,48 @@
color: white; color: white;
} }
button.secondary {
background: #95a5a6;
}
button.danger { button.danger {
background: #e74c3c; background: #e74c3c;
} }
.modal-backdrop { dialog {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
border: none;
cursor: pointer;
z-index: 100;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 2rem; padding: 2rem;
border-radius: 8px; border-radius: 8px;
min-width: 400px; border: none;
z-index: 101; box-shadow: 0 4px 20px rgba(0,0,0,0.2);
} }
.modal h3 { dialog::backdrop {
margin-top: 0; background: rgba(0,0,0,0.5);
} }
.modal form { dialog h3 {
margin: 0 0 1rem 0;
}
dialog form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
} }
.modal label { dialog label {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
} }
.modal input { dialog input {
padding: 0.5rem; padding: 0.5rem;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
} }
.modal .error { dialog .error {
color: #e74c3c; color: #e74c3c;
background: #fdecea; background: #fdecea;
padding: 0.5rem; padding: 0.5rem;