diff --git a/src/food-market.web/src/components/SupplyLineQuickAdd.tsx b/src/food-market.web/src/components/SupplyLineQuickAdd.tsx index 9f2a1db..333a27a 100644 --- a/src/food-market.web/src/components/SupplyLineQuickAdd.tsx +++ b/src/food-market.web/src/components/SupplyLineQuickAdd.tsx @@ -1,4 +1,5 @@ -import { useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react' +import { useEffect, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react' +import { createPortal } from 'react-dom' import { useMutation } from '@tanstack/react-query' import { Plus } from 'lucide-react' import { api } from '@/lib/api' @@ -62,6 +63,28 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) { )>(null) const inputRef = useRef(null) const wrapRef = useRef(null) + const dropdownRef = useRef(null) + const [dropdownPos, setDropdownPos] = useState<{ top: number; left: number; width: number } | null>(null) + + // Считаем позицию dropdown'а по rect input'а — без этого + // он рендерится через portal в body и будет в углу. + const recomputePos = () => { + const el = inputRef.current + if (!el) return + const r = el.getBoundingClientRect() + setDropdownPos({ top: r.bottom + 4, left: r.left, width: r.width }) + } + useLayoutEffect(() => { + if (!open) return + recomputePos() + const onScrollOrResize = () => recomputePos() + window.addEventListener('scroll', onScrollOrResize, true) + window.addEventListener('resize', onScrollOrResize) + return () => { + window.removeEventListener('scroll', onScrollOrResize, true) + window.removeEventListener('resize', onScrollOrResize) + } + }, [open]) // Автофокус при монтировании useEffect(() => { @@ -92,11 +115,15 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) { return () => { cancelled = true } }, [debounced, storeId]) - // Outside click — закрыть dropdown + // Outside click — закрыть dropdown. Dropdown рендерится в Portal, поэтому + // проверяем и wrap (input), и сам dropdown. useEffect(() => { if (!open) return const onDoc = (e: MouseEvent) => { - if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setOpen(false) + const t = e.target as Node + const inWrap = wrapRef.current?.contains(t) + const inDropdown = dropdownRef.current?.contains(t) + if (!inWrap && !inDropdown) setOpen(false) } document.addEventListener('mousedown', onDoc) return () => document.removeEventListener('mousedown', onDoc) @@ -225,8 +252,12 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) { {hint} )} - {open && (query.trim().length > 0 || items.length > 0) && ( -
+ {open && (query.trim().length > 0 || items.length > 0) && dropdownPos && createPortal( +
{loading && items.length === 0 ? (
Ищу…
) : items.length === 0 ? ( @@ -275,7 +306,8 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) {
)} -
+ , + document.body, )}