diff --git a/src/components/ToggleSwitch.tsx b/src/components/ToggleSwitch.tsx index ce0944ee..81165b7e 100644 --- a/src/components/ToggleSwitch.tsx +++ b/src/components/ToggleSwitch.tsx @@ -7,6 +7,8 @@ interface ToggleSwitchProps { value: boolean; onValueChange: (val: boolean) => void; disabled?: boolean; + /** What the switch turns on or off, read out by screen readers. */ + accessibilityLabel?: string; } /** @@ -16,7 +18,7 @@ interface ToggleSwitchProps { * - thumb: h-4 w-4 rounded-full bg-background * - translate-x-6 (checked) / translate-x-1 (unchecked) */ -export default function ToggleSwitch({ value, onValueChange, disabled = false }: ToggleSwitchProps) { +export default function ToggleSwitch({ value, onValueChange, disabled = false, accessibilityLabel }: ToggleSwitchProps) { const c = useColors(); const styles = React.useMemo(() => makeStyles(c), [c]); return ( @@ -28,6 +30,7 @@ export default function ToggleSwitch({ value, onValueChange, disabled = false }: ]} onPress={() => !disabled && onValueChange(!value)} accessibilityRole="switch" + accessibilityLabel={accessibilityLabel} accessibilityState={{ checked: value, disabled }} > updateAction(index, { keepCopy: v || undefined })} + accessibilityLabel={t('settings.filters.forward_keep_copy', 'Keep a copy')} /> @@ -446,7 +447,11 @@ export function FilterRuleModal({ visible, rule, mailboxes, onSave, onClose }: F {t('settings.filters.stop_processing', 'Stop processing subsequent rules')} - + {/* Folder rules skip spam unless the rule opts in (webmail 6241ed61). */} @@ -455,7 +460,11 @@ export function FilterRuleModal({ visible, rule, mailboxes, onSave, onClose }: F {t('settings.filters.include_spam', 'Also move messages marked as spam')} - + )} diff --git a/src/components/settings/FilterSettings.tsx b/src/components/settings/FilterSettings.tsx index 73893a2e..9f972b8b 100644 --- a/src/components/settings/FilterSettings.tsx +++ b/src/components/settings/FilterSettings.tsx @@ -385,7 +385,7 @@ export function FilterSettings({ onOpenVacation }: FilterSettingsProps = {}) { return ( - handleToggle(rule.id)} /> + handleToggle(rule.id)} accessibilityLabel={rule.name} /> 0 && ( {t('settings.filters.expanded_view', 'Expanded view')} - updateSetting('filtersExpandedView', v)} /> + updateSetting('filtersExpandedView', v)} + accessibilityLabel={t('settings.filters.expanded_view', 'Expanded view')} + /> )} diff --git a/src/components/settings/SidebarAppsSettings.tsx b/src/components/settings/SidebarAppsSettings.tsx index 7a57eb18..438a5b9b 100644 --- a/src/components/settings/SidebarAppsSettings.tsx +++ b/src/components/settings/SidebarAppsSettings.tsx @@ -188,7 +188,11 @@ function AppForm({ initial, onSave, onCancel }: AppFormProps) { {t('settings.sidebar_apps.form.show_on_mobile', "Show on mobile")} - + diff --git a/src/components/settings/TemplateSettings.tsx b/src/components/settings/TemplateSettings.tsx index b6628d21..b8ca34fc 100644 --- a/src/components/settings/TemplateSettings.tsx +++ b/src/components/settings/TemplateSettings.tsx @@ -246,11 +246,19 @@ export function TemplateSettings() { {tr('settings.templates.favorite', 'Favorite')} - + {tr('settings.templates.is_html', 'Body is HTML')} - + diff --git a/src/components/settings/settings-section.tsx b/src/components/settings/settings-section.tsx index c998cd79..22eb3140 100644 --- a/src/components/settings/settings-section.tsx +++ b/src/components/settings/settings-section.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { View, Text, @@ -65,6 +65,10 @@ export function SettingsSection({ ); } +// The label of the SettingItem a control sits in. A switch has no text of +// its own, so without it a screen reader only announces "switch, off". +const SettingLabelContext = React.createContext(undefined); + interface SettingItemProps { label: string; description?: string; @@ -96,7 +100,11 @@ export function SettingItem({ {description && {description}} - {children && {children}} + {children && ( + + {children} + + )} ); } @@ -105,14 +113,18 @@ interface ToggleSwitchProps { checked: boolean; onChange: (v: boolean) => void; disabled?: boolean; + /** Defaults to the label of the enclosing SettingItem. */ + accessibilityLabel?: string; } -export function ToggleSwitch({ checked, onChange, disabled }: ToggleSwitchProps) { +export function ToggleSwitch({ checked, onChange, disabled, accessibilityLabel }: ToggleSwitchProps) { + const settingLabel = useContext(SettingLabelContext); return ( ); }