From 6e9ad89f7b2433654ab43aae0bd35c05ea52fbbc Mon Sep 17 00:00:00 2001 From: markosbenitez Date: Fri, 26 Jun 2026 20:03:09 -0300 Subject: [PATCH] fix(wizard): z-index dropdown barrio + texto cortado opciones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue A: barrio-autocomplete-list cambia a position:fixed + getBoundingClientRect() para escapar el overflow-y:auto de wizard-answer-area que clippeaba el dropdown. z-index: 20 → 30 (clearear sticky topbar/bottombar z-index:10 en stacking context raíz). Listener de scroll cierra el dropdown si el contenedor scrollea. Issue B: wizard-answer-area max-height 360px → 480px (desktop) / 320px → 400px (mobile) para mostrar ~8 opciones completas. .wizard-answer-area.has-overflow { padding-bottom: 44px } asegura que el fade ::after (28px) cubre espacio vacío, no texto de opción. Co-Authored-By: Claude Sonnet 4.6 --- app/globals.css | 14 +++++----- .../widgets/barrio-autocomplete-field.tsx | 27 +++++++++++++++++-- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/app/globals.css b/app/globals.css index d5b3fc4..e9aa2c9 100644 --- a/app/globals.css +++ b/app/globals.css @@ -292,10 +292,9 @@ body { /* ── Barrio autocomplete ── */ .barrio-autocomplete-wrapper { position: relative; } .barrio-autocomplete-list { - position: absolute; - top: calc(100% + 4px); - left: 0; - right: 0; + /* position: fixed + top/left/width set by JS via getBoundingClientRect() + so the list escapes any overflow:auto ancestor (wizard-answer-area) */ + position: fixed; background: var(--color-surface); border: 1.5px solid var(--color-border-focus); border-radius: var(--radius); @@ -305,7 +304,7 @@ body { padding: 4px 0; max-height: 220px; overflow-y: auto; - z-index: 20; + z-index: 30; } .barrio-autocomplete-item { padding: 9px 12px; @@ -629,10 +628,11 @@ body { .wizard-answer-area { flex: 0 1 auto; min-height: 0; - max-height: 360px; + max-height: 480px; overflow-y: auto; padding-bottom: 8px; } +.wizard-answer-area.has-overflow { padding-bottom: 44px; } /* El fade SOLO se pinta cuando .wizard-answer-area mide overflow real (clase has-overflow agregada por JS — ver wizard-form.tsx). CSS no tiene forma de detectar scrollHeight > clientHeight por sí solo: un ::after @@ -672,7 +672,7 @@ body { @media (max-width: 480px) { .wizard-prompt-text { font-size: 1.1875rem; } .wizard-section-title { font-size: 1.5rem; } - .wizard-answer-area { max-height: 320px; } + .wizard-answer-area { max-height: 400px; } } /* ── Loading skeleton ── */ diff --git a/components/widgets/barrio-autocomplete-field.tsx b/components/widgets/barrio-autocomplete-field.tsx index 8803ad6..69b1e80 100644 --- a/components/widgets/barrio-autocomplete-field.tsx +++ b/components/widgets/barrio-autocomplete-field.tsx @@ -1,5 +1,5 @@ 'use client' -import { useState, useEffect, useRef, type ReactNode } from 'react' +import { useState, useEffect, useRef, type CSSProperties, type ReactNode } from 'react' type GeoData = Record> @@ -27,6 +27,8 @@ export function BarrioAutocompleteField({ const [geoData, setGeoData] = useState(null) const [open, setOpen] = useState(false) const [activeIdx, setActiveIdx] = useState(-1) + const [dropdownStyle, setDropdownStyle] = useState({}) + const inputRef = useRef(null) useEffect(() => { fetch('/data/geo_paraguay_full.json') @@ -35,6 +37,24 @@ export function BarrioAutocompleteField({ .catch(() => {}) }, []) + // Close dropdown on scroll so the fixed-position list doesn't drift from the input + useEffect(() => { + if (!open) return + const close = () => setOpen(false) + window.addEventListener('scroll', close, { passive: true, capture: true }) + return () => window.removeEventListener('scroll', close, { capture: true }) + }, [open]) + + function computeDropdownStyle() { + if (!inputRef.current) return + const rect = inputRef.current.getBoundingClientRect() + setDropdownStyle({ + top: rect.bottom + 4, + left: rect.left, + width: rect.width, + }) + } + const barrios: string[] = (() => { if (!geoData) return [] if (departamento && ciudad) { @@ -54,6 +74,7 @@ export function BarrioAutocompleteField({ function handleInput(v: string) { onChange(v) + computeDropdownStyle() setOpen(true) setActiveIdx(-1) } @@ -104,9 +125,10 @@ export function BarrioAutocompleteField({ aria-activedescendant={activeIdx >= 0 ? `${name}-opt-${activeIdx}` : undefined} autoComplete="off" className="text-short-input" + ref={inputRef} onChange={(e) => handleInput(e.target.value)} onKeyDown={handleKeyDown} - onFocus={() => { if (suggestions.length > 0) setOpen(true) }} + onFocus={() => { if (suggestions.length > 0) { computeDropdownStyle(); setOpen(true) } }} onBlur={() => setTimeout(() => setOpen(false), 150)} /> {open && suggestions.length > 0 && ( @@ -115,6 +137,7 @@ export function BarrioAutocompleteField({ role="listbox" className="barrio-autocomplete-list" aria-label="Sugerencias de barrios" + style={dropdownStyle} > {suggestions.map((barrio, i) => (