mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-06 22:13:04 +00:00
fix(dashboard): size the touch context menu as a card on tablets
The touch menu took 90% of the viewport, which on an iPad meant a ~920px strip with its labels far from the row they belong to. It now stops at 400px, which every phone stays under. Its height is also measured after the width is set, so the clamp to the viewport uses the real height.
This commit is contained in:
1 parent
127bf3c827
commit
619e184c14
2 files changed
+55
-4
No files matched your search
@@ -16,6 +16,9 @@ function isTouchPrimaryDevice () {
|
||||
);
|
||||
}
|
||||
|
||||
/** Widest the touch menu gets, so phones (90% of up to ~440px) are unaffected. */
|
||||
const MAX_TOUCH_MENU_WIDTH = 400;
|
||||
|
||||
/** Labels of the destructive file actions, in the current language. */
|
||||
const dangerLabels = () => new Set([i18n('delete'), i18n('delete_permanently')]);
|
||||
|
||||
@@ -103,13 +106,20 @@ export default class ContextMenuModal {
|
||||
*/
|
||||
positionModal (targetRect) {
|
||||
const isMobile = isTouchPrimaryDevice();
|
||||
const modalHeight = this.modal.offsetHeight;
|
||||
const modalWidth = this.modal.offsetWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const margin = 20; // Minimum margin from viewport edges
|
||||
|
||||
const width = isMobile ? viewportWidth * 0.9 : modalWidth;
|
||||
// Touch: most of a phone's width, but a phone-sized card on a tablet
|
||||
// rather than a strip spanning the screen. Applied before measuring
|
||||
// the height, which depends on how the labels wrap at this width.
|
||||
if ( isMobile ) {
|
||||
this.modal.style.width = `${Math.min(viewportWidth * 0.9, MAX_TOUCH_MENU_WIDTH)}px`;
|
||||
} else {
|
||||
this.modal.style.width = 'auto';
|
||||
}
|
||||
const modalHeight = this.modal.offsetHeight;
|
||||
const width = this.modal.offsetWidth;
|
||||
|
||||
// Center over the target horizontally, clamped to the viewport. (The
|
||||
// old code hardcoded left:300px on non-touch devices — which used to
|
||||
@@ -131,7 +141,6 @@ export default class ContextMenuModal {
|
||||
|
||||
this.modal.style.top = `${top}px`;
|
||||
this.modal.style.left = `${left}px`;
|
||||
this.modal.style.width = isMobile ? '90%' : 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// @vitest-environment jsdom
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
globalThis.i18n = (key) => key;
|
||||
|
||||
const { default: ContextMenuModal } = await import('./ContextMenu.js');
|
||||
|
||||
const setTouchViewport = (width, touch = true) => {
|
||||
window.innerWidth = width;
|
||||
window.innerHeight = 800;
|
||||
window.matchMedia = (query) => ({
|
||||
matches: touch && (query === '(pointer: coarse)' || query === '(hover: none)'),
|
||||
});
|
||||
};
|
||||
|
||||
const openMenu = () => {
|
||||
const menu = new ContextMenuModal();
|
||||
menu.show([{ label: 'Open', action: () => {} }], { left: 100, top: 100, width: 200, height: 30 });
|
||||
return document.querySelector('.context-menu-modal-dialog');
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('ContextMenuModal width', () => {
|
||||
it('takes 90% of a phone screen', () => {
|
||||
setTouchViewport(390);
|
||||
expect(openMenu().style.width).toBe('351px');
|
||||
});
|
||||
|
||||
it('stops at a card width on a tablet instead of spanning the screen', () => {
|
||||
setTouchViewport(1024);
|
||||
expect(openMenu().style.width).toBe('400px');
|
||||
});
|
||||
|
||||
it('sizes to its content for a mouse', () => {
|
||||
setTouchViewport(1024, false);
|
||||
expect(openMenu().style.width).toBe('auto');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user