import { Combobox } from "@headlessui/react"; import { DropDownItem } from "@shared/nej-react-components/Parts/DropDown"; import { useEffect, useRef, useState } from "react"; import tw, { styled } from "twin.macro"; import "styled-components/macro"; import { useField } from "@shared/nej-react-components/Parts/Input"; export function AutocompleteSelect({ title, titleProps = null, data, nullable = false, renderOption = undefined, onScrollEnd = undefined, onQueryChange = undefined, ...props }) { const [field, meta, helpers] = useField(props); const [query, setQuery] = useState(field.value ?? ""); const [internalData, setInternalData] = useState([]); const sentinelRef = useRef(null); useEffect(() => { if (!onScrollEnd || !sentinelRef.current) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) onScrollEnd(); }, { threshold: 0.1 } ); observer.observe(sentinelRef.current); return () => observer.disconnect(); }, [onScrollEnd]); useEffect(() => { (async () => { if (typeof data === "function") { //if data is async function await it setInternalData(await data(query)); } else if (Array.isArray(data)) { // When renderOption is provided the caller manages filtering externally, just show data as-is if (renderOption) { setInternalData(data); } else { setInternalData( query === "" ? data : data.filter((item) => { if (typeof item === "string") return item.toLowerCase().includes(query.toLowerCase()); return item.label.toLowerCase().includes(query.toLowerCase()); }) ); } } else if (typeof data === "object") { // TypeScript enum objects — convert values to { value, label } then filter const enumItems = Object.values(data).map((value) => ({ value, label: value })); setInternalData( !renderOption && query !== "" ? enumItems.filter((item) => String(item.label).toLowerCase().includes(query.toLowerCase())) : enumItems ); } else { console.error("data is not an array or function :c"); setInternalData([]); } })(); }, [query, data]); return ( <> typeof item === "string" ? item == field.value : item.value === field.value )[0] : null } onChange={(value) => { // If value is an object (from internalData lookup), store only the primitive .value const stored = value != null && typeof value === "object" ? value.value : value; field.onChange({ target: { value: stored, name: field.name } }); }} nullable={nullable} >
{title && ( )}
{ setQuery(event.target.value); onQueryChange?.(event.target.value); }} displayValue={(val) => (typeof val === "string" ? val : val?.label)} />
{internalData?.map((val) => ( {renderOption ? (state) => renderOption(val, state) : {typeof val === "string" ? val : val.label} } ))} {onScrollEnd &&
}
{meta?.touched && meta.error ? (
{meta.error}
) : null} ); }