freshyo/packages/web-components/src/components/search-bar.tsx
2026-05-10 16:45:39 +05:30

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>
)
}