testes/src_2/features/prafrot/components/AutocompleteInput.jsx

121 lines
4.6 KiB
JavaScript

import React, { useState, useEffect, useRef } from 'react';
import { Search, ChevronDown, Check } from 'lucide-react';
const AutocompleteInput = ({
label,
value,
onChange,
options = [],
onSelect,
placeholder = "Pesquisar...",
displayKey = "label", // key to show in list
valueKey = "value", // key to use for value
searchKeys = [], // keys to search in. if empty, searches displayKey
className = "",
disabled = false,
required = false
}) => {
const [isOpen, setIsOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState(value || '');
const wrapperRef = useRef(null);
useEffect(() => {
setSearchTerm(value || '');
}, [value]);
useEffect(() => {
const handleClickOutside = (event) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const filteredOptions = options.filter(option => {
if (!searchTerm) return true;
const term = searchTerm.toLowerCase();
// If searchKeys provided, search in those
if (searchKeys.length > 0) {
return searchKeys.some(key => {
const val = option[key];
return val && String(val).toLowerCase().includes(term);
});
}
// Otherwise search in displayKey or direct string
const label = typeof option === 'object' ? option[displayKey] : option;
return String(label).toLowerCase().includes(term);
});
const handleSelect = (option) => {
const displayVal = typeof option === 'object' ? option[displayKey] : option;
const actualVal = typeof option === 'object' ? (option[valueKey] || option[displayKey]) : option;
setSearchTerm(displayVal);
onChange(displayVal); // Update the input value
if (onSelect) onSelect(option); // Trigger specific selection logic
setIsOpen(false);
};
return (
<div className={`space-y-1.5 relative ${className}`} ref={wrapperRef}>
{label && (
<label className="text-[10px] uppercase font-bold text-slate-500 dark:text-slate-400 tracking-wider ml-1">
{label} {required && <span className="text-red-500">*</span>}
</label>
)}
<div className="relative">
<input
type="text"
className="w-full bg-slate-50 dark:bg-[#141414] border border-slate-200 dark:border-[#333] rounded-lg pl-3 pr-8 py-2 text-sm text-slate-700 dark:text-slate-200 focus:outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-all placeholder:text-slate-400 dark:placeholder:text-slate-700"
placeholder={placeholder}
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
onChange(e.target.value);
setIsOpen(true);
}}
onFocus={() => setIsOpen(true)}
disabled={disabled}
/>
<ChevronDown
size={16}
className={`absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
/>
{isOpen && filteredOptions.length > 0 && (
<ul className="absolute z-50 w-full mt-1 bg-white dark:bg-[#1c1c1c] border border-slate-200 dark:border-[#333] rounded-lg shadow-xl max-h-60 overflow-auto custom-scrollbar">
{filteredOptions.map((option, index) => {
const display = typeof option === 'object' ? option[displayKey] : option;
const isSelected = display === value;
return (
<li
key={index}
className={`px-3 py-2 text-sm cursor-pointer flex items-center justify-between hover:bg-slate-100 dark:hover:bg-[#2a2a2a] ${
isSelected ? 'bg-emerald-50 dark:bg-emerald-900/20 text-emerald-600 dark:text-emerald-400 font-medium' : 'text-slate-700 dark:text-slate-300'
}`}
onClick={() => handleSelect(option)}
>
<span>{display}</span>
{isSelected && <Check size={14} />}
</li>
);
})}
</ul>
)}
{isOpen && filteredOptions.length === 0 && searchTerm && (
<div className="absolute z-50 w-full mt-1 bg-white dark:bg-[#1c1c1c] border border-slate-200 dark:border-[#333] rounded-lg shadow-xl p-3 text-sm text-slate-500 text-center">
Sem resultados
</div>
)}
</div>
</div>
);
};
export default AutocompleteInput;