fix(collections): replace click-swallowing backdrop with document click-outside
This commit is contained in:
1 file changed
+35
-17
@@ -61,8 +61,33 @@
|
||||
let sortDir = $state<SortDir>(initial.sortDir ?? 'asc');
|
||||
let kindFilter = $state<KindFilter>(initial.kindFilter ?? 'all');
|
||||
let searchQuery = $state('');
|
||||
let showSortMenu = $state(false);
|
||||
let showFilterMenu = $state(false);
|
||||
// Mutually-exclusive toolbar dropdown state. Only one menu is open at a time.
|
||||
type OpenMenu = 'sort' | 'filter' | null;
|
||||
let openMenu = $state<OpenMenu>(null);
|
||||
|
||||
// Close the open menu on clicks outside any control button or menu.
|
||||
// Clicks on `.control-btn` / `.control-menu` are handled by their own
|
||||
// onclick handlers, which correctly swap `openMenu` between siblings.
|
||||
$effect(() => {
|
||||
if (openMenu === null) return;
|
||||
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement | null;
|
||||
if (!target) return;
|
||||
if (target.closest('.control-btn') || target.closest('.control-menu')) return;
|
||||
openMenu = null;
|
||||
}
|
||||
|
||||
// Defer registration so the click that opened the menu doesn't
|
||||
// immediately close it.
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
}, 0);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
};
|
||||
});
|
||||
|
||||
const normalizedSearch = $derived(searchQuery.trim().toLowerCase());
|
||||
|
||||
@@ -122,7 +147,7 @@
|
||||
sortBy = value;
|
||||
sortDir = 'asc';
|
||||
}
|
||||
showSortMenu = false;
|
||||
openMenu = null;
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
@@ -215,15 +240,14 @@
|
||||
<button
|
||||
type="button"
|
||||
class="control-btn"
|
||||
onclick={() => { showSortMenu = !showSortMenu; showFilterMenu = false; }}
|
||||
onclick={() => (openMenu = openMenu === 'sort' ? null : 'sort')}
|
||||
title={$t('collectionDetail.sort')}
|
||||
>
|
||||
<ArrowUpDown size={14} />
|
||||
<span>{sortOptions.find((o) => o.value === sortBy)?.label}</span>
|
||||
<span class="sort-indicator">{sortDir === 'asc' ? '↑' : '↓'}</span>
|
||||
</button>
|
||||
{#if showSortMenu}
|
||||
<div class="control-backdrop" onclick={() => (showSortMenu = false)} role="presentation"></div>
|
||||
{#if openMenu === 'sort'}
|
||||
<div class="control-menu">
|
||||
{#each sortOptions as option}
|
||||
<button
|
||||
@@ -247,7 +271,7 @@
|
||||
type="button"
|
||||
class="control-btn"
|
||||
class:active={kindFilter !== 'all'}
|
||||
onclick={() => { showFilterMenu = !showFilterMenu; showSortMenu = false; }}
|
||||
onclick={() => (openMenu = openMenu === 'filter' ? null : 'filter')}
|
||||
title={$t('collectionDetail.filter')}
|
||||
>
|
||||
<Filter size={14} />
|
||||
@@ -256,14 +280,13 @@
|
||||
<span class="filter-count">1</span>
|
||||
{/if}
|
||||
</button>
|
||||
{#if showFilterMenu}
|
||||
<div class="control-backdrop" onclick={() => (showFilterMenu = false)} role="presentation"></div>
|
||||
{#if openMenu === 'filter'}
|
||||
<div class="control-menu">
|
||||
<button
|
||||
type="button"
|
||||
class="control-menu-item"
|
||||
class:selected={kindFilter === 'all'}
|
||||
onclick={() => { kindFilter = 'all'; showFilterMenu = false; }}
|
||||
onclick={() => { kindFilter = 'all'; openMenu = null; }}
|
||||
>
|
||||
<span>{$t('collections.kindFilterAll')}</span>
|
||||
{#if kindFilter === 'all'}<Check size={12} />{/if}
|
||||
@@ -272,7 +295,7 @@
|
||||
type="button"
|
||||
class="control-menu-item"
|
||||
class:selected={kindFilter === 'collection'}
|
||||
onclick={() => { kindFilter = 'collection'; showFilterMenu = false; }}
|
||||
onclick={() => { kindFilter = 'collection'; openMenu = null; }}
|
||||
>
|
||||
<span>{$t('collections.kindFilterCollection')}</span>
|
||||
{#if kindFilter === 'collection'}<Check size={12} />{/if}
|
||||
@@ -281,7 +304,7 @@
|
||||
type="button"
|
||||
class="control-menu-item"
|
||||
class:selected={kindFilter === 'artist_collection'}
|
||||
onclick={() => { kindFilter = 'artist_collection'; showFilterMenu = false; }}
|
||||
onclick={() => { kindFilter = 'artist_collection'; openMenu = null; }}
|
||||
>
|
||||
<span>{$t('collections.kindFilterArtistCollection')}</span>
|
||||
{#if kindFilter === 'artist_collection'}<Check size={12} />{/if}
|
||||
@@ -556,11 +579,6 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.control-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 200;
|
||||
}
|
||||
.control-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
|
||||
Reference in new issue
Block a user