70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
import React, { useState, useCallback, useRef } from 'react'
|
|
import { cn } from '../lib/utils'
|
|
import { Search, X } from 'lucide-react'
|
|
|
|
interface SearchBarProps {
|
|
placeholder?: string
|
|
onSearch?: (query: string) => void
|
|
debounceMs?: number
|
|
className?: string
|
|
value?: string
|
|
onChange?: (value: string) => void
|
|
}
|
|
|
|
export function SearchBar({
|
|
placeholder = 'Search...',
|
|
onSearch,
|
|
debounceMs = 300,
|
|
className,
|
|
value: controlledValue,
|
|
onChange,
|
|
}: SearchBarProps) {
|
|
const [internalValue, setInternalValue] = useState('')
|
|
const timerRef = useRef<ReturnType<typeof setTimeout>>(null)
|
|
|
|
const value = controlledValue !== undefined ? controlledValue : internalValue
|
|
const setValue = onChange || setInternalValue
|
|
|
|
const handleChange = useCallback(
|
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const newValue = e.target.value
|
|
setValue(newValue)
|
|
if (timerRef.current) clearTimeout(timerRef.current)
|
|
timerRef.current = setTimeout(() => {
|
|
onSearch?.(newValue)
|
|
}, debounceMs)
|
|
},
|
|
[onSearch, debounceMs, setValue]
|
|
)
|
|
|
|
const handleClear = useCallback(() => {
|
|
setValue('')
|
|
onSearch?.('')
|
|
}, [onSearch, setValue])
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex w-full flex-row items-center rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm',
|
|
className
|
|
)}
|
|
>
|
|
<input
|
|
type="text"
|
|
value={value}
|
|
onChange={handleChange}
|
|
placeholder={placeholder}
|
|
className="min-w-0 flex-1 bg-transparent text-sm text-gray-800 placeholder:text-gray-400 !outline-none focus:!outline-none focus-visible:!outline-none"
|
|
/>
|
|
{value && (
|
|
<button
|
|
onClick={handleClear}
|
|
className="mr-2 shrink-0 text-gray-400 hover:text-gray-600"
|
|
>
|
|
<X className="h-5 w-5" />
|
|
</button>
|
|
)}
|
|
<Search className="h-5 w-5 shrink-0 text-brand-500" />
|
|
</div>
|
|
)
|
|
}
|