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 sortDir = $state<SortDir>(initial.sortDir ?? 'asc');
|
||||||
let kindFilter = $state<KindFilter>(initial.kindFilter ?? 'all');
|
let kindFilter = $state<KindFilter>(initial.kindFilter ?? 'all');
|
||||||
let searchQuery = $state('');
|
let searchQuery = $state('');
|
||||||
let showSortMenu = $state(false);
|
// Mutually-exclusive toolbar dropdown state. Only one menu is open at a time.
|
||||||
let showFilterMenu = $state(false);
|
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());
|
const normalizedSearch = $derived(searchQuery.trim().toLowerCase());
|
||||||
|
|
||||||
@@ -122,7 +147,7 @@
|
|||||||
sortBy = value;
|
sortBy = value;
|
||||||
sortDir = 'asc';
|
sortDir = 'asc';
|
||||||
}
|
}
|
||||||
showSortMenu = false;
|
openMenu = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetFilters() {
|
function resetFilters() {
|
||||||
@@ -215,15 +240,14 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="control-btn"
|
class="control-btn"
|
||||||
onclick={() => { showSortMenu = !showSortMenu; showFilterMenu = false; }}
|
onclick={() => (openMenu = openMenu === 'sort' ? null : 'sort')}
|
||||||
title={$t('collectionDetail.sort')}
|
title={$t('collectionDetail.sort')}
|
||||||
>
|
>
|
||||||
<ArrowUpDown size={14} />
|
<ArrowUpDown size={14} />
|
||||||
<span>{sortOptions.find((o) => o.value === sortBy)?.label}</span>
|
<span>{sortOptions.find((o) => o.value === sortBy)?.label}</span>
|
||||||
<span class="sort-indicator">{sortDir === 'asc' ? '↑' : '↓'}</span>
|
<span class="sort-indicator">{sortDir === 'asc' ? '↑' : '↓'}</span>
|
||||||
</button>
|
</button>
|
||||||
{#if showSortMenu}
|
{#if openMenu === 'sort'}
|
||||||
<div class="control-backdrop" onclick={() => (showSortMenu = false)} role="presentation"></div>
|
|
||||||
<div class="control-menu">
|
<div class="control-menu">
|
||||||
{#each sortOptions as option}
|
{#each sortOptions as option}
|
||||||
<button
|
<button
|
||||||
@@ -247,7 +271,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="control-btn"
|
class="control-btn"
|
||||||
class:active={kindFilter !== 'all'}
|
class:active={kindFilter !== 'all'}
|
||||||
onclick={() => { showFilterMenu = !showFilterMenu; showSortMenu = false; }}
|
onclick={() => (openMenu = openMenu === 'filter' ? null : 'filter')}
|
||||||
title={$t('collectionDetail.filter')}
|
title={$t('collectionDetail.filter')}
|
||||||
>
|
>
|
||||||
<Filter size={14} />
|
<Filter size={14} />
|
||||||
@@ -256,14 +280,13 @@
|
|||||||
<span class="filter-count">1</span>
|
<span class="filter-count">1</span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{#if showFilterMenu}
|
{#if openMenu === 'filter'}
|
||||||
<div class="control-backdrop" onclick={() => (showFilterMenu = false)} role="presentation"></div>
|
|
||||||
<div class="control-menu">
|
<div class="control-menu">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="control-menu-item"
|
class="control-menu-item"
|
||||||
class:selected={kindFilter === 'all'}
|
class:selected={kindFilter === 'all'}
|
||||||
onclick={() => { kindFilter = 'all'; showFilterMenu = false; }}
|
onclick={() => { kindFilter = 'all'; openMenu = null; }}
|
||||||
>
|
>
|
||||||
<span>{$t('collections.kindFilterAll')}</span>
|
<span>{$t('collections.kindFilterAll')}</span>
|
||||||
{#if kindFilter === 'all'}<Check size={12} />{/if}
|
{#if kindFilter === 'all'}<Check size={12} />{/if}
|
||||||
@@ -272,7 +295,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="control-menu-item"
|
class="control-menu-item"
|
||||||
class:selected={kindFilter === 'collection'}
|
class:selected={kindFilter === 'collection'}
|
||||||
onclick={() => { kindFilter = 'collection'; showFilterMenu = false; }}
|
onclick={() => { kindFilter = 'collection'; openMenu = null; }}
|
||||||
>
|
>
|
||||||
<span>{$t('collections.kindFilterCollection')}</span>
|
<span>{$t('collections.kindFilterCollection')}</span>
|
||||||
{#if kindFilter === 'collection'}<Check size={12} />{/if}
|
{#if kindFilter === 'collection'}<Check size={12} />{/if}
|
||||||
@@ -281,7 +304,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="control-menu-item"
|
class="control-menu-item"
|
||||||
class:selected={kindFilter === 'artist_collection'}
|
class:selected={kindFilter === 'artist_collection'}
|
||||||
onclick={() => { kindFilter = 'artist_collection'; showFilterMenu = false; }}
|
onclick={() => { kindFilter = 'artist_collection'; openMenu = null; }}
|
||||||
>
|
>
|
||||||
<span>{$t('collections.kindFilterArtistCollection')}</span>
|
<span>{$t('collections.kindFilterArtistCollection')}</span>
|
||||||
{#if kindFilter === 'artist_collection'}<Check size={12} />{/if}
|
{#if kindFilter === 'artist_collection'}<Check size={12} />{/if}
|
||||||
@@ -556,11 +579,6 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-backdrop {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 200;
|
|
||||||
}
|
|
||||||
.control-menu {
|
.control-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(100% + 4px);
|
top: calc(100% + 4px);
|
||||||
|
|||||||
Reference in new issue
Block a user