From b3ef13616f1216bf81d59772e38d18860da7b48f Mon Sep 17 00:00:00 2001 From: Maurice Date: Wed, 14 Oct 2026 10:45:09 +0200 Subject: [PATCH] fix(nav): put the prerelease badge beside the logo and never let the actions cover the tab pill Both side columns keep their content width, so the pill gives way: it drops its labels to icons when they no longer fit (measured, since plugins and languages change the width) and scrolls only after that. The username is no longer cut at 96px. --- client/src/components/Layout/Navbar.test.tsx | 21 ++++ client/src/components/Layout/Navbar.tsx | 57 +++++---- .../src/hooks/useCollapseOnOverflow.test.ts | 109 ++++++++++++++++++ client/src/hooks/useCollapseOnOverflow.ts | 54 +++++++++ 4 files changed, 219 insertions(+), 22 deletions(-) create mode 100644 client/src/hooks/useCollapseOnOverflow.test.ts create mode 100644 client/src/hooks/useCollapseOnOverflow.ts diff --git a/client/src/components/Layout/Navbar.test.tsx b/client/src/components/Layout/Navbar.test.tsx index 00363749e..4e8bd9af4 100644 --- a/client/src/components/Layout/Navbar.test.tsx +++ b/client/src/components/Layout/Navbar.test.tsx @@ -612,6 +612,27 @@ describe('Navbar layout (#1983)', () => { expect(pill.style.overflowX).toBe('auto'); }); + it('keeps both columns at their content width, so the actions never spill over the pill', () => { + withAddons(8); + const { container } = render(); + const nav = container.querySelector('nav') as HTMLElement; + const columns = Array.from(nav.children).filter(c => c.classList.contains('flex-1')); + // A min-w-0 column shrinks below its content and its justify-end children + // overflow leftwards across the pill (5.0.0-pre.1 feedback). + for (const c of columns) { + expect(c.classList.contains('min-w-0')).toBe(false); + expect(c.classList.contains('min-w-max')).toBe(true); + } + }); + + it('shows the prerelease badge in the brand column, beside the logo', () => { + seedStore(useAuthStore, { isPrerelease: true, appVersion: '5.0.0-pre.1' }); + const { container } = render(); + const nav = container.querySelector('nav') as HTMLElement; + const brandColumn = nav.children[0] as HTMLElement; + expect(brandColumn.contains(screen.getByText('5.0.0-pre.1'))).toBe(true); + }); + it('still renders every addon as a reachable link, however many there are', () => { withAddons(8); render(); diff --git a/client/src/components/Layout/Navbar.tsx b/client/src/components/Layout/Navbar.tsx index e40abb83b..1d49ee221 100644 --- a/client/src/components/Layout/Navbar.tsx +++ b/client/src/components/Layout/Navbar.tsx @@ -12,6 +12,7 @@ import InAppNotificationBell from './InAppNotificationBell.tsx' import HelpButton from '../Help/HelpButton' import { resolvePluginIcon } from '../shared/PluginIcon' import { visibleManagedNavItems } from '../../managed' +import { useCollapseOnOverflow } from '../../hooks/useCollapseOnOverflow' const ADDON_ICONS: Record = { CalendarDays, Briefcase, Globe, Compass, Bookmark } @@ -95,6 +96,16 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }: return translated !== key ? translated : addon.name } + const tabs = [{ id: '__trips', path: '/dashboard', label: t('nav.myTrips'), Icon: Briefcase }, + ...globalAddons.map(a => ({ id: a.id, path: `/${a.id}`, label: getAddonName(a), Icon: ADDON_ICONS[a.icon] || CalendarDays })), + ...pagePlugins.map(p => ({ id: `plugin:${p.id}`, path: `/plugins/${p.id}`, label: p.name, Icon: resolvePluginIcon(p.icon) })), + // Empty in this repository — see client/src/managed. + ...visibleManagedNavItems(user?.role === 'admin').map(m => ({ id: `managed:${m.id}`, path: m.path, label: m.label, Icon: m.Icon })) + ] + // Labels go when they no longer fit beside the logo and the actions; the + // title and aria-label on each tab keep it named. + const { ref: pillRef, collapsed: pillCollapsed } = useCollapseOnOverflow(tabs.map(tab => tab.label).join('|')) + return (