diff --git a/frontend/src/lib/components/IconGridSelect.svelte b/frontend/src/lib/components/IconGridSelect.svelte index e72a509..dfb3a20 100644 --- a/frontend/src/lib/components/IconGridSelect.svelte +++ b/frontend/src/lib/components/IconGridSelect.svelte @@ -25,17 +25,25 @@ } = $props(); let open = $state(false); + let search = $state(''); let triggerEl: HTMLButtonElement; + let searchEl: HTMLInputElement; let popupStyle = $state(''); + const showSearch = $derived(items.length > 4); const selected = $derived(items.find(i => String(i.value) === String(value))); + const filtered = $derived( + search.trim() + ? items.filter(i => i.label.toLowerCase().includes(search.toLowerCase())) + : items + ); function toggle() { if (disabled) return; if (!open && triggerEl) { const rect = triggerEl.getBoundingClientRect(); const spaceBelow = window.innerHeight - rect.bottom; - const popupHeight = Math.min(items.length * 60 + 16, 320); + const popupHeight = Math.min(items.length * 60 + 16 + (showSearch ? 40 : 0), 320); const top = spaceBelow > popupHeight + 8 ? rect.bottom + 4 : rect.top - popupHeight - 4; @@ -43,16 +51,22 @@ popupStyle = `position:fixed; z-index:9999; top:${top}px; left:${Math.max(4, left)}px;`; } open = !open; + search = ''; + if (open && showSearch) { + requestAnimationFrame(() => searchEl?.focus()); + } } function select(item: GridItem) { value = item.value; open = false; + search = ''; } function handleKeydown(e: KeyboardEvent) { if (e.key === 'Escape' && open) { open = false; + search = ''; } } @@ -80,8 +94,13 @@
+ {#if showSearch} + + {/if}
- {#each items as item} + {#each filtered as item}
{/if} @@ -149,6 +171,18 @@ overflow-y: auto; scrollbar-width: thin; } + .icon-grid-search { + width: 100%; + padding: 0.375rem 0.5rem; + margin-bottom: 0.375rem; + border: none; + border-bottom: 1px solid var(--color-border); + border-radius: 0; + background: transparent; + color: var(--color-foreground); + font-size: 0.8rem; + outline: none; + } .icon-grid { display: grid; gap: 0.375rem;