fix: give sheet close buttons, chips and small text buttons a full-size touch target

This commit is contained in:
Linus Rath
2026-10-15 04:05:40 +02:00
parent 52eb18dc33
commit 776447dd41
17 changed files with 42 additions and 31 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ export function IdentitySheet({
</View> </View>
<View style={styles.sheetHeader}> <View style={styles.sheetHeader}>
<Text style={styles.sheetTitle}>{t('email_composer.from', 'From')}</Text> <Text style={styles.sheetTitle}>{t('email_composer.from', 'From')}</Text>
<Pressable onPress={onClose} hitSlop={8} style={styles.sheetClose} <Pressable onPress={onClose} hitSlop={10} style={styles.sheetClose}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
> >
+1 -1
View File
@@ -96,7 +96,7 @@ export function MoveSheet({
</View> </View>
<View style={styles.sheetHeader}> <View style={styles.sheetHeader}>
<Text style={styles.sheetTitle}>{t('context_menu.move_to', 'Move to folder')}</Text> <Text style={styles.sheetTitle}>{t('context_menu.move_to', 'Move to folder')}</Text>
<Pressable onPress={onClose} hitSlop={8} style={styles.sheetClose} <Pressable onPress={onClose} hitSlop={10} style={styles.sheetClose}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
> >
+1 -1
View File
@@ -150,7 +150,7 @@ export function PushOnboardingPrompt(): React.ReactElement | null {
</View> </View>
<Pressable <Pressable
onPress={handleDismiss} onPress={handleDismiss}
hitSlop={8} hitSlop={14}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('settings.notifications.push.dismiss_aria', 'Dismiss notification prompt')} accessibilityLabel={t('settings.notifications.push.dismiss_aria', 'Dismiss notification prompt')}
style={styles.close} style={styles.close}
+1 -1
View File
@@ -87,7 +87,7 @@ export function TagSheet({ visible, onClose, keywords, selectedEmails, onToggle
<Text style={styles.title}> <Text style={styles.title}>
{t('context_menu.items_selected', `${selectedEmails.length} emails selected`, { count: selectedEmails.length })} {t('context_menu.items_selected', `${selectedEmails.length} emails selected`, { count: selectedEmails.length })}
</Text> </Text>
<Pressable onPress={onClose} hitSlop={8} style={styles.close} <Pressable onPress={onClose} hitSlop={10} style={styles.close}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
> >
+1 -1
View File
@@ -83,7 +83,7 @@ export function TemplateSheet({ visible, onClose, onPick }: TemplateSheetProps)
</View> </View>
<View style={styles.sheetHeader}> <View style={styles.sheetHeader}>
<Text style={styles.sheetTitle}>{t('templates.picker_title', 'Choose a Template')}</Text> <Text style={styles.sheetTitle}>{t('templates.picker_title', 'Choose a Template')}</Text>
<Pressable onPress={onClose} hitSlop={8} style={styles.sheetClose} <Pressable onPress={onClose} hitSlop={10} style={styles.sheetClose}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
> >
+6 -5
View File
@@ -57,6 +57,7 @@ import {
import { getEventEditability } from '../../lib/calendar-editability'; import { getEventEditability } from '../../lib/calendar-editability';
import { useContactNameResolver } from '../../lib/contact-name-resolver'; import { useContactNameResolver } from '../../lib/contact-name-resolver';
import { useCalendarLocale } from '../../lib/calendar-locale'; import { useCalendarLocale } from '../../lib/calendar-locale';
import { formatCalendarDate } from '../../lib/date-format';
import { useSheetDrag } from '../../lib/use-sheet-drag'; import { useSheetDrag } from '../../lib/use-sheet-drag';
import { recurrenceEndLabel, recurrenceIntervalLabel } from '../../lib/recurrence'; import { recurrenceEndLabel, recurrenceIntervalLabel } from '../../lib/recurrence';
import type { TranslateFn } from '../../stores/locale-store'; import type { TranslateFn } from '../../stores/locale-store';
@@ -95,9 +96,9 @@ function formatRange(event: CalendarEvent, timeFormat: TimeFormat | undefined, l
start.getMonth() === end.getMonth() && start.getMonth() === end.getMonth() &&
start.getDate() === end.getDate() start.getDate() === end.getDate()
) { ) {
return format(start, 'EEEE, MMM d, yyyy', opts); return formatCalendarDate(start, locale.code, 'longWeekdayMonthDayYear');
} }
return `${format(start, 'MMM d', opts)} – ${format(end, 'MMM d, yyyy', opts)}`; return `${formatCalendarDate(start, locale.code, 'monthDay')} – ${formatCalendarDate(end, locale.code, 'monthDayYear')}`;
} }
const tp = timePattern(timeFormat); const tp = timePattern(timeFormat);
const sameDay = const sameDay =
@@ -105,9 +106,9 @@ function formatRange(event: CalendarEvent, timeFormat: TimeFormat | undefined, l
start.getMonth() === end.getMonth() && start.getMonth() === end.getMonth() &&
start.getDate() === end.getDate(); start.getDate() === end.getDate();
if (sameDay) { if (sameDay) {
return `${format(start, 'EEE, MMM d', opts)} · ${format(start, tp, opts)} – ${format(end, tp, opts)}`; return `${formatCalendarDate(start, locale.code, 'weekdayMonthDay')} · ${format(start, tp, opts)} – ${format(end, tp, opts)}`;
} }
return `${format(start, `MMM d, ${tp}`, opts)} – ${format(end, `MMM d, ${tp}`, opts)}`; return `${formatCalendarDate(start, locale.code, 'monthDay')}, ${format(start, tp, opts)} – ${formatCalendarDate(end, locale.code, 'monthDay')}, ${format(end, tp, opts)}`;
} }
function recurrenceLabel(event: CalendarEvent, t: TranslateFn, locale: Locale): string | null { function recurrenceLabel(event: CalendarEvent, t: TranslateFn, locale: Locale): string | null {
@@ -276,7 +277,7 @@ export function EventDetailSheet({
<Pressable <Pressable
onPress={onClose} onPress={onClose}
style={styles.closeBtn} style={styles.closeBtn}
hitSlop={8} hitSlop={10}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
> >
+5 -4
View File
@@ -41,6 +41,7 @@ import {
} from '../../lib/task-editor'; } from '../../lib/task-editor';
import { isWritableCalendar } from '../../lib/calendar-editability'; import { isWritableCalendar } from '../../lib/calendar-editability';
import { useCalendarLocale } from '../../lib/calendar-locale'; import { useCalendarLocale } from '../../lib/calendar-locale';
import { formatCalendarDate } from '../../lib/date-format';
import { useSheetDrag } from '../../lib/use-sheet-drag'; import { useSheetDrag } from '../../lib/use-sheet-drag';
import { MODAL_ORIENTATIONS } from '../../lib/modal-orientations'; import { MODAL_ORIENTATIONS } from '../../lib/modal-orientations';
import { useSheetInsets } from '../../lib/use-layout'; import { useSheetInsets } from '../../lib/use-layout';
@@ -213,8 +214,8 @@ export function TasksSheet({
if (!d || !task.due) return null; if (!d || !task.due) return null;
const hasTime = !task.showWithoutTime && !/^\d{4}-\d{2}-\d{2}$/.test(task.due); const hasTime = !task.showWithoutTime && !/^\d{4}-\d{2}-\d{2}$/.test(task.due);
return hasTime return hasTime
? format(d, `EEE, MMM d · ${timePattern(timeFormat)}`, { locale }) ? `${formatCalendarDate(d, locale.code, 'weekdayMonthDay')} · ${format(d, timePattern(timeFormat), { locale })}`
: format(d, 'EEE, MMM d', { locale }); : formatCalendarDate(d, locale.code, 'weekdayMonthDay');
}; };
const priorityColor = (level: PriorityLevel): string => const priorityColor = (level: PriorityLevel): string =>
@@ -251,7 +252,7 @@ export function TasksSheet({
<Pressable <Pressable
onPress={onClose} onPress={onClose}
style={styles.closeBtn} style={styles.closeBtn}
hitSlop={8} hitSlop={10}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
> >
@@ -299,7 +300,7 @@ export function TasksSheet({
> >
<CalendarIcon size={14} color={editor.due ? c.primary : c.textMuted} /> <CalendarIcon size={14} color={editor.due ? c.primary : c.textMuted} />
<Text style={[styles.chipText, editor.due && styles.chipTextActive]}> <Text style={[styles.chipText, editor.due && styles.chipTextActive]}>
{editor.due ? format(editor.due, 'MMM d', { locale }) : t('calendar.tasks.due_date', 'Due date')} {editor.due ? formatCalendarDate(editor.due, locale.code, 'monthDay') : t('calendar.tasks.due_date', 'Due date')}
</Text> </Text>
{editor.due && ( {editor.due && (
<Pressable <Pressable
@@ -92,7 +92,7 @@ export default function AddressBookPickerSheet({
<Text style={styles.title}>{title ?? t('contacts.bulk.move_to_address_book', 'Move to address book')}</Text> <Text style={styles.title}>{title ?? t('contacts.bulk.move_to_address_book', 'Move to address book')}</Text>
<Pressable <Pressable
onPress={onClose} onPress={onClose}
hitSlop={8} hitSlop={10}
style={styles.close} style={styles.close}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
+1 -1
View File
@@ -82,7 +82,7 @@ export default function TagAssignSheet({
<Text style={styles.title}>{title ?? t('contacts.bulk.add_tag', 'Add tag')}</Text> <Text style={styles.title}>{title ?? t('contacts.bulk.add_tag', 'Add tag')}</Text>
<Pressable <Pressable
onPress={onClose} onPress={onClose}
hitSlop={8} hitSlop={10}
style={styles.close} style={styles.close}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
+1 -1
View File
@@ -78,7 +78,7 @@ export function ActionSheet({ visible, title, subtitle, items, onClose, children
<Text style={styles.title} numberOfLines={1}>{title}</Text> <Text style={styles.title} numberOfLines={1}>{title}</Text>
{subtitle ? <Text style={styles.subtitle} numberOfLines={1}>{subtitle}</Text> : null} {subtitle ? <Text style={styles.subtitle} numberOfLines={1}>{subtitle}</Text> : null}
</View> </View>
<Pressable onPress={onClose} hitSlop={8} style={styles.close} <Pressable onPress={onClose} hitSlop={10} style={styles.close}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
> >
+3 -3
View File
@@ -74,15 +74,15 @@ export function ThreadMessageCard({
<MessageContent {...content} compact={false} onToggleStar={bare ? undefined : onToggleStar} /> <MessageContent {...content} compact={false} onToggleStar={bare ? undefined : onToggleStar} />
{!bare && ( {!bare && (
<View style={[styles.actions, !replyable && styles.actionsDisabled]}> <View style={[styles.actions, !replyable && styles.actionsDisabled]}>
<Pressable style={styles.actionBtn} onPress={reply('reply')} hitSlop={4}> <Pressable style={styles.actionBtn} onPress={reply('reply')} hitSlop={9}>
<Reply size={16} color={c.textSecondary} /> <Reply size={16} color={c.textSecondary} />
<Text style={styles.actionLabel}>{t('email_viewer.reply', 'Reply')}</Text> <Text style={styles.actionLabel}>{t('email_viewer.reply', 'Reply')}</Text>
</Pressable> </Pressable>
<Pressable style={styles.actionBtn} onPress={reply('replyAll')} hitSlop={4}> <Pressable style={styles.actionBtn} onPress={reply('replyAll')} hitSlop={9}>
<ReplyAll size={16} color={c.textSecondary} /> <ReplyAll size={16} color={c.textSecondary} />
<Text style={styles.actionLabel}>{t('email_viewer.reply_all', 'Reply All')}</Text> <Text style={styles.actionLabel}>{t('email_viewer.reply_all', 'Reply All')}</Text>
</Pressable> </Pressable>
<Pressable style={styles.actionBtn} onPress={reply('forward')} hitSlop={4}> <Pressable style={styles.actionBtn} onPress={reply('forward')} hitSlop={9}>
<Forward size={16} color={c.textSecondary} /> <Forward size={16} color={c.textSecondary} />
<Text style={styles.actionLabel}>{t('email_viewer.forward', 'Forward')}</Text> <Text style={styles.actionLabel}>{t('email_viewer.forward', 'Forward')}</Text>
</Pressable> </Pressable>
@@ -137,7 +137,7 @@ export function DeviceSyncCollectionsSheet({ visible, onClose, registryId, autho
</View> </View>
<Pressable <Pressable
onPress={close} onPress={close}
hitSlop={8} hitSlop={10}
style={styles.close} style={styles.close}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
+2 -2
View File
@@ -2282,12 +2282,12 @@ export default function ComposeScreen({ route, navigation }: Props) {
/> />
)} )}
{field === 'to' && !ccVisible && ( {field === 'to' && !ccVisible && (
<Pressable onPress={() => setCcVisible(true)} style={styles.ccToggle}> <Pressable onPress={() => setCcVisible(true)} style={styles.ccToggle} hitSlop={10} accessibilityRole="button">
<Text style={styles.ccToggleText}>{t('email_composer.cc', 'Cc')}</Text> <Text style={styles.ccToggleText}>{t('email_composer.cc', 'Cc')}</Text>
</Pressable> </Pressable>
)} )}
{field === 'cc' && !bccVisible && ( {field === 'cc' && !bccVisible && (
<Pressable onPress={() => setBccVisible(true)} style={styles.ccToggle}> <Pressable onPress={() => setBccVisible(true)} style={styles.ccToggle} hitSlop={10} accessibilityRole="button">
<Text style={styles.ccToggleText}>{t('email_composer.bcc', 'Bcc')}</Text> <Text style={styles.ccToggleText}>{t('email_composer.bcc', 'Bcc')}</Text>
</Pressable> </Pressable>
)} )}
+7 -1
View File
@@ -536,6 +536,7 @@ export default function ContactsScreen({ onOpenContact, activeContactId = null }
key={chip.key} key={chip.key}
onPress={() => setSelectedCategory(chip.category)} onPress={() => setSelectedCategory(chip.category)}
style={[styles.chip, chip.active && styles.chipActive]} style={[styles.chip, chip.active && styles.chipActive]}
hitSlop={{ top: 6, bottom: 6 }}
accessibilityRole="button" accessibilityRole="button"
accessibilityState={{ selected: chip.active }} accessibilityState={{ selected: chip.active }}
> >
@@ -718,15 +719,20 @@ function makeStyles(c: ThemePalette) {
// flexShrink 0: in a short (landscape) window the column squeezed the // flexShrink 0: in a short (landscape) window the column squeezed the
// horizontal scroller and cut the chips off at the bottom. // horizontal scroller and cut the chips off at the bottom.
chipBar: { flexGrow: 0, flexShrink: 0, marginBottom: spacing.sm }, chipBar: { flexGrow: 0, flexShrink: 0, marginBottom: 2 },
// The vertical padding leaves room for the chips' slop, which makes them
// 48 dp targets (they were about 28 dp).
chipBarContent: { chipBarContent: {
paddingHorizontal: spacing.lg, paddingHorizontal: spacing.lg,
paddingVertical: 6,
gap: spacing.sm, gap: spacing.sm,
alignItems: 'center', alignItems: 'center',
}, },
chip: { chip: {
paddingHorizontal: spacing.md, paddingHorizontal: spacing.md,
paddingVertical: spacing.xs, paddingVertical: spacing.xs,
minHeight: 36,
justifyContent: 'center',
borderRadius: radius.full, borderRadius: radius.full,
backgroundColor: c.surface, backgroundColor: c.surface,
borderWidth: 1, borderWidth: 1,
+4 -4
View File
@@ -1305,7 +1305,7 @@ export default function EmailListScreen({
</Pressable> </Pressable>
<Pressable <Pressable
onPress={() => removeRecentSearch(q)} onPress={() => removeRecentSearch(q)}
hitSlop={8} hitSlop={18}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('advanced_search.suggestions_remove_recent', 'Remove from recent searches')} accessibilityLabel={t('advanced_search.suggestions_remove_recent', 'Remove from recent searches')}
> >
@@ -1600,7 +1600,7 @@ export default function EmailListScreen({
<Pressable <Pressable
onPress={() => setFilterMenuOpen(false)} onPress={() => setFilterMenuOpen(false)}
style={styles.filterMenuClose} style={styles.filterMenuClose}
hitSlop={6} hitSlop={10}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
> >
@@ -1702,7 +1702,7 @@ export default function EmailListScreen({
{filters.dateAfter ? ( {filters.dateAfter ? (
<Pressable <Pressable
onPress={() => setFilterField('dateAfter', undefined)} onPress={() => setFilterField('dateAfter', undefined)}
hitSlop={6} hitSlop={12}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('advanced_search.clear', 'Clear')} accessibilityLabel={t('advanced_search.clear', 'Clear')}
> >
@@ -1724,7 +1724,7 @@ export default function EmailListScreen({
{filters.dateBefore ? ( {filters.dateBefore ? (
<Pressable <Pressable
onPress={() => setFilterField('dateBefore', undefined)} onPress={() => setFilterField('dateBefore', undefined)}
hitSlop={6} hitSlop={12}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('advanced_search.clear', 'Clear')} accessibilityLabel={t('advanced_search.clear', 'Clear')}
> >
+2 -2
View File
@@ -1356,7 +1356,7 @@ function MoreMenuSheet({
<Text style={styles.sheetTitle}>{t('email_viewer.more_actions', 'More actions')}</Text> <Text style={styles.sheetTitle}>{t('email_viewer.more_actions', 'More actions')}</Text>
<Pressable <Pressable
onPress={onClose} onPress={onClose}
hitSlop={8} hitSlop={10}
style={styles.sheetClose} style={styles.sheetClose}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
@@ -1507,7 +1507,7 @@ function TagMenuSheet({ visible, onClose, keywords, activeKeywords, onToggle }:
<Text style={styles.sheetTitle}>{t('email_viewer.tag', 'Tag')}</Text> <Text style={styles.sheetTitle}>{t('email_viewer.tag', 'Tag')}</Text>
<Pressable <Pressable
onPress={onClose} onPress={onClose}
hitSlop={8} hitSlop={10}
style={styles.sheetClose} style={styles.sheetClose}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('common.close', 'Close')} accessibilityLabel={t('common.close', 'Close')}
+4 -1
View File
@@ -73,6 +73,7 @@ export default function EmailStep({
key={address} key={address}
onPress={() => onChange(address)} onPress={() => onChange(address)}
style={styles.suggestion} style={styles.suggestion}
hitSlop={4}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={address} accessibilityLabel={address}
> >
@@ -108,7 +109,9 @@ function makeStyles(c: ThemePalette) {
borderColor: c.border, borderColor: c.border,
borderRadius: 999, borderRadius: 999,
paddingHorizontal: spacing.md, paddingHorizontal: spacing.md,
paddingVertical: spacing.xs, // 40 dp plus the slop: the chips were about 26 dp tall.
minHeight: 40,
justifyContent: 'center',
maxWidth: '100%', maxWidth: '100%',
}, },
suggestionText: { ...typography.caption, color: c.textSecondary }, suggestionText: { ...typography.caption, color: c.textSecondary },