import { useState, useRef, useEffect, useCallback, useId } from "react"; import { createPortal } from "react-dom"; import { useQuery } from "@tanstack/react-query"; import { warehouseItemListOptions } from "../../lib/queries/warehouse"; interface ItemPickerProps { value: number | null; onChange: (itemId: number) => void; itemName?: string; } export default function ItemPicker({ value, onChange, itemName, }: ItemPickerProps) { const [search, setSearch] = useState(itemName ?? ""); const [open, setOpen] = useState(false); const { data } = useQuery(warehouseItemListOptions({ search, perPage: 20 })); const items = data?.data ?? []; const containerRef = useRef(null); const activeIndexRef = useRef(-1); const listboxId = useId(); const [activeIndex, setActiveIndex] = useState(-1); const [dropdownStyle, setDropdownStyle] = useState<{ position: "fixed"; top: number; left: number; width: number; zIndex: number; } | null>(null); const updatePosition = useCallback(() => { if (!containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); setDropdownStyle({ position: "fixed", top: rect.bottom + 2, left: rect.left, width: rect.width, zIndex: 100, }); }, []); // Reset active index when items change useEffect(() => { if (activeIndex >= items.length) { activeIndexRef.current = -1; setActiveIndex(-1); } }, [items, activeIndex]); useEffect(() => { if (open) { updatePosition(); const onScroll = () => updatePosition(); const onClose = () => setOpen(false); window.addEventListener("scroll", onScroll, true); window.addEventListener("resize", onClose); return () => { window.removeEventListener("scroll", onScroll, true); window.removeEventListener("resize", onClose); }; } else { setDropdownStyle(null); activeIndexRef.current = -1; setActiveIndex(-1); } }, [open, updatePosition]); // Close on click-outside via mousedown on document useEffect(() => { if (!open) return; const onDocMouseDown = (e: MouseEvent) => { const target = e.target; if (containerRef.current && target instanceof Node) { if (!containerRef.current.contains(target)) { // Don't close if click landed on an option in the portal const listEl = document.getElementById(listboxId); if (listEl && listEl.contains(target)) return; setOpen(false); } } }; document.addEventListener("mousedown", onDocMouseDown); return () => document.removeEventListener("mousedown", onDocMouseDown); }, [open, listboxId]); const handleSelect = (itemId: number, name: string) => { onChange(itemId); setOpen(false); setSearch(name); }; const setActive = (index: number) => { activeIndexRef.current = index; setActiveIndex(index); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "ArrowDown") { e.preventDefault(); if (!open) { setOpen(true); if (items.length > 0) setActive(0); return; } const next = activeIndexRef.current < items.length - 1 ? activeIndexRef.current + 1 : 0; setActive(next); } else if (e.key === "ArrowUp") { e.preventDefault(); if (!open) { setOpen(true); if (items.length > 0) setActive(items.length - 1); return; } const prev = activeIndexRef.current > 0 ? activeIndexRef.current - 1 : items.length - 1; setActive(prev); } else if (e.key === "Enter") { if ( open && activeIndexRef.current >= 0 && activeIndexRef.current < items.length ) { e.preventDefault(); const item = items[activeIndexRef.current]; handleSelect(item.id, item.name); } } else if (e.key === "Escape") { if (open) { e.preventDefault(); setOpen(false); } } else if (e.key === "Home") { if (open && items.length > 0) { e.preventDefault(); setActive(0); } } else if (e.key === "End") { if (open && items.length > 0) { e.preventDefault(); setActive(items.length - 1); } } else if (e.key === "Tab") { setOpen(false); } }; const activeId = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : undefined; return (
{ setSearch(e.target.value); setOpen(true); }} onFocus={() => { setOpen(true); updatePosition(); }} onKeyDown={handleKeyDown} /> {open && items.length > 0 && dropdownStyle && createPortal(
    {items.map((item, index) => (
  • { e.preventDefault(); handleSelect(item.id, item.name); }} > {item.name} {item.item_number && ( {item.item_number} )} {item.available_quantity !== undefined && ( {item.available_quantity} {item.unit} )}
  • ))}
, document.body, )}
); }