fix(mobile): keep the dashboard filter row from widening the layout viewport

This commit is contained in:
Maurice
2026-09-01 19:48:51 +02:00
committed by Maurice
parent 46a89e7a90
commit 33e737e453
4 changed files with 53 additions and 12 deletions
+8 -1
View File
@@ -15,7 +15,14 @@ body { height: 100%; overflow: auto; overscroll-behavior: none; -webkit-overflow
reloads the installed PWA. Desktop/tablet keep the rules above. */
@media (max-width: 767px) {
html { overflow: visible; overscroll-behavior: none; }
body { height: auto; overflow: visible; }
/* overflow-x clip (not hidden: clip creates no scroll container) so a screen
that accidentally overflows sideways cannot widen the document. Android's
forced-zoom setting reacts to a wider document by growing the layout
viewport that position:fixed resolves against, which pushed the top bar
and the dock off the right edge on narrow phones. On body, never on html:
body's overflow only propagates to the viewport while html stays visible,
and the shared body scroll lock relies on that propagation. */
body { height: auto; overflow: visible; overflow-x: clip; }
/* Transparent so the shell's own background layer, which sits behind the page
on a negative z-index, is not painted over: a negative layer goes above the
canvas but below the background of ordinary blocks, and body is one. The
+11
View File
@@ -363,3 +363,14 @@
-webkit-appearance: none;
appearance: none;
}
/* Horizontal scroller without a visible scrollbar, for control rows that must
survive narrow viewports and large system font scales (dashboard filter). */
.m-hscroll {
overflow-x: auto;
overscroll-behavior-x: contain;
scrollbar-width: none;
}
.m-hscroll::-webkit-scrollbar {
display: none;
}
@@ -132,17 +132,25 @@ export default function MDashboard(): React.ReactElement {
return (
<>
<div className="mt-[14px] flex items-center gap-[7px]">
<MSegmented<TripFilter>
value={tripFilter}
onChange={setTripFilter}
variant="intrinsic"
options={[
{ value: 'planned', label: t('dashboard.filter.planned') },
{ value: 'archive', label: t('dashboard.archived') },
{ value: 'completed', label: t('dashboard.mobile.completed') },
]}
/>
<MIconBtn ariaLabel={t('dashboard.subscribeAllTrips')} size={36} className="ml-auto" onClick={() => setSubOpen(true)}>
{/* The chips scroll inside their own flexible box on narrow viewports
or large system font scales; without it this row was the widest
thing on the page and dragged the fixed bars off-screen under
Android's forced zoom. min-w-max keeps the pill track wrapping
its chips instead of getting squeezed to the box. */}
<div className="m-hscroll min-w-0 flex-1">
<MSegmented<TripFilter>
value={tripFilter}
onChange={setTripFilter}
variant="intrinsic"
className="min-w-max"
options={[
{ value: 'planned', label: t('dashboard.filter.planned') },
{ value: 'archive', label: t('dashboard.archived') },
{ value: 'completed', label: t('dashboard.mobile.completed') },
]}
/>
</div>
<MIconBtn ariaLabel={t('dashboard.subscribeAllTrips')} size={36} className="flex-none" onClick={() => setSubOpen(true)}>
<CalendarPlus size={15} strokeWidth={2} className="text-m-muted" />
</MIconBtn>
<button
@@ -539,4 +539,19 @@ describe('MDashboard', () => {
await waitFor(() =>
expect(screen.queryByText('dashboard.subscribeAllTripsDesc')).not.toBeInTheDocument());
});
it('FE-MOB-DASH-038: the filter row cannot widen the document on narrow phones (#discord S26)', async () => {
render(<MDashboard />);
// The chip track scrolls inside its own flexible box; without this the row
// was the widest element on the page and dragged the fixed bars off-screen
// under Android's forced zoom.
const chip = await screen.findByText('dashboard.filter.planned');
const track = chip.closest('button')!.parentElement as HTMLElement;
expect(track.className).toContain('min-w-max');
const wrapper = track.parentElement as HTMLElement;
expect(wrapper.className).toContain('m-hscroll');
expect(wrapper.className).toContain('min-w-0');
expect(wrapper.className).toContain('flex-1');
});
});