From 8bde93c03f3a3cb8d7e31b858076100b7cc02489 Mon Sep 17 00:00:00 2001 From: gnacho Date: Wed, 7 Oct 2026 20:49:52 +0200 Subject: [PATCH] feat(nav): horizontally scrollable mobile bottom nav Replace the fixed 4/5-column grid (which depended on the charger extension being on) with a scrollable row: min-width tabs, hidden scrollbar and auto-center on the active tab (keynest #284 pattern). --- app/src/components/shell/AppLayout.tsx | 31 +++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/app/src/components/shell/AppLayout.tsx b/app/src/components/shell/AppLayout.tsx index 529f863..0740042 100644 --- a/app/src/components/shell/AppLayout.tsx +++ b/app/src/components/shell/AppLayout.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, type MouseEvent, type ReactNode } from 'react'; +import { useEffect, useRef, useState, type MouseEvent, type ReactNode } from 'react'; import { Link, NavLink, useLocation, useNavigate } from 'react-router'; import { flushSync } from 'react-dom'; import { motion } from 'framer-motion'; @@ -403,7 +403,16 @@ export default function AppLayout({ children }: { children: ReactNode }) { const titleKey = TITLE_KEYS.find(([re]) => re.test(pathname))?.[1] ?? 'nav.dashboard'; const count = useInverterCount(); - const { navItems, chargerOn } = useNavItems(); + const { navItems } = useNavItems(); + /* Bottom-nav deslizable: centra el tab activo en la fila scrolleable. */ + const bottomNavRef = useRef(null); + useEffect(() => { + const el = bottomNavRef.current; + const activeEl = el?.querySelector('[data-active="true"]'); + if (el && activeEl) { + activeEl.scrollIntoView({ inline: 'center', block: 'nearest' }); + } + }, [pathname]); const resolvedTitleKey = titleKey === 'nav.inversores' ? invLabelKey(count) : titleKey; const lgMargin = collapsed ? 'lg:ml-16' : 'lg:ml-[232px]'; @@ -466,13 +475,21 @@ export default function AppLayout({ children }: { children: ReactNode }) { className="[view-transition-name:helios-nav] fixed bottom-0 left-0 right-0 z-50 border-t border-app bg-surface/85 pb-safe backdrop-blur-[16px] md:hidden" aria-label={t('nav.dashboard')} > - {/* Sin Ajustes: en móvil el acceso es el avatar del header (→ /ajustes) */} -
+ {/* Fila deslizable (keynest #284 / deltos #290 / netpulse #1312): + min-width por tab, scrollbar oculta y auto-scroll al activo. */} +
{navItems.map(({ to, labelKey, icon: Icon }) => { const active = isActive(pathname, to); const label = t(labelKey === 'nav.inversores' ? invLabelKey(count) : labelKey); return ( - + - - - +
); }