mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 00:07:01 +00:00
fix: tell screen readers which tags the picker has checked
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
View, Text, StyleSheet, ScrollView, Pressable, Modal, Animated, Easing,
|
||||
} from 'react-native';
|
||||
import { X, Check, Tag } from './icons';
|
||||
import { tagCheckedState, tagRowAccessibility } from '../lib/tag-row-a11y';
|
||||
import { spacing, radius, typography, type ThemePalette } from '../theme/tokens';
|
||||
import { useColors } from '../theme/colors';
|
||||
import { useSheetDrag } from '../lib/use-sheet-drag';
|
||||
@@ -120,6 +121,7 @@ export function TagSheet({ visible, onClose, keywords, selectedEmails, onToggle
|
||||
<Pressable
|
||||
key={kw.id}
|
||||
onPress={() => onToggle(token, !applied)}
|
||||
{...tagRowAccessibility(tagCheckedState(selectedEmails, token))}
|
||||
style={({ pressed }) => [styles.row, tagRowIndent(kw.depth), pressed && styles.rowPressed]}
|
||||
>
|
||||
<Tag size={16} color={dot} fill={dot} />
|
||||
@@ -136,6 +138,7 @@ export function TagSheet({ visible, onClose, keywords, selectedEmails, onToggle
|
||||
<Pressable
|
||||
key={`unknown:${id}`}
|
||||
onPress={() => onToggle(token, !applied)}
|
||||
{...tagRowAccessibility(tagCheckedState(selectedEmails, token))}
|
||||
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
||||
>
|
||||
<Tag size={16} color={c.tags.gray.dot} fill={c.tags.gray.dot} />
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Tag picker rows showed a check mark but exposed no checked state.
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { tagCheckedState, tagRowAccessibility } from '../tag-row-a11y';
|
||||
|
||||
describe('tag picker row state', () => {
|
||||
const a = { keywords: { $label1: true } };
|
||||
const b = { keywords: {} };
|
||||
|
||||
it('is checked when every selected email has the tag', () => {
|
||||
expect(tagCheckedState([a, a], '$label1')).toBe(true);
|
||||
expect(tagRowAccessibility(true)).toEqual({ accessibilityRole: 'checkbox', accessibilityState: { checked: true } });
|
||||
});
|
||||
|
||||
it('is mixed when only some have it, unchecked when none do', () => {
|
||||
expect(tagCheckedState([a, b], '$label1')).toBe('mixed');
|
||||
expect(tagCheckedState([b], '$label1')).toBe(false);
|
||||
expect(tagCheckedState([], '$label1')).toBe(false);
|
||||
});
|
||||
|
||||
it('is spread onto both kinds of tag row', () => {
|
||||
const source = fs.readFileSync(path.resolve(__dirname, '../../components/TagSheet.tsx'), 'utf8');
|
||||
const spreads = source.match(/\{\.\.\.tagRowAccessibility\(/g) ?? [];
|
||||
expect(spreads.length).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
// What a tag picker row tells a screen reader. The rows drew a check mark
|
||||
// but said nothing about it, so TalkBack read a tag as a plain button. As in
|
||||
// the webmail tag picker (components/email/tag-picker.tsx, role
|
||||
// menuitemcheckbox with aria-checked true / "mixed" / false): checked when
|
||||
// every selected email has the tag, mixed when only some do.
|
||||
|
||||
export type TagCheckedState = boolean | 'mixed';
|
||||
|
||||
export function tagCheckedState(
|
||||
selectedEmails: ReadonlyArray<{ keywords?: Record<string, boolean> | null }>,
|
||||
token: string,
|
||||
): TagCheckedState {
|
||||
if (selectedEmails.length === 0) return false;
|
||||
const withTag = selectedEmails.filter((e) => !!e.keywords?.[token]).length;
|
||||
if (withTag === 0) return false;
|
||||
return withTag === selectedEmails.length ? true : 'mixed';
|
||||
}
|
||||
|
||||
/** The row's role and state props. */
|
||||
export function tagRowAccessibility(checked: TagCheckedState): {
|
||||
accessibilityRole: 'checkbox';
|
||||
accessibilityState: { checked: TagCheckedState };
|
||||
} {
|
||||
return { accessibilityRole: 'checkbox', accessibilityState: { checked } };
|
||||
}
|
||||
Reference in New Issue
Block a user