From 10497a6307385a5827295ad73dbed7d3bdd3dcfa Mon Sep 17 00:00:00 2001 From: SITO Date: Sat, 9 May 2026 00:27:39 +0200 Subject: [PATCH] =?UTF-8?q?fix(mobile):=20paginaci=C3=B3n=202-en-2=20con?= =?UTF-8?q?=20flechas,=20sin=20scroll=20del=20dedo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mobile-ui.js: el contenedor se mueve por translateX en pasos de ~2 botones en lugar de scrollLeft. Eliminado el botón "Ver todos". - mobile.css: nuevo .mode-buttons-clip con overflow:hidden para bloquear el scroll por gesto. La fila usa width:max-content y transition en transform. - Flechas más grandes (36x40, font 1.7rem) para que sean visibles y fáciles de pulsar. Co-Authored-By: Claude Sonnet 4.6 --- .../src/client/assets/styles/mobile.css | 75 +++++++------------ .../src/client/public/js/mobile-ui.js | 71 ++++++++---------- 2 files changed, 60 insertions(+), 86 deletions(-) diff --git a/nodejs-project/nodejs-project/src/client/assets/styles/mobile.css b/nodejs-project/nodejs-project/src/client/assets/styles/mobile.css index 77ddd3f8..6297a100 100644 --- a/nodejs-project/nodejs-project/src/client/assets/styles/mobile.css +++ b/nodejs-project/nodejs-project/src/client/assets/styles/mobile.css @@ -741,45 +741,50 @@ pre, code { Scroll horizontal con flechas + expand/collapse ============================================================ */ -/* Wrapper: flechas + contenedor alineados en fila */ +/* Wrapper: flechas + clip alineados en fila */ .mode-buttons-wrap { display: flex !important; align-items: center !important; - gap: 4px !important; + gap: 6px !important; width: 100% !important; margin-top: 12px !important; } -/* Contenedor de botones: scroll horizontal, sin wrap */ -.mode-buttons-wrap .mode-buttons { - flex: 1 !important; - overflow-x: auto !important; +/* Clip: oculta lo que no cabe, sin scroll de dedo */ +.mode-buttons-clip { + flex: 1 1 0 !important; + min-width: 0 !important; + overflow: hidden !important; + position: relative !important; +} + +/* Grid/buttons dentro del clip: una sola fila, ancho del contenido */ +.mode-buttons-clip > .mode-buttons, +.mode-buttons-clip > .activity-filter-grid { + display: flex !important; + flex-direction: row !important; flex-wrap: nowrap !important; - scrollbar-width: none !important; - scroll-behavior: smooth !important; - margin-top: 0 !important; - padding-bottom: 4px !important; -} -.mode-buttons-wrap .mode-buttons::-webkit-scrollbar { - display: none !important; + width: max-content !important; + max-width: none !important; + overflow: visible !important; + margin: 0 !important; + padding: 0 !important; + gap: 6px !important; + transition: transform 0.25s ease !important; + will-change: transform !important; } -/* Cuando está expandido: vuelve a wrap para mostrar todo en grid */ -.mode-buttons-wrap.mode-buttons-expanded .mode-buttons { - overflow-x: visible !important; - flex-wrap: wrap !important; -} - -/* Flechas de scroll */ +/* Flechas de paginación */ .mode-btn-arrow { flex-shrink: 0 !important; - width: 30px !important; - height: 34px !important; + width: 36px !important; + height: 40px !important; display: flex !important; align-items: center !important; justify-content: center !important; border-radius: 8px !important; - font-size: 1.6rem !important; + font-size: 1.7rem !important; + font-weight: 700 !important; line-height: 1 !important; cursor: pointer !important; padding: 0 !important; @@ -838,27 +843,3 @@ pre, code { font-size: 0.85rem !important; } -/* Wrapper aplica también al activity-filter-grid */ -.mode-buttons-wrap .activity-filter-grid { - flex: 1 1 0 !important; - width: auto !important; - min-width: 0 !important; - overflow-x: auto !important; - flex-wrap: nowrap !important; - scrollbar-width: none !important; - scroll-behavior: smooth !important; - padding-bottom: 4px !important; -} -.mode-buttons-wrap .activity-filter-grid::-webkit-scrollbar { - display: none !important; -} -.mode-buttons-wrap.mode-buttons-expanded .activity-filter-grid { - overflow-x: visible !important; - flex-wrap: wrap !important; -} - -/* Asegurar que .mode-buttons dentro del wrap también suelte el width 100% */ -.mode-buttons-wrap .mode-buttons { - width: auto !important; - min-width: 0 !important; -} diff --git a/nodejs-project/nodejs-project/src/client/public/js/mobile-ui.js b/nodejs-project/nodejs-project/src/client/public/js/mobile-ui.js index 652416d8..aec75bc3 100644 --- a/nodejs-project/nodejs-project/src/client/public/js/mobile-ui.js +++ b/nodejs-project/nodejs-project/src/client/public/js/mobile-ui.js @@ -6,74 +6,67 @@ cnt.dataset.ms = '1'; var items = cnt.querySelectorAll('form, button.filter-btn, a.filter-btn'); - var n = items.length; - if (n < 3) return; + if (items.length < 3) return; - // Wrap container with arrows + // Build pager: [arrow-left] [clip > cnt] [arrow-right] var wrap = document.createElement('div'); wrap.className = 'mode-buttons-wrap'; cnt.parentNode.insertBefore(wrap, cnt); - wrap.appendChild(cnt); + + var clip = document.createElement('div'); + clip.className = 'mode-buttons-clip'; + wrap.appendChild(clip); + clip.appendChild(cnt); var la = document.createElement('button'); la.type = 'button'; la.className = 'mode-btn-arrow mode-btn-arrow-left'; la.innerHTML = '‹'; la.setAttribute('aria-label', 'Anterior'); + wrap.insertBefore(la, clip); var ra = document.createElement('button'); ra.type = 'button'; ra.className = 'mode-btn-arrow mode-btn-arrow-right'; ra.innerHTML = '›'; ra.setAttribute('aria-label', 'Siguiente'); - - wrap.insertBefore(la, cnt); wrap.appendChild(ra); - function getStep() { + var offset = 0; + + function step() { var btn = cnt.querySelector('button.filter-btn, a.filter-btn, form'); var w = btn ? btn.getBoundingClientRect().width : 90; if (!w || w < 40) w = 90; return Math.round((w + 6) * 2); } + function maxOffset() { + return Math.max(0, cnt.scrollWidth - clip.clientWidth); + } + function apply() { + var max = maxOffset(); + if (offset > max) offset = max; + if (offset < 0) offset = 0; + cnt.style.transform = 'translateX(' + (-offset) + 'px)'; + var atStart = offset <= 0; + var atEnd = offset >= max - 1; + la.style.opacity = atStart ? '0.25' : '1'; + la.style.pointerEvents = atStart ? 'none' : 'auto'; + ra.style.opacity = atEnd ? '0.25' : '1'; + ra.style.pointerEvents = atEnd ? 'none' : 'auto'; + } la.addEventListener('click', function (e) { e.preventDefault(); - cnt.scrollBy({ left: -getStep(), behavior: 'smooth' }); + offset -= step(); + apply(); }); ra.addEventListener('click', function (e) { e.preventDefault(); - cnt.scrollBy({ left: getStep(), behavior: 'smooth' }); + offset += step(); + apply(); }); - - function syncArrows() { - var atStart = cnt.scrollLeft < 5; - var atEnd = cnt.scrollLeft + cnt.clientWidth >= cnt.scrollWidth - 5; - la.style.opacity = atStart ? '0.2' : '1'; - ra.style.opacity = atEnd ? '0.2' : '1'; - la.style.pointerEvents = atStart ? 'none' : 'auto'; - ra.style.pointerEvents = atEnd ? 'none' : 'auto'; - } - cnt.addEventListener('scroll', syncArrows, { passive: true }); - setTimeout(syncArrows, 120); - - // Expand/collapse toggle for large button groups - if (n > 6) { - var expanded = false; - var xb = document.createElement('button'); - xb.type = 'button'; - xb.className = 'mode-expand-btn'; - xb.textContent = 'Ver todos ▾'; - wrap.parentNode.insertBefore(xb, wrap.nextSibling); - - xb.addEventListener('click', function (e) { - e.preventDefault(); - expanded = !expanded; - cnt.classList.toggle('mode-buttons-expanded', expanded); - wrap.classList.toggle('mode-buttons-expanded', expanded); - xb.textContent = expanded ? 'Ver menos ▴' : 'Ver todos ▾'; - setTimeout(syncArrows, 50); - }); - } + setTimeout(apply, 120); + window.addEventListener('resize', apply); }); }