fix: version 2.13.1 -- ComboBox/Flyout no se salen del viewport
El panel se posicionaba una sola vez hacia abajo/derecha del trigger sin revisar si cabia. Ahora se mide el tamaño ya renderizado y se voltea hacia arriba / se recorta hacia adentro si no hay espacio -- regla agregada tambien al orquestador del grupo (project-md-templates/leeme_primero.txt). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ChevronDown, Search } from 'lucide-react';
|
||||
|
||||
@@ -10,16 +10,40 @@ import { ChevronDown, Search } from 'lucide-react';
|
||||
export function ComboBox({ options, value, onChange, placeholder = '', searchThreshold = 10 }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const [pos, setPos] = useState(null);
|
||||
const [pos, setPos] = useState(null); // { top, left, width, anchorTop, anchorBottom, ready }
|
||||
const triggerRef = useRef(null);
|
||||
const panelRef = useRef(null);
|
||||
|
||||
function openPanel() {
|
||||
const rect = triggerRef.current.getBoundingClientRect();
|
||||
setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width });
|
||||
setPos({
|
||||
top: rect.bottom + 4, left: rect.left, width: rect.width,
|
||||
anchorTop: rect.top, anchorBottom: rect.bottom, ready: false,
|
||||
});
|
||||
setOpen(true);
|
||||
}
|
||||
|
||||
// Fase 2 (igual tecnica que Flyout): con el panel ya en el DOM (oculto) se mide su alto real y,
|
||||
// si no cabe hacia abajo, se voltea hacia arriba del trigger -- nunca se deja cortado por el
|
||||
// borde inferior del viewport (ver project-md-templates/leeme_primero.txt, seccion UI/UX).
|
||||
useLayoutEffect(() => {
|
||||
if (!open || !pos || pos.ready || !panelRef.current) return;
|
||||
const panelRect = panelRef.current.getBoundingClientRect();
|
||||
const margin = 8;
|
||||
|
||||
let top = pos.top;
|
||||
if (pos.anchorBottom + 4 + panelRect.height > window.innerHeight - margin) {
|
||||
const openUpTop = pos.anchorTop - 4 - panelRect.height;
|
||||
top = openUpTop >= margin ? openUpTop : Math.max(margin, window.innerHeight - margin - panelRect.height);
|
||||
}
|
||||
|
||||
let left = pos.left;
|
||||
if (left + pos.width > window.innerWidth - margin) left = window.innerWidth - margin - pos.width;
|
||||
if (left < margin) left = margin;
|
||||
|
||||
setPos((p) => ({ ...p, top, left, ready: true }));
|
||||
}, [open, pos]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
function onDocMouseDown(e) {
|
||||
@@ -57,7 +81,7 @@ export function ComboBox({ options, value, onChange, placeholder = '', searchThr
|
||||
<ChevronDown size={14} />
|
||||
</button>
|
||||
{open && pos && createPortal(
|
||||
<div ref={panelRef} className="combobox-panel" style={{ top: pos.top, left: pos.left, width: pos.width }}>
|
||||
<div ref={panelRef} className="combobox-panel" style={{ top: pos.top, left: pos.left, width: pos.width, visibility: pos.ready ? 'visible' : 'hidden' }}>
|
||||
{options.length >= searchThreshold && (
|
||||
<div className="combobox-search" style={{ position: 'relative' }}>
|
||||
<Search size={13} style={{ position: 'absolute', left: 8, top: 9, opacity: .6 }} />
|
||||
|
||||
Reference in New Issue
Block a user