diff --git a/src/gui/src/helpers/toolbarEmbed.js b/src/gui/src/helpers/toolbarEmbed.js index 7a7ccf816..a3a35ba47 100644 --- a/src/gui/src/helpers/toolbarEmbed.js +++ b/src/gui/src/helpers/toolbarEmbed.js @@ -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 }; } diff --git a/src/gui/src/helpers/toolbarEmbed.test.js b/src/gui/src/helpers/toolbarEmbed.test.js index 742232174..237d575d0 100644 --- a/src/gui/src/helpers/toolbarEmbed.test.js +++ b/src/gui/src/helpers/toolbarEmbed.test.js @@ -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' } };