mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 00:07:01 +00:00
fix: tell screen readers which undo-send delay, sort order and language is picked
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
// The undo-send delay chips (and the other Composer choices), the Files sort
|
||||
// sheet and the language list drew the picked option in another colour only:
|
||||
// a screen reader heard every option alike and couldn't tell which was set.
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { radioOptionA11y, RADIO_GROUP_A11Y } from '../radio-a11y';
|
||||
|
||||
const SRC = path.resolve(__dirname, '../..');
|
||||
const read = (rel: string) => fs.readFileSync(path.join(SRC, rel), 'utf8').split('\r\n').join('\n');
|
||||
|
||||
describe('radioOptionA11y', () => {
|
||||
it('gives the picked option the radio role and the selected and checked state', () => {
|
||||
expect(radioOptionA11y(true)).toEqual({ accessibilityRole: 'radio', accessibilityState: { selected: true, checked: true } });
|
||||
expect(radioOptionA11y(false).accessibilityState).toEqual({ selected: false, checked: false });
|
||||
expect(RADIO_GROUP_A11Y.accessibilityRole).toBe('radiogroup');
|
||||
});
|
||||
});
|
||||
|
||||
describe('option groups', () => {
|
||||
it('Composer chips (undo-send delay, autosave, signature) say which one is set', () => {
|
||||
const source = read('components/settings/ComposingSettings.tsx');
|
||||
const segmented = source.slice(source.indexOf('const segmented'), source.indexOf('return (\n <SettingsSection'));
|
||||
expect(segmented).toContain('{...RADIO_GROUP_A11Y}');
|
||||
expect(segmented).toContain('{...radioOptionA11y(active)}');
|
||||
});
|
||||
|
||||
it('the Files sort sheet says which order is set', () => {
|
||||
const source = read('screens/FilesScreen.tsx');
|
||||
expect(source).toContain('{...radioOptionA11y(sortKey === k)}');
|
||||
expect(source).toContain('{...radioOptionA11y(sortDir === d)}');
|
||||
});
|
||||
|
||||
it('the language list says which language is set', () => {
|
||||
expect(read('components/settings/LanguageSettings.tsx')).toContain('{...radioOptionA11y(active)}');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
// One choice of a set of options (the undo-send delay, the Files sort order,
|
||||
// the app language). Drawn as a styled Pressable, an option told a screen
|
||||
// reader nothing about which one was picked: TalkBack read "5 seconds" for
|
||||
// every chip alike. A radio role with the selected and checked state reads
|
||||
// "selected" (Android) or "checked" (iOS) on the current one.
|
||||
|
||||
export interface RadioOptionA11y {
|
||||
accessibilityRole: 'radio';
|
||||
accessibilityState: { selected: boolean; checked: boolean };
|
||||
}
|
||||
|
||||
export function radioOptionA11y(selected: boolean): RadioOptionA11y {
|
||||
return { accessibilityRole: 'radio', accessibilityState: { selected, checked: selected } };
|
||||
}
|
||||
|
||||
/** For the View around the options. */
|
||||
export const RADIO_GROUP_A11Y = { accessibilityRole: 'radiogroup' } as const;
|
||||
@@ -8,6 +8,7 @@ import { useColors } from '../../theme/colors';
|
||||
import { useSettingsStore, type ReplyIdentityMatch, type SignaturePosition } from '../../stores/settings-store';
|
||||
import { useLocaleStore } from '../../stores/locale-store';
|
||||
import { SUPPORTED_SUB_ADDRESS_DELIMITERS } from '../../lib/sub-addressing';
|
||||
import { RADIO_GROUP_A11Y, radioOptionA11y } from '../radio-a11y';
|
||||
|
||||
export function ComposingSettings() {
|
||||
const c = useColors();
|
||||
@@ -82,12 +83,13 @@ export function ComposingSettings() {
|
||||
current: T,
|
||||
onPick: (value: T) => void,
|
||||
) => (
|
||||
<View style={styles.segmentRow}>
|
||||
<View style={styles.segmentRow} {...RADIO_GROUP_A11Y}>
|
||||
{options.map((opt) => {
|
||||
const active = current === opt.value;
|
||||
return (
|
||||
<Pressable
|
||||
key={String(opt.value)}
|
||||
{...radioOptionA11y(active)}
|
||||
onPress={() => onPick(opt.value)}
|
||||
style={[styles.segment, active && styles.segmentActive]}
|
||||
>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useLocaleStore } from '../../stores/locale-store';
|
||||
import { useSettingsStore, type DateFormat, type TimeFormat } from '../../stores/settings-store';
|
||||
import { formatListDate } from '../../lib/date-format';
|
||||
import { SUPPORTED_LOCALES, detectDeviceLocale, type LocaleCode } from '../../i18n';
|
||||
import { radioOptionA11y } from '../radio-a11y';
|
||||
|
||||
export function LanguageSettings() {
|
||||
const c = useColors();
|
||||
@@ -43,6 +44,7 @@ export function LanguageSettings() {
|
||||
return (
|
||||
<Pressable
|
||||
key={code}
|
||||
{...radioOptionA11y(active)}
|
||||
onPress={() => setOverride(code === 'system' ? null : (code as LocaleCode))}
|
||||
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
||||
>
|
||||
|
||||
@@ -72,6 +72,7 @@ import { formatListDate } from '../lib/date-format';
|
||||
import { useLayout, useSheetInsets } from '../lib/use-layout';
|
||||
import { useSheetBottomInset } from '../lib/sheet-bottom-inset';
|
||||
import { filesListWidth } from '../lib/files-split';
|
||||
import { radioOptionA11y } from '../components/radio-a11y';
|
||||
import { useBackWhileFocused } from '../lib/use-back-while-focused';
|
||||
import { filesBackStep } from '../lib/files-back';
|
||||
import { driveChange, type DriveScope } from '../lib/files-drive';
|
||||
@@ -2148,7 +2149,7 @@ function SortSheet({ visible, sortKey, sortDir, t, onClose, onChangeKey, onChang
|
||||
<View style={[styles.sheet, { paddingBottom: spacing.xxl + bottomInset }]}>
|
||||
<Text style={styles.sheetTitle}>{t('files.settings_default_sort', 'Sort')}</Text>
|
||||
{keys.map((k) => (
|
||||
<Pressable key={k} style={styles.action} onPress={() => { onChangeKey(k); onClose(); }}>
|
||||
<Pressable key={k} style={styles.action} {...radioOptionA11y(sortKey === k)} onPress={() => { onChangeKey(k); onClose(); }}>
|
||||
<Text style={[styles.actionLabel, sortKey === k && { color: c.primary, fontWeight: '600' }]}>
|
||||
{t(`files.${k}`, k)}
|
||||
</Text>
|
||||
@@ -2157,7 +2158,7 @@ function SortSheet({ visible, sortKey, sortDir, t, onClose, onChangeKey, onChang
|
||||
))}
|
||||
<View style={styles.sheetDivider} />
|
||||
{(['asc', 'desc'] as FilesSortDir[]).map((d) => (
|
||||
<Pressable key={d} style={styles.action} onPress={() => { onChangeDir(d); onClose(); }}>
|
||||
<Pressable key={d} style={styles.action} {...radioOptionA11y(sortDir === d)} onPress={() => { onChangeDir(d); onClose(); }}>
|
||||
{d === 'asc' ? <ArrowUp size={18} color={c.text} /> : <ArrowDown size={18} color={c.text} />}
|
||||
<Text style={[styles.actionLabel, sortDir === d && { color: c.primary, fontWeight: '600' }]}>
|
||||
{d === 'asc'
|
||||
|
||||
Reference in New Issue
Block a user