fix: make small buttons easier to hit

This commit is contained in:
Linus Rath
2026-10-15 03:30:00 +02:00
parent e19229a5c6
commit c8434b0610
9 changed files with 28 additions and 14 deletions
+4 -2
View File
@@ -186,7 +186,9 @@ function SidebarRow({
{hasChildren ? ( {hasChildren ? (
<Pressable <Pressable
onPress={onToggleExpand} onPress={onToggleExpand}
hitSlop={6} // The 20 dp slot keeps the tree indent; the touch area reaches
// 44 dp so a near miss doesn't open the folder instead.
hitSlop={12}
style={styles.chevron} style={styles.chevron}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={isExpanded ? t('sidebar.collapse', 'Collapse') : t('sidebar.expand', 'Expand')} accessibilityLabel={isExpanded ? t('sidebar.collapse', 'Collapse') : t('sidebar.expand', 'Expand')}
@@ -1399,7 +1401,7 @@ function makeStyles(c: ThemePalette) {
...typography.bodySemibold, ...typography.bodySemibold,
color: c.text, color: c.text,
}, },
sectionSettings: { padding: 4 }, sectionSettings: { padding: 10 },
quickRow: { quickRow: {
flexDirection: 'row', flexDirection: 'row',
+2
View File
@@ -29,6 +29,8 @@ export default function ToggleSwitch({ value, onValueChange, disabled = false, a
disabled && styles.disabled, disabled && styles.disabled,
]} ]}
onPress={() => !disabled && onValueChange(!value)} onPress={() => !disabled && onValueChange(!value)}
// The track is 44 x 24; the touch area reaches 48 dp high.
hitSlop={{ top: 12, bottom: 12, left: 4, right: 4 }}
accessibilityRole="switch" accessibilityRole="switch"
accessibilityLabel={accessibilityLabel} accessibilityLabel={accessibilityLabel}
accessibilityState={{ checked: value, disabled }} accessibilityState={{ checked: value, disabled }}
+4 -1
View File
@@ -161,7 +161,7 @@ export function QuickReplyBox({ email, jmapAccountId, onMoreOptions, onSent, onF
editable={!sending} editable={!sending}
/> />
<View style={styles.actions}> <View style={styles.actions}>
<Pressable style={styles.more} onPress={() => onMoreOptions(text)} hitSlop={6}> <Pressable style={styles.more} onPress={() => onMoreOptions(text)} hitSlop={10} accessibilityRole="button">
<Maximize2 size={14} color={c.textSecondary} /> <Maximize2 size={14} color={c.textSecondary} />
<Text style={styles.moreText}>{t('email_viewer.more_options', 'More options')}</Text> <Text style={styles.moreText}>{t('email_viewer.more_options', 'More options')}</Text>
</Pressable> </Pressable>
@@ -169,6 +169,9 @@ export function QuickReplyBox({ email, jmapAccountId, onMoreOptions, onSent, onF
style={[styles.send, (!text.trim() || sending) && styles.sendDisabled]} style={[styles.send, (!text.trim() || sending) && styles.sendDisabled]}
onPress={send} onPress={send}
disabled={!text.trim() || sending} disabled={!text.trim() || sending}
hitSlop={10}
accessibilityRole="button"
accessibilityState={{ disabled: !text.trim() || sending, busy: sending }}
> >
{sending ? <ActivityIndicator size="small" color={c.primaryForeground} /> : <Send size={14} color={c.primaryForeground} />} {sending ? <ActivityIndicator size="small" color={c.primaryForeground} /> : <Send size={14} color={c.primaryForeground} />}
<Text style={styles.sendText}>{sending ? t('email_viewer.sending', 'Sending...') : t('email_viewer.send', 'Send')}</Text> <Text style={styles.sendText}>{sending ? t('email_viewer.sending', 'Sending...') : t('email_viewer.send', 'Send')}</Text>
+2 -2
View File
@@ -5,7 +5,7 @@ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { ArrowDown, ArrowUp, Check, ChevronRight, Plus, Star, Trash2, Users } from 'lucide-react-native'; import { ArrowDown, ArrowUp, Check, ChevronRight, Plus, Star, Trash2, Users } from 'lucide-react-native';
import { SettingsSection, SettingItem } from './settings-section'; import { SettingsSection, SettingItem } from './settings-section';
import Button from '../Button'; import Button from '../Button';
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens';
import { useColors } from '../../theme/colors'; import { useColors } from '../../theme/colors';
import { useAuthStore } from '../../stores/auth-store'; import { useAuthStore } from '../../stores/auth-store';
import { useAccountStore } from '../../stores/account-store'; import { useAccountStore } from '../../stores/account-store';
@@ -341,7 +341,7 @@ function makeStyles(c: ThemePalette) {
rowError: { ...typography.caption, color: c.error }, rowError: { ...typography.caption, color: c.error },
defaultBadge: { ...typography.caption, color: c.primary, marginTop: 2 }, defaultBadge: { ...typography.caption, color: c.primary, marginTop: 2 },
rowActions: { flexDirection: 'row', gap: 2 }, rowActions: { flexDirection: 'row', gap: 2 },
iconBtn: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center', borderRadius: radius.sm }, iconBtn: { width: componentSizes.buttonLg, height: componentSizes.buttonLg, alignItems: 'center', justifyContent: 'center', borderRadius: radius.sm },
addRow: { gap: spacing.sm, alignItems: 'flex-start' }, addRow: { gap: spacing.sm, alignItems: 'flex-start' },
hint: { ...typography.caption, color: c.mutedForeground }, hint: { ...typography.caption, color: c.mutedForeground },
}); });
+2 -1
View File
@@ -213,7 +213,8 @@ function RecipientChip({
</Text> </Text>
<Pressable <Pressable
onPress={onRemove} onPress={onRemove}
hitSlop={8} // A 12 px icon: the touch area reaches about 40 dp around it.
hitSlop={14}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t('email_composer.recipient_remove', 'Remove')} accessibilityLabel={t('email_composer.recipient_remove', 'Remove')}
> >
+2 -2
View File
@@ -2025,8 +2025,8 @@ function makeStyles(c: ThemePalette) {
lineHeight: 14, lineHeight: 14,
}, },
searchClearButton: { searchClearButton: {
width: 20, width: 32,
height: 20, height: 32,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderRadius: radius.full, borderRadius: radius.full,
+2 -2
View File
@@ -1627,8 +1627,8 @@ function makeStyles(c: ThemePalette) {
borderBottomColor: c.border, borderBottomColor: c.border,
}, },
toolbarBack: { toolbarBack: {
width: componentSizes.avatarSm, width: componentSizes.buttonLg,
height: componentSizes.avatarSm, height: componentSizes.buttonLg,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderRadius: radius.full, borderRadius: radius.full,
+9 -3
View File
@@ -60,7 +60,7 @@ import { useLayout, useSheetInsets } from '../lib/use-layout';
import { useBackWhileFocused } from '../lib/use-back-while-focused'; import { useBackWhileFocused } from '../lib/use-back-while-focused';
import { filesBackStep } from '../lib/files-back'; import { filesBackStep } from '../lib/files-back';
import type { FileNode } from '../api/types'; import type { FileNode } from '../api/types';
import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../theme/tokens';
import { useColors } from '../theme/colors'; import { useColors } from '../theme/colors';
import { import {
useSettingsStore, type FilesViewMode, type FilesSortKey, type FilesSortDir, useSettingsStore, type FilesViewMode, type FilesSortKey, type FilesSortDir,
@@ -1492,7 +1492,10 @@ function makeStyles(c: ThemePalette) {
gap: spacing.xs, gap: spacing.xs,
}, },
headerBtn: { headerBtn: {
padding: spacing.xs, minWidth: componentSizes.buttonLg,
minHeight: componentSizes.buttonLg,
alignItems: 'center',
justifyContent: 'center',
borderRadius: radius.sm, borderRadius: radius.sm,
}, },
headerBtnActive: { headerBtnActive: {
@@ -1627,7 +1630,10 @@ function makeStyles(c: ThemePalette) {
color: c.textMuted, color: c.textMuted,
}, },
rowMore: { rowMore: {
padding: spacing.xs, width: componentSizes.buttonLg,
height: componentSizes.buttonLg,
alignItems: 'center',
justifyContent: 'center',
}, },
gridContent: { gridContent: {
padding: spacing.sm, padding: spacing.sm,
+1 -1
View File
@@ -109,7 +109,7 @@ function makeStyles(c: ThemePalette) {
paddingHorizontal: spacing.lg, paddingHorizontal: spacing.lg,
height: componentSizes.headerHeight, height: componentSizes.headerHeight,
}, },
headerButton: { width: 32, height: 32, alignItems: 'center', justifyContent: 'center' }, headerButton: { width: componentSizes.buttonLg, height: componentSizes.buttonLg, alignItems: 'center', justifyContent: 'center' },
// One readable column, centred on a tablet or a phone in landscape. // One readable column, centred on a tablet or a phone in landscape.
content: { content: {
width: '100%', width: '100%',