diff --git a/apps/manacore/apps/web/src/lib/components/workbench/AppPage.svelte b/apps/manacore/apps/web/src/lib/components/workbench/AppPage.svelte index 9256c38f2..c9fed760c 100644 --- a/apps/manacore/apps/web/src/lib/components/workbench/AppPage.svelte +++ b/apps/manacore/apps/web/src/lib/components/workbench/AppPage.svelte @@ -81,6 +81,31 @@ let overlay = $derived(overlayStack.length > 0 ? overlayStack[overlayStack.length - 1] : null); let hasOverlay = $derived(overlayStack.length > 0); + // Close animation + let closing = $state(false); + + function closeAllOverlays() { + closing = true; + setTimeout(() => { + overlayStack = []; + siblingIds = []; + siblingKey = ''; + closing = false; + }, 150); + } + + // Escape key closes overlay + function handleKeydown(e: KeyboardEvent) { + if (e.key === 'Escape' && hasOverlay) { + e.stopPropagation(); + if (overlayStack.length > 1) { + goBack(); + } else { + closeAllOverlays(); + } + } + } + // Sibling item IDs for prev/next navigation (only for first overlay level) let siblingIds = $state([]); let siblingKey = $state(''); @@ -207,8 +232,7 @@ let overlayCardEl = $state(null); let appPageEl = $state(null); - // Close overlay on click outside the overlay card BUT inside this AppPage - // (clicks in other AppPages should NOT close this overlay) + // Close overlay on click outside or Escape key $effect(() => { if (!overlay) return; function handleGlobalClick(e: MouseEvent) { @@ -219,17 +243,17 @@ appPageEl.contains(target) && !overlayCardEl.contains(target) ) { - overlayStack = []; - siblingIds = []; - siblingKey = ''; + closeAllOverlays(); } } const timer = setTimeout(() => { window.addEventListener('click', handleGlobalClick, true); }, 0); + window.addEventListener('keydown', handleKeydown); return () => { clearTimeout(timer); window.removeEventListener('click', handleGlobalClick, true); + window.removeEventListener('keydown', handleKeydown); }; }); @@ -273,9 +297,9 @@ - {#if overlay?.component} - {@const OverlayComponent = overlay.component} -
+ {#if overlay?.component || closing} + {@const OverlayComponent = overlay?.component} +
{#if hasPrev} @@ -291,32 +315,26 @@ {/if} - - {overlay.overlayTitle ?? appName} + {overlay?.overlayTitle ?? appName} {#if siblingIds.length > 1 && overlayStack.length === 1} {currentSiblingIndex() + 1}/{siblingIds.length} {/if} -
- {#key overlay.params[siblingKey] ?? ''} - - {/key} + {#if OverlayComponent && overlay} + {#key overlay.params[siblingKey] ?? ''} + + {/key} + {/if}
@@ -384,6 +402,10 @@ justify-content: center; animation: fadeIn 0.15s ease-out; } + .overlay-backdrop.closing { + animation: fadeOut 0.15s ease-in forwards; + pointer-events: none; + } .overlay-card { width: 100%; @@ -397,6 +419,9 @@ overflow: hidden; animation: scaleIn 0.2s ease-out; } + .closing .overlay-card { + animation: scaleOut 0.15s ease-in forwards; + } :global(.dark) .overlay-card { background: #252220; box-shadow: @@ -531,4 +556,22 @@ transform: scale(1); } } + @keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } + } + @keyframes scaleOut { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.96); + } + }