From 76d85959f1644e0a75c39f0bfdc2e70f26c6ab40 Mon Sep 17 00:00:00 2001 From: egutierrez Date: Thu, 14 May 2026 14:03:06 +0200 Subject: [PATCH] feat(kanban): sidebar edge zone now toggles (open + close) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 32px left drag zone now also closes the sidebar when it is open. Symmetric behaviour: dwell ≥400ms while dragging closes if open / opens if closed. To prevent a drag that starts with the pointer already inside the sidebar (e.g. dragging a sidebar card itself) from immediately auto- closing, the close action requires the pointer to have left the strip at least once after the drag started. So: - closed + drag-to-edge -> opens (unchanged). - open + drag a card out, then move the card back to the edge -> closes. - open + drag a sidebar card directly to the board -> nothing happens. After a successful toggle the dwell flag resets, so the user must leave the strip and re-enter to trigger another action. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/App.tsx | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f76fecf..1de671e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -193,6 +193,12 @@ export function App() { } let timer: number | null = null; let inside = false; + // Para evitar que un drag iniciado dentro del sidebar abierto dispare un + // cierre inmediato, exigimos que el puntero haya salido de la franja al + // menos una vez tras empezar el drag. Asi: abrir = entrar a la franja + // tras empezar fuera (que ya pasaba); cerrar = salir de la franja y + // volver a entrar. + let hasLeftStrip = false; const clear = () => { if (timer !== null) { window.clearTimeout(timer); @@ -203,16 +209,26 @@ export function App() { const nowInside = ev.clientX <= DRAG_EDGE_WIDTH; if (nowInside === inside) return; inside = nowInside; - setEdgeArmed(nowInside); - if (nowInside) { - if (navOpenRef.current) return; // already open, nothing to do - clear(); - timer = window.setTimeout(() => { - setNavOpen(true); - }, DRAG_EDGE_HOVER_MS); - } else { + // El brillo visible solo cuando "armable": fuera-y-luego-dentro (open + // siempre) o dentro con sidebar cerrado (open trigger). + const armable = nowInside && (!navOpenRef.current || hasLeftStrip); + setEdgeArmed(armable); + if (!nowInside) { + hasLeftStrip = true; clear(); + return; } + // nowInside = true + if (!armable) return; + clear(); + const willOpen = !navOpenRef.current; + timer = window.setTimeout(() => { + setNavOpen(willOpen); + // Tras toggle, resetea el flag para no encadenar otra accion sin + // que el usuario salga + vuelva. + hasLeftStrip = false; + setEdgeArmed(false); + }, DRAG_EDGE_HOVER_MS); }; document.addEventListener("mousemove", onMove); return () => {