fix(supply-quick-add): dropdown not rendering — Portal + fixed position
Some checks are pending
Some checks are pending
Корень проблемы: Section рендерится с класcом overflow-hidden (нужен для скруглений углов) — absolute-позиционированный dropdown обрезался границей карточки и был не виден совсем. Решение: dropdown через React createPortal вынесен в document.body, позиция вычисляется по getBoundingClientRect() input'а на каждый open + window scroll/resize. position: fixed, z-[100] — выше любого sticky-header. Outside-click handler теперь учитывает оба контейнера (input wrap + portal-узел) — клик по элементу dropdown'а больше не закрывает его как «снаружи». Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
43bf1dc3de
commit
c8a7efde47
|
|
@ -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<HTMLInputElement>(null)
|
||||
const wrapRef = useRef<HTMLDivElement>(null)
|
||||
const dropdownRef = useRef<HTMLDivElement>(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}
|
||||
</div>
|
||||
)}
|
||||
{open && (query.trim().length > 0 || items.length > 0) && (
|
||||
<div className="absolute z-30 mt-1 w-full rounded-md border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-lg max-h-80 overflow-auto">
|
||||
{open && (query.trim().length > 0 || items.length > 0) && dropdownPos && createPortal(
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
style={{ position: 'fixed', top: dropdownPos.top, left: dropdownPos.left, width: dropdownPos.width }}
|
||||
className="z-[100] rounded-md border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 shadow-lg max-h-80 overflow-auto"
|
||||
>
|
||||
{loading && items.length === 0 ? (
|
||||
<div className="px-3 py-2 text-sm text-slate-400">Ищу…</div>
|
||||
) : items.length === 0 ? (
|
||||
|
|
@ -275,7 +306,8 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) {
|
|||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
|
||||
<ProductQuickCreateModal
|
||||
|
|
|
|||
Loading…
Reference in a new issue