feature/searchbar-enhance #17

Merged
ilia merged 48 commits from feature/searchbar-enhance into main 2026-01-18 14:04:53 +00:00
Showing only changes of commit 261c19db69 - Show all commits

View File

@@ -1,3 +1,10 @@
<!--
Component: SearchBar
Search input with popover dropdown for results/suggestions
- Features keyboard navigation (ArrowDown/Up/Enter) and auto-focus prevention on popover open.
- The input field serves as the popover trigger.
-->
<script lang="ts"> <script lang="ts">
import { Input } from '$shared/shadcn/ui/input'; import { Input } from '$shared/shadcn/ui/input';
import { Label } from '$shared/shadcn/ui/label'; import { Label } from '$shared/shadcn/ui/label';
@@ -7,17 +14,20 @@ import {
Trigger as PopoverTrigger, Trigger as PopoverTrigger,
} from '$shared/shadcn/ui/popover'; } from '$shared/shadcn/ui/popover';
import { useId } from 'bits-ui'; import { useId } from 'bits-ui';
import { import type { Snippet } from 'svelte';
type Snippet,
tick,
} from 'svelte';
interface Props { interface Props {
/** Unique identifier for the input element */
id: string; id: string;
/** Current search value (bindable) */
value: string; value: string;
/** Additional CSS classes for the container */
class?: string; class?: string;
/** Placeholder text for the input */
placeholder?: string; placeholder?: string;
/** Optional label displayed above the input */
label?: string; label?: string;
/** Content to render inside the popover (receives unique content ID) */
children: Snippet<[{ id: string }]> | undefined; children: Snippet<[{ id: string }]> | undefined;
} }
@@ -35,13 +45,6 @@ let triggerRef = $state<HTMLInputElement>(null!);
// svelte-ignore state_referenced_locally // svelte-ignore state_referenced_locally
const contentId = useId(id); const contentId = useId(id);
function closeAndFocusTrigger() {
open = false;
tick().then(() => {
triggerRef?.focus();
});
}
function handleKeyDown(event: KeyboardEvent) { function handleKeyDown(event: KeyboardEvent) {
if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') { if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') {
event.preventDefault(); event.preventDefault();
@@ -50,16 +53,14 @@ function handleKeyDown(event: KeyboardEvent) {
function handleInputClick() { function handleInputClick() {
open = true; open = true;
tick().then(() => {
triggerRef?.focus();
});
} }
</script> </script>
<PopoverRoot> <PopoverRoot bind:open>
<PopoverTrigger bind:ref={triggerRef}> <PopoverTrigger bind:ref={triggerRef}>
{#snippet child({ props })} {#snippet child({ props })}
<div {...props} class="flex flex-row flex-1 w-full"> {@const { onclick, ...rest } = props}
<div {...rest} class="flex flex-row flex-1 w-full">
{#if label} {#if label}
<Label for={id}>{label}</Label> <Label for={id}>{label}</Label>
{/if} {/if}
@@ -68,6 +69,7 @@ function handleInputClick() {
placeholder={placeholder} placeholder={placeholder}
bind:value={value} bind:value={value}
onkeydown={handleKeyDown} onkeydown={handleKeyDown}
onclick={handleInputClick}
class="flex flex-row flex-1" class="flex flex-row flex-1"
/> />
</div> </div>
@@ -76,7 +78,12 @@ function handleInputClick() {
<PopoverContent <PopoverContent
onOpenAutoFocus={e => e.preventDefault()} onOpenAutoFocus={e => e.preventDefault()}
class="w-max" onInteractOutside={(e => {
if (e.target === triggerRef) {
e.preventDefault();
}
})}
class="w-(--bits-popover-anchor-width) min-w-(--bits-popover-anchor-width)"
> >
{@render children?.({ id: contentId })} {@render children?.({ id: contentId })}
</PopoverContent> </PopoverContent>