feat(05-01): create SearchBar component with debounced input
- Debounced value binding (300ms delay via $effect cleanup) - Only trigger search when query >= 2 chars OR cleared to empty - Bindable value prop for parent integration - '/' keyboard shortcut to focus search (GitHub-style) - Native event listener to avoid Svelte 5 keydown bug
This commit is contained in:
60
src/lib/components/SearchBar.svelte
Normal file
60
src/lib/components/SearchBar.svelte
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { value = $bindable() }: Props = $props();
|
||||||
|
|
||||||
|
// Internal state for immediate user input
|
||||||
|
let inputValue = $state(value);
|
||||||
|
let searchInput: HTMLInputElement;
|
||||||
|
|
||||||
|
// Debounce: update bound value 300ms after user stops typing
|
||||||
|
$effect(() => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
// Only trigger when >= 2 chars OR cleared to empty
|
||||||
|
if (inputValue.length >= 2 || inputValue.length === 0) {
|
||||||
|
value = inputValue;
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sync inputValue if parent changes value externally
|
||||||
|
$effect(() => {
|
||||||
|
if (value !== inputValue && (value === '' || inputValue === '')) {
|
||||||
|
inputValue = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// "/" keyboard shortcut - use native listener to avoid Svelte 5 bug
|
||||||
|
onMount(() => {
|
||||||
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
|
// Skip if already in an input or textarea
|
||||||
|
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === '/') {
|
||||||
|
e.preventDefault();
|
||||||
|
searchInput?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleKeydown);
|
||||||
|
return () => document.removeEventListener('keydown', handleKeydown);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
bind:this={searchInput}
|
||||||
|
bind:value={inputValue}
|
||||||
|
type="text"
|
||||||
|
placeholder='Search entries... (press "/")'
|
||||||
|
class="w-full px-4 py-2 border border-gray-200 rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user