cherry: import element_modal_v1.svelte from wip-modal-fix-attempt
This commit is contained in:
180
src/lib/elements/element_modal_v1.svelte
Normal file
180
src/lib/elements/element_modal_v1.svelte
Normal 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>
|
||||
Reference in New Issue
Block a user