cherry: import element_modal_v1.svelte from wip-modal-fix-attempt

This commit is contained in:
Scott Idem
2026-03-17 10:29:37 -04:00
parent 6b49e2f036
commit 1156e02e48

View File

@@ -0,0 +1,180 @@
<script lang="ts">
import { onMount, onDestroy, run } from 'svelte/legacy';
interface Props {
open?: boolean;
title?: string;
autoclose?: boolean;
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl';
placement?: 'top-center' | 'center' | 'bottom-center';
class_li?: string; // Additional classes for the dialog
}
let {
open = $bindable(false),
title = '',
autoclose = true,
size = 'md',
placement = 'center',
class_li = ''
}: Props = $props();
let dialog_element: HTMLDialogElement;
// Open/close dialog reactively
run(() => {
if (dialog_element) {
if (open) {
dialog_element.showModal();
} else {
dialog_element.close();
}
}
});
onMount(() => {
// Handle backdrop click to close (if autoclose is true)
dialog_element.addEventListener('click', (event) => {
if (autoclose && event.target === dialog_element) {
open = false;
}
});
// Handle Escape key to close
const handle_keydown = (event: KeyboardEvent) => {
if (event.key === 'Escape' && open) {
event.preventDefault(); // Prevent default browser escape behavior (e.g., page back)
open = false;
}
};
window.addEventListener('keydown', handle_keydown);
onDestroy(() => {
window.removeEventListener('keydown', handle_keydown);
});
});
// Determine max-width based on size prop
let max_width_class = $derived(
size === 'sm'
? 'max-w-sm'
: size === 'md'
? 'max-w-md'
: size === 'lg'
? 'max-w-lg'
: size === 'xl'
? 'max-w-xl'
: size === '2xl'
? 'max-w-2xl'
: size === '3xl'
? 'max-w-3xl'
: size === '4xl'
? 'max-w-4xl'
: size === '5xl'
? 'max-w-5xl'
: size === '6xl'
? 'max-w-6xl'
: size === '7xl'
? 'max-w-7xl'
: 'max-w-md'
);
// Determine placement classes
let placement_class = $derived(
placement === 'top-center'
? 'justify-center items-start pt-[5vh]'
: placement === 'center'
? 'justify-center items-center'
: placement === 'bottom-center'
? 'justify-center items-end pb-[5vh]'
: 'justify-center items-center' // Default to center
);
</script>
<dialog
bind:this={dialog_element}
class="
p-0 bg-transparent overflow-visible
backdrop:bg-black/50 backdrop:backdrop-blur-sm
"
onclose={() => (open = false)}
>
<div
class="
bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg shadow-xl
flex flex-col
mx-auto
{max_width_class}
w-full
{class_li}
"
>
<!-- Modal Header -->
{#if title || $$slots.header}
<header class="p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
{#if $$slots.header}
<slot name="header" />
{:else}
<h3 class="text-xl font-semibold">{title}</h3>
{/if}
<button onclick={() => (open = false)} class="p-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</header>
{/if}
<!-- Modal Body -->
<main class="p-4 overflow-y-auto max-h-[70vh]">
<slot />
</main>
<!-- Modal Footer -->
{#if $$slots.footer}
<footer class="p-4 border-t border-gray-200 dark:border-gray-700">
<slot name="footer" />
</footer>
{/if}
</div>
</dialog>
<style lang="postcss">
dialog {
display: flex; /* Override default to allow flexbox positioning */
width: 100%;
height: 100%;
top: 0;
left: 0;
position: fixed;
}
dialog[open] {
opacity: 0;
animation: fade-in 0.15s forwards ease-out;
}
dialog:not([open]) {
opacity: 1;
animation: fade-out 0.15s forwards ease-out;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
pointer-events: none; /* Disable interaction while fading out */
}
}
</style>