mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: give menu rows, the phone More panel's close and the subject star a 44px touch target
This commit is contained in:
@@ -200,3 +200,14 @@ describe('phone More panel parity', () => {
|
||||
expect(within(menu).queryByRole('menuitem', { name: /keyboard_shortcuts/ })).toBeNull();
|
||||
});
|
||||
});
|
||||
describe('phone touch targets (MAIL-18)', () => {
|
||||
it('draws the panel close at 44px and gives the subject star the touch hit area', () => {
|
||||
renderViewer({ onToggleStar: vi.fn() });
|
||||
const close = screen.getByTestId('more-panel-close');
|
||||
expect(close.className).toContain('h-11');
|
||||
expect(close.className).toContain('w-11');
|
||||
const star = screen.getByTestId('subject-star');
|
||||
expect(star).toHaveAccessibleName('tooltips.star');
|
||||
expect(star.className).toContain('pointer-coarse:after:min-h-11');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4562,16 +4562,16 @@ export function EmailViewer({
|
||||
) : (
|
||||
<span className="text-sm font-semibold text-foreground">{t('more_actions')}</span>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<IconButton
|
||||
size="icon-lg"
|
||||
role="menuitem"
|
||||
aria-label={tCommon('close')}
|
||||
label={tCommon('close')}
|
||||
data-testid="more-panel-close"
|
||||
onClick={() => { setMoreMenuOpen(false); setMoreMenuSub(null); }}
|
||||
className="h-9 w-9"
|
||||
className="-me-2"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</Button>
|
||||
</IconButton>
|
||||
</div>
|
||||
<div ref={mobileSubListRef} className="flex-1 overflow-y-auto py-2">
|
||||
{moreMenuSub === null && (
|
||||
@@ -4808,16 +4808,18 @@ export function EmailViewer({
|
||||
{/* Star inline with subject (top toolbar mode). Mountain View
|
||||
has it on the sender's line instead. */}
|
||||
{toolbarPosition === 'top' && !mvViewer && (
|
||||
<button
|
||||
<IconButton
|
||||
onClick={onToggleStar}
|
||||
className="flex-shrink-0 p-1 rounded hover:bg-muted transition-colors"
|
||||
title={isStarred ? t('tooltips.unstar') : t('tooltips.star')}
|
||||
label={isStarred ? t('tooltips.unstar') : t('tooltips.star')}
|
||||
aria-pressed={!!isStarred}
|
||||
data-testid="subject-star"
|
||||
className="flex-shrink-0 -my-1"
|
||||
>
|
||||
<Star className={cn(
|
||||
"w-4 h-4 lg:w-5 lg:h-5 transition-colors",
|
||||
isStarred ? "fill-amber-400 text-amber-400" : "text-muted-foreground/40"
|
||||
)} />
|
||||
</button>
|
||||
</IconButton>
|
||||
)}
|
||||
{isImportant && (
|
||||
<span className="px-1.5 lg:px-2 py-0.5 bg-warning/15 text-warning rounded-full text-xs font-medium whitespace-nowrap flex-shrink-0 self-center">
|
||||
|
||||
@@ -44,7 +44,7 @@ export function MoveTargetList({ tree, targetIds, onPick, variant = "menu" }: Mo
|
||||
const label = localizeMailboxName(node.role, node.name, (key) => tSidebar(`mailboxes.${key}`));
|
||||
const rowClass = panel
|
||||
? "w-full pe-4 py-2.5 min-h-[44px] text-sm text-start flex items-center gap-3"
|
||||
: "w-full pe-3 py-1.5 text-sm text-start flex items-center gap-2";
|
||||
: "w-full pe-3 py-1.5 text-sm text-start flex items-center gap-2 pointer-coarse:min-h-11";
|
||||
const icon = <Icon className={cn(panel ? "w-5 h-5" : "w-4 h-4", "flex-shrink-0")} />;
|
||||
return (
|
||||
<div key={node.id}>
|
||||
|
||||
@@ -101,7 +101,7 @@ export function TagPicker({
|
||||
|
||||
const rowClass = cn(
|
||||
"w-full text-start flex items-center gap-2 hover:bg-muted cursor-pointer",
|
||||
touch ? "px-4 py-2.5 min-h-[44px] text-sm gap-3" : "px-3 py-1.5 text-sm",
|
||||
touch ? "px-4 py-2.5 min-h-[44px] text-sm gap-3" : "px-3 py-1.5 text-sm pointer-coarse:min-h-11",
|
||||
);
|
||||
const dotClass = touch ? "w-3.5 h-3.5" : "w-3 h-3";
|
||||
const checkClass = touch ? "w-4 h-4" : "w-3.5 h-3.5";
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ContextMenu, ContextMenuSubMenu, ContextMenuItem } from '../context-menu';
|
||||
|
||||
// jsdom can't match `pointer: coarse`, so this checks the rule is on the rows:
|
||||
// 44px on a touch screen (R7), the desktop's own height elsewhere.
|
||||
describe('ContextMenu rows on a touch screen', () => {
|
||||
it('gives items and submenu entries a 44px minimum height under a coarse pointer only', () => {
|
||||
render(
|
||||
<ContextMenu isOpen position={{ x: 10, y: 10 }} onClose={() => {}}>
|
||||
<ContextMenuItem label="Reply" onClick={() => {}} />
|
||||
<ContextMenuSubMenu label="Move to" testId="sub">
|
||||
<ContextMenuItem label="Archive" onClick={() => {}} />
|
||||
</ContextMenuSubMenu>
|
||||
</ContextMenu>,
|
||||
);
|
||||
const item = screen.getByRole('menuitem', { name: 'Reply' });
|
||||
const sub = screen.getByTestId('sub');
|
||||
for (const row of [item, sub]) {
|
||||
expect(row.className).toContain('pointer-coarse:min-h-11');
|
||||
expect(row.className).toContain('py-1.5');
|
||||
expect(row.className).not.toMatch(/(^|\s)min-h-11/);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -512,7 +512,8 @@ export function ContextMenuItem({
|
||||
disabled={disabled}
|
||||
title={title}
|
||||
className={cn(
|
||||
"w-full px-3 py-1.5 text-sm text-start flex items-center gap-2",
|
||||
// 44px rows on a touch screen (R7); the desktop keeps its density.
|
||||
"w-full px-3 py-1.5 text-sm text-start flex items-center gap-2 pointer-coarse:min-h-11",
|
||||
"transition-colors duration-150",
|
||||
"focus:outline-none focus-visible:bg-muted",
|
||||
disabled && "opacity-50 cursor-not-allowed",
|
||||
@@ -637,7 +638,7 @@ export function ContextMenuSubMenu({
|
||||
ref={triggerRef}
|
||||
data-submenu-trigger=""
|
||||
className={cn(
|
||||
"w-full px-3 py-1.5 text-sm flex items-center gap-2 outline-none",
|
||||
"w-full px-3 py-1.5 text-sm flex items-center gap-2 outline-none pointer-coarse:min-h-11",
|
||||
"transition-colors duration-150",
|
||||
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer hover:bg-muted focus-visible:bg-muted",
|
||||
isOpen && !disabled && "bg-muted"
|
||||
|
||||
Reference in New Issue
Block a user