feature/searchbar-enhance #17
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user