fix: name the buttons of the reading pane for a selection in words

This commit is contained in:
Linus Rath
2026-11-04 11:33:25 +01:00
parent 9ae258cc74
commit f84cc909d5
3 changed files with 36 additions and 3 deletions
@@ -108,6 +108,21 @@ describe('selection bar: Move, read state and the reading pane', () => {
});
});
describe('the reading pane for a selection (mail-p2-15)', () => {
it('names its buttons in words, while the bar beside the list keeps icons', () => {
const host = document.createElement('div');
document.body.appendChild(host);
render(<EmailList emails={useEmailStore.getState().emails} selectionPaneHost={host} />);
const pane = host.querySelector('[data-testid="selection-pane"]') as HTMLElement;
for (const id of ['archive', 'delete', 'move', 'mark-read', 'tag']) {
const button = pane.querySelector(`[data-testid="pane-batch-${id}"]`) as HTMLElement;
expect(button.textContent?.trim()).toBe(button.getAttribute('aria-label'));
}
expect(screen.getByTestId('batch-archive').textContent).toBe('');
host.remove();
});
});
describe('selection bar and rows from the keyboard', () => {
it('keeps the collapsed bar out of the Tab order', () => {
useEmailStore.setState({ selectedEmailKeys: new Set() });
+5
View File
@@ -26,6 +26,7 @@ export function BatchTagButton({
disabled,
scope = null,
testId = "batch-tag",
showLabel = false,
}: {
title: string;
/** Tags every selected message carries. */
@@ -40,6 +41,8 @@ export function BatchTagButton({
scope?: TagScope | null;
/** The reading pane's copy has its own (mail-10). */
testId?: string;
/** Name the button in words beside the icon, as the reading pane does (mail-p2-15). */
showLabel?: boolean;
}) {
const [pos, setPos] = useState<{ top: number; left: number } | null>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null);
@@ -101,8 +104,10 @@ export function BatchTagButton({
aria-haspopup="menu"
aria-expanded={isOpen}
disabled={disabled}
{...(showLabel ? { size: "sm" as const, title: "", className: "gap-1.5" } : {})}
>
<Tag />
{showLabel && <span data-selection-label="">{title}</span>}
</IconButton>
{isOpen &&
createPortal(
+16 -3
View File
@@ -776,11 +776,17 @@ export function EmailList({
// reading pane. The pane's copies get their own test ids.
const selectionButtons = (where: 'bar' | 'pane') => {
const testId = (id: string) => (where === 'bar' ? id : `pane-${id}`);
// The pane has room: its buttons say what they do (mail-p2-15). The
// bar beside the list stays icons with tooltips.
const labelled = where === 'pane';
const shape = labelled ? { size: 'sm' as const, title: '', className: 'gap-1.5' } : {};
const name = (label: string) => (labelled ? <span data-selection-label="">{label}</span> : null);
return (
<>
{archiveActionEnabled && effectiveMailboxRole !== 'archive' && (
<IconButton label={tContextMenu('archive')} onClick={() => void handleBatchArchive()} disabled={isProcessing} data-testid={testId('batch-archive')}>
<IconButton label={tContextMenu('archive')} onClick={() => void handleBatchArchive()} disabled={isProcessing} data-testid={testId('batch-archive')} {...shape}>
<Archive />
{name(tContextMenu('archive'))}
</IconButton>
)}
<IconButton
@@ -788,9 +794,11 @@ export function EmailList({
onClick={handleBatchDelete}
disabled={isProcessing}
data-testid={testId('batch-delete')}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
{...shape}
className={cn("text-destructive hover:text-destructive hover:bg-destructive/10", labelled && "gap-1.5")}
>
<Trash2 />
{name(t('batch_actions.delete'))}
</IconButton>
{!moveMenuIsEmpty(selectionMoveModel) && (
<IconButton
@@ -800,8 +808,10 @@ export function EmailList({
onClick={(e) => moveMenu.openContextMenuFrom(e.currentTarget, null, 'end')}
disabled={isProcessing}
data-testid={testId('batch-move')}
{...shape}
>
<FolderInput />
{name(tContextMenu('move_to'))}
</IconButton>
)}
<IconButton
@@ -809,10 +819,13 @@ export function EmailList({
onClick={() => handleBatchMarkAsRead(selectionHasUnread)}
disabled={isProcessing}
data-testid={testId('batch-mark-read')}
{...shape}
>
{selectionHasUnread ? <MailOpen /> : <Mail />}
{name(selectionHasUnread ? t('batch_actions.mark_read') : t('batch_actions.mark_unread'))}
</IconButton>
<BatchTagButton
showLabel={labelled}
key={hasSelection ? 'selection' : 'none'}
title={tContextMenu('tag')}
selectedIds={selectionTags.all}
@@ -1433,7 +1446,7 @@ export function EmailList({
<CheckSquare className="w-10 h-10 text-muted-foreground" />
</div>
<h2 className="text-xl font-semibold text-foreground mb-4">{selectionTitle}</h2>
<div className="flex items-center justify-center gap-1">
<div className="flex flex-wrap items-center justify-center gap-1">
{selectionButtons('pane')}
<IconButton
label={t('batch_actions.more')}