| <script> | |
| let { | |
| show = false, | |
| title = '', | |
| size = 'md', | |
| onclose = () => {}, | |
| children | |
| } = $props(); | |
| const sizes = { | |
| sm: 'max-w-sm', | |
| md: 'max-w-lg', | |
| lg: 'max-w-2xl', | |
| xl: 'max-w-4xl', | |
| full: 'max-w-6xl' | |
| }; | |
| function handleBackdrop(e) { | |
| if (e.target === e.currentTarget) { | |
| onclose(); | |
| } | |
| } | |
| function handleKeydown(e) { | |
| if (e.key === 'Escape') onclose(); | |
| } | |
| </script> | |
| <svelte:window onkeydown={handleKeydown} /> | |
| {#if show} | |
| <div | |
| class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm p-4" | |
| onclick={handleBackdrop} | |
| role="dialog" | |
| aria-modal="true" | |
| > | |
| <div class="card w-full {sizes[size]} max-h-[90vh] flex flex-col animate-in"> | |
| {#if title} | |
| <div class="flex items-center justify-between px-6 py-4 border-b border-gray-200"> | |
| <h2 class="text-lg font-semibold text-gray-800">{title}</h2> | |
| <button | |
| onclick={onclose} | |
| class="text-gray-400 hover:text-gray-700 transition-colors p-2 rounded-lg hover:bg-gray-100" | |
| > | |
| <svg class="w-5 h-5" 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> | |
| </div> | |
| {/if} | |
| <div class="px-6 py-4 overflow-y-auto flex-1"> | |
| {@render children?.()} | |
| </div> | |
| </div> | |
| </div> | |
| {/if} | |
| <style> | |
| .animate-in { | |
| animation: modal-in 0.2s ease-out; | |
| } | |
| @keyframes modal-in { | |
| from { | |
| opacity: 0; | |
| transform: scale(0.95) translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: scale(1) translateY(0); | |
| } | |
| } | |
| </style> | |