diff --git a/components/settings/__tests__/accessibility-settings.test.tsx b/components/settings/__tests__/accessibility-settings.test.tsx index cc876edea..3afc266a0 100644 --- a/components/settings/__tests__/accessibility-settings.test.tsx +++ b/components/settings/__tests__/accessibility-settings.test.tsx @@ -154,3 +154,49 @@ describe('read aloud voice', () => { } }); }); + +// settings-10 (2026-11-04): Accessibility repeated about twenty settings of +// other pages, two rows of it called "Notification sound". Only access +// settings stay; a link row in each section leads to the page with the rest. +describe('one setting, one place', () => { + const borrowed = [ + 'settings.appearance.list_density.label', + 'settings.appearance.toolbar_labels.label', + 'settings.email_behavior.always_light_mode.label', + 'calendar.settings.hover_preview', + 'settings.email_behavior.group_conversations.label', + 'settings.email_behavior.show_preview.label', + 'settings.advanced.sender_favicons.label', + 'settings.email_behavior.mark_read.label', + 'settings.email_behavior.return_to_list_after_action.label', + 'settings.email_behavior.send_delay.label', + 'settings.email_behavior.confirm_bulk_delete.label', + 'settings.notifications.email.sound', + 'settings.notifications.calendar.sound', + 'settings.notifications.sound_selection.choose', + 'settings.appearance.favicon_unread_badge.label', + ]; + + it('draws no row that belongs to another page', () => { + const { container } = render(); + const rows = Array.from(container.querySelectorAll('[data-search-label]')).map((el) => el.getAttribute('data-search-label')); + expect(rows.filter((row) => borrowed.includes(row ?? ''))).toEqual([]); + }); + + it('links to the pages that have them', () => { + const goTo: unknown[] = []; + const listener = (e: Event) => goTo.push((e as CustomEvent).detail); + window.addEventListener('settings-tab-change', listener); + try { + render(); + for (const link of ['text', 'light_mode', 'list', 'reading', 'bulk_delete', 'sounds']) { + expect(screen.getByText(`uxs_structure.a11y_links.${link}.label`)).toBeTruthy(); + } + const soundsRow = screen.getByText('uxs_structure.a11y_links.sounds.label').closest('[data-search-label]') as HTMLElement; + fireEvent.click(within(soundsRow).getByRole('button')); + expect(goTo).toEqual([{ tab: 'notifications', section: 'sound' }]); + } finally { + window.removeEventListener('settings-tab-change', listener); + } + }); +}); diff --git a/components/settings/accessibility-settings.tsx b/components/settings/accessibility-settings.tsx index 3f6f606e2..f35a71d17 100644 --- a/components/settings/accessibility-settings.tsx +++ b/components/settings/accessibility-settings.tsx @@ -6,13 +6,11 @@ import { useSettingsStore } from '@/stores/settings-store'; import { usePolicyStore } from '@/stores/policy-store'; import { SettingsSection, SettingItem, RadioGroup, Select, ToggleSwitch } from './settings-section'; import { Button } from '@/components/ui/button'; -import { ChevronRight, Keyboard, Volume2 } from '@/components/icons'; -import { IS_LITE } from '@/lib/lite'; -import { useNoThirdPartyTraffic } from '@/hooks/use-third-party-mode'; +import { ChevronRight, Keyboard } from '@/components/icons'; +import { useAuthStore } from '@/stores/auth-store'; import { openKeyboardShortcuts } from '@/lib/app-commands'; import { voicesForLocale } from '@/lib/read-aloud'; import { useSpeechVoices } from '@/hooks/use-read-aloud'; -import { NOTIFICATION_SOUNDS, playNotificationSound, type NotificationSoundChoice } from '@/lib/notification-sound'; import { ACCESSIBILITY_KEYS, FONT_SIZES, @@ -32,7 +30,6 @@ import { } from '@/lib/accessibility-presets'; import { AccessibilityPresetPreview } from './accessibility-preset-preview'; import { FONT_SIZES as COMPOSE_FONT_SIZES } from '@/components/email/font-size'; -import type { Density, SendDelaySeconds } from '@/stores/settings-store'; type SettingKey = Parameters['updateSetting']>[0]; @@ -54,21 +51,19 @@ function LinkButton({ label, onClick }: { label: string; onClick: () => void }) } /** - * Settings > Accessibility: presets on top, then the options by topic. Rows - * that are also everyday preferences (conversation view, undo send, sounds…) - * are the same setting shown on their own page too, not a copy. + * Settings > Accessibility: presets on top, then the options by topic. Only + * settings about access live here; everyday preferences that help too + * (density, conversation view, undo send, sounds…) stay on their own page, + * and a link row in the matching section leads there. One setting, one place. */ export function AccessibilitySettings() { const t = useTranslations('settings.accessibility'); const tRoot = useTranslations(); - const tEmail = useTranslations('settings.email_behavior'); - const tAppearance = useTranslations('settings.appearance'); - const tNotifications = useTranslations('settings.notifications'); - const tCalendar = useTranslations('calendar.settings'); + const tLinks = useTranslations('uxs_structure.a11y_links'); const settings = useSettingsStore(); const { updateSetting, resetSettings } = settings; - const { isSettingLocked, isSettingHidden, getRestriction } = usePolicyStore(); - const faviconsAllowed = !useNoThirdPartyTraffic(); + const { isSettingLocked, isSettingHidden, getRestriction, isFeatureEnabled } = usePolicyStore(); + const calendarShown = (useAuthStore((s) => s.client)?.supportsCalendars() ?? false) && isFeatureEnabled('calendarEnabled'); const [applied, setApplied] = useState<{ id: AccessibilityPresetId; changes: PresetChange[] } | null>(null); const [resetDone, setResetDone] = useState(false); const locale = useLocale(); @@ -271,22 +266,8 @@ export function AccessibilitySettings() { set('plainLanguage', checked)} /> - {!isSettingHidden('density') && ( - - set('calendarHoverPreview', value)} - options={[ - { value: 'instant', label: tCalendar('hover_preview_instant') }, - { value: 'delay-500ms', label: tCalendar('hover_preview_delay_500ms') }, - { value: 'delay-1s', label: tCalendar('hover_preview_delay_1s') }, - { value: 'delay-2s', label: tCalendar('hover_preview_delay_2s') }, - { value: 'off', label: tCalendar('hover_preview_off') }, - ]} - /> - set('showRowActionButton', checked)} /> @@ -391,6 +359,11 @@ export function AccessibilitySettings() { goToSettings('message_list', 'actions')} /> + {calendarShown && ( + + goToSettings('calendar')} /> + + )} @@ -474,35 +447,11 @@ export function AccessibilitySettings() { ]} /> - - set('disableThreading', !checked)} /> + + goToSettings('message_list', 'display')} /> - {!isSettingHidden('showPreview') && ( - - set('showPreview', checked)} /> - - )} - {!IS_LITE && faviconsAllowed && ( - - set('senderFavicons', checked)} /> - - )} - {!isSettingHidden('markAsReadDelay') && ( - - set('sendDelaySeconds', Number(value) as SendDelaySeconds)} - options={[ - { value: '0', label: tEmail('send_delay.off') }, - { value: '10', label: tEmail('send_delay.seconds', { seconds: 10 }) }, - { value: '30', label: tEmail('send_delay.seconds', { seconds: 30 }) }, - { value: '60', label: tEmail('send_delay.seconds', { seconds: 60 }) }, - ]} - /> - {!isSettingHidden('sendConfirmation') && ( set('sendConfirmation', checked)} /> @@ -537,50 +474,18 @@ export function AccessibilitySettings() { set('confirmArchive', checked)} /> - - set('confirmBulkDelete', checked)} /> - - + goToSettings('composing', 'sending')} /> + + goToSettings('message_list', 'delete')} /> + set('visualAlerts', checked)} /> - - set('emailNotificationSound', checked)} /> - - - set('calendarNotificationSound', checked)} /> - - -
- -