fix: keep date and size filters usable when the Mountain View search field is narrow

This commit is contained in:
Linus Rath
2026-11-03 07:33:21 +01:00
parent 94c85cc9f1
commit 154cbe7c82
2 changed files with 33 additions and 3 deletions
@@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { FilterPanelHost } from '../filter-panel-host';
import { FilterPanelHost, filterPanelBox } from '../filter-panel-host';
// Mountain View moves the filter button into the global top bar. The panel
// follows it there, given an anchor; without one it stays in the flow of the
@@ -36,6 +36,17 @@ describe('FilterPanelHost', () => {
anchor.remove();
});
it('stays wide enough for its fields when the search field is narrow', () => {
// The field is 286px wide at 1024: a panel that size cut the date fields to "dd".
expect(filterPanelBox({ left: 165, right: 451, bottom: 56, width: 286 }, 1024, false)).toEqual({ top: 60, left: 165, width: 480 });
});
it('grows towards the trailing side in RTL and stays on screen', () => {
expect(filterPanelBox({ left: 573, right: 859, bottom: 56, width: 286 }, 1024, true)).toEqual({ top: 60, left: 379, width: 480 });
expect(filterPanelBox({ left: 700, right: 986, bottom: 56, width: 286 }, 1024, false).left).toBe(536);
expect(filterPanelBox({ left: 20, right: 300, bottom: 56, width: 280 }, 400, false)).toEqual({ top: 60, left: 8, width: 384 });
});
it('draws it at the end of <body>, above the sidebar column and not inside the top bar', () => {
const anchor = document.createElement('div');
document.body.appendChild(anchor);
+21 -2
View File
@@ -52,6 +52,26 @@ export function FilterPanelHost({
);
}
/** Below this the form's labelled rows leave its date and size fields a few characters wide. */
const MIN_PANEL_WIDTH = 480;
const VIEWPORT_MARGIN = 8;
/**
* Where the panel goes for a given search field: under it, sharing its leading
* edge and width. The field shrinks with the window, so a narrow one gets a
* panel wider than itself, growing towards the trailing side and kept on screen.
*/
export function filterPanelBox(
rect: { left: number; right: number; bottom: number; width: number },
viewportWidth: number,
rtl: boolean,
): CSSProperties {
const width = Math.max(rect.width, Math.min(MIN_PANEL_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2));
const preferred = rtl ? rect.right - width : rect.left;
const left = Math.max(VIEWPORT_MARGIN, Math.min(preferred, viewportWidth - width - VIEWPORT_MARGIN));
return { top: rect.bottom + 4, left, width };
}
/** The anchor's leading edge, width and bottom, kept current as the bar resizes. */
function useAnchorBox(anchor: HTMLElement | null): CSSProperties | null {
const [box, setBox] = useState<CSSProperties | null>(null);
@@ -62,8 +82,7 @@ function useAnchorBox(anchor: HTMLElement | null): CSSProperties | null {
return;
}
const measure = () => {
const rect = anchor.getBoundingClientRect();
setBox({ top: rect.bottom + 4, left: rect.left, width: rect.width });
setBox(filterPanelBox(anchor.getBoundingClientRect(), window.innerWidth, getComputedStyle(anchor).direction === "rtl"));
};
measure();