109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
import React from "react";
|
|
|
|
interface SearchInputProps {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
onSearch: () => void;
|
|
placeholder?: string;
|
|
loading?: boolean;
|
|
disabled?: boolean;
|
|
minLength?: number;
|
|
}
|
|
|
|
/**
|
|
* SearchInput Component
|
|
* Componente reutilizável para campo de busca com botão de pesquisa
|
|
*
|
|
* @param value - Valor atual do input
|
|
* @param onChange - Callback quando o valor muda
|
|
* @param onSearch - Callback quando o botão de busca é clicado ou Enter é pressionado
|
|
* @param placeholder - Texto placeholder do input
|
|
* @param loading - Indica se está carregando
|
|
* @param disabled - Indica se está desabilitado
|
|
* @param minLength - Tamanho mínimo para habilitar a busca
|
|
*/
|
|
const SearchInput: React.FC<SearchInputProps> = ({
|
|
value,
|
|
onChange,
|
|
onSearch,
|
|
placeholder = "Ex: Cimento, Tijolo, Furadeira...",
|
|
loading = false,
|
|
disabled = false,
|
|
minLength = 3,
|
|
}) => {
|
|
const isValid = value.trim().length >= minLength;
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (e.key === "Enter" && !loading && isValid) {
|
|
onSearch();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="relative group mb-0 lg:mb-10 max-w-full lg:max-w-4xl mx-auto">
|
|
<input
|
|
type="text"
|
|
placeholder={placeholder}
|
|
className="w-full p-3 lg:p-5 pl-10 lg:pl-14 rounded-xl lg:rounded-2xl bg-white shadow-sm lg:shadow-lg shadow-slate-200 outline-none border-2 border-transparent focus:border-orange-500 transition-all text-sm lg:text-base font-medium disabled:opacity-50 disabled:cursor-not-allowed"
|
|
value={value}
|
|
onChange={(e) => onChange(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
disabled={disabled || loading}
|
|
/>
|
|
<button
|
|
onClick={onSearch}
|
|
disabled={loading || !isValid}
|
|
className="absolute right-1.5 lg:right-2 top-1.5 lg:top-2 bottom-1.5 lg:bottom-2 bg-[#002147] text-white px-3 lg:px-6 rounded-lg lg:rounded-xl font-black uppercase text-[10px] lg:text-xs tracking-widest hover:bg-[#003366] transition-all shadow-lg disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center touch-manipulation"
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<svg
|
|
className="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
></circle>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
></path>
|
|
</svg>
|
|
Buscando...
|
|
</>
|
|
) : (
|
|
"Buscar"
|
|
)}
|
|
</button>
|
|
{value.trim().length > 0 && value.trim().length < minLength && (
|
|
<p className="absolute -bottom-6 left-0 text-xs text-slate-400 font-medium mt-1">
|
|
Digite pelo menos {minLength} caracteres
|
|
</p>
|
|
)}
|
|
<svg
|
|
className="absolute left-3 lg:left-8 top-1/2 -translate-y-1/2 w-4 h-4 lg:w-5 lg:h-5 text-slate-300 group-focus-within:text-orange-500 transition-colors"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2.5"
|
|
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SearchInput;
|