Keep toolbar buttons in place when a menu opens near the viewport edge

On phones the expanded frame was clamped to an 8px viewport margin, which
moved the whole toolbar row (by the header padding minus 8px) under the
user's finger. The frame now shrinks to fit beside and below the collapsed
toolbar, and only falls back to shifting when there isn't enough room.
This commit is contained in:
jelveh committed 2026-10-01 22:17:18 -07:00
1 parent a338c8db46
commit ed32c212d3
2 files changed
+42 -7

No files matched your search

+27 -7
View File
@@ -22,12 +22,32 @@ export function shouldShowUpgrade (user) {
return !user.subscription?.active && !user.team?.uid;
}
const MIN_PANEL_WIDTH = 280;
const MIN_PANEL_HEIGHT = 240;
/**
* Places the expanded toolbar frame inside the viewport. It grows left and down
* from the collapsed frame and shrinks rather than shifts when there is room, so
* the toolbar buttons stay under the pointer.
*/
export function toolbarBounds (anchor, viewport, panel, requestedHeight) {
const width = Math.min(panel === 'apps' ? 440 : 360, viewport.width - 16);
const height = Math.min(panel === 'apps' ? 620 : (Number.isFinite(requestedHeight) ? Math.max(180, Math.min(requestedHeight, 800)) : 560), viewport.height - 16);
return {
width: Math.max(0, width), height: Math.max(0, height),
left: Math.max(8, Math.min(anchor.right - width, viewport.width - width - 8)),
top: Math.max(8, Math.min(anchor.top, viewport.height - height - 8)),
};
let width = Math.max(0, Math.min(panel === 'apps' ? 440 : 360, viewport.width - 16));
let height = Math.max(0, Math.min(panel === 'apps' ? 620 : (Number.isFinite(requestedHeight) ? Math.max(180, Math.min(requestedHeight, 800)) : 560), viewport.height - 16));
let left;
let top;
const roomLeft = anchor.right - 8;
if ( anchor.right <= viewport.width - 8 && roomLeft >= Math.min(width, MIN_PANEL_WIDTH) ) {
width = Math.min(width, roomLeft);
left = anchor.right - width;
} else {
left = Math.max(8, Math.min(anchor.right - width, viewport.width - width - 8));
}
const roomBelow = viewport.height - anchor.top - 8;
if ( anchor.top >= 8 && roomBelow >= Math.min(height, MIN_PANEL_HEIGHT) ) {
height = Math.min(height, roomBelow);
top = anchor.top;
} else {
top = Math.max(8, Math.min(anchor.top, viewport.height - height - 8));
}
return { width, height, left, top };
}
+15
View File
@@ -34,6 +34,21 @@ describe('toolbar integration', () => {
}
}
});
it('keeps the toolbar buttons in place when the menu fits beside them', () => {
for ( const [viewport, anchor] of [
[{ width: 390, height: 700 }, { right: 374, top: 8 }],
[{ width: 320, height: 560 }, { right: 304, top: 12 }],
[{ width: 1100, height: 600 }, { right: 1084, top: 12 }],
] ) {
for ( const panel of ['apps', 'account'] ) {
const box = toolbarBounds(anchor, viewport, panel);
expect(box.left + box.width).toBe(anchor.right);
expect(box.top).toBe(anchor.top);
expect(box.left).toBeGreaterThanOrEqual(8);
expect(box.top + box.height).toBeLessThanOrEqual(viewport.height - 8);
}
}
});
it('opens an upgrade deep link once the billing UI is ready', () => {
let opens = 0;
const target = { location: { href: 'https://puter.com/dashboard?upgrade=1#usage' } };