diff --git a/App/components/ui/searchable-select.tsx b/App/components/ui/searchable-select.tsx index 4e2dc1b..71a7168 100644 --- a/App/components/ui/searchable-select.tsx +++ b/App/components/ui/searchable-select.tsx @@ -34,13 +34,12 @@ export function SearchableSelect({ useEffect(() => { setMounted(true); }, []); - // Recalculate portal position whenever the dropdown opens - useLayoutEffect(() => { - if (!open || !containerRef.current) return; + // Recalculate portal position on open, scroll, and resize so the panel tracks + // the trigger even when the page (or any ancestor) is scrolled. + const updatePortalPos = useCallback(() => { + if (!containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); const PANEL_WIDTH = 420; - // Prefer right-aligning with the trigger; clamp so it doesn't go off-screen left - const right = window.innerWidth - rect.right; const left = Math.max(8, rect.right - PANEL_WIDTH); setPortalStyle({ position: "fixed", @@ -49,7 +48,23 @@ export function SearchableSelect({ width: PANEL_WIDTH, zIndex: 9999, }); - }, [open]); + }, []); + + useLayoutEffect(() => { + if (!open) return; + updatePortalPos(); + }, [open, updatePortalPos]); + + useEffect(() => { + if (!open) return; + // capture:true catches scroll on any ancestor, not just window + window.addEventListener("scroll", updatePortalPos, true); + window.addEventListener("resize", updatePortalPos); + return () => { + window.removeEventListener("scroll", updatePortalPos, true); + window.removeEventListener("resize", updatePortalPos); + }; + }, [open, updatePortalPos]); // Close on outside click / Escape useEffect(() => {