Compare commits

...

1 Commits

Author SHA1 Message Date
Cyril 829ef7ed1b ️(a11y) improve settings dialog screen reader support
Add aria labels, initial focus and accessible heading to dialog
2026-02-24 15:45:32 +01:00
3 changed files with 44 additions and 24 deletions
@@ -21,7 +21,7 @@ import { AudioTab } from './tabs/AudioTab'
import { VideoTab } from './tabs/VideoTab' import { VideoTab } from './tabs/VideoTab'
import { TranscriptionTab } from './tabs/TranscriptionTab' import { TranscriptionTab } from './tabs/TranscriptionTab'
import { ShortcutTab } from './tabs/ShortcutTab' import { ShortcutTab } from './tabs/ShortcutTab'
import { useRef } from 'react' import { useEffect, useRef } from 'react'
import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery' import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery'
import { SettingsDialogExtendedKey } from '@/features/settings/type' import { SettingsDialogExtendedKey } from '@/features/settings/type'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner' import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
@@ -68,6 +68,17 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
const isAdminOrOwner = useIsAdminOrOwner() const isAdminOrOwner = useIsAdminOrOwner()
useEffect(() => {
if (!props.isOpen) return
const timer = setTimeout(() => {
const selectedTab = dialogEl.current?.querySelector(
'[role="tab"][data-selected]'
) as HTMLElement | null
selectedTab?.focus()
}, 200)
return () => clearTimeout(timer)
}, [props.isOpen])
return ( return (
<Dialog innerRef={dialogEl} {...props} role="dialog" type="flex"> <Dialog innerRef={dialogEl} {...props} role="dialog" type="flex">
<Tabs <Tabs
@@ -83,46 +94,53 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
paddingRight: !isWideScreen ? '1rem' : undefined, paddingRight: !isWideScreen ? '1rem' : undefined,
}} }}
> >
{isWideScreen && ( <Heading
<Heading slot="title" level={1} className={text({ variant: 'h1' })}> slot="title"
{t('dialog.heading')} level={1}
</Heading> className={
)} isWideScreen ? text({ variant: 'h1' }) : 'sr-only'
<TabList border={false}> }
<Tab icon highlight id={SettingsDialogExtendedKey.ACCOUNT}> >
<RiAccountCircleLine /> {t('dialog.heading')}
</Heading>
<TabList
border={false}
aria-label={t('dialog.tablistLabel')}
>
<Tab icon highlight id={SettingsDialogExtendedKey.ACCOUNT} aria-label={t(`tabs.${SettingsDialogExtendedKey.ACCOUNT}`)}>
<RiAccountCircleLine aria-hidden="true" />
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.ACCOUNT}`)} {isWideScreen && t(`tabs.${SettingsDialogExtendedKey.ACCOUNT}`)}
</Tab> </Tab>
<Tab icon highlight id={SettingsDialogExtendedKey.AUDIO}> <Tab icon highlight id={SettingsDialogExtendedKey.AUDIO} aria-label={t(`tabs.${SettingsDialogExtendedKey.AUDIO}`)}>
<RiSpeakerLine /> <RiSpeakerLine aria-hidden="true" />
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.AUDIO}`)} {isWideScreen && t(`tabs.${SettingsDialogExtendedKey.AUDIO}`)}
</Tab> </Tab>
<Tab icon highlight id={SettingsDialogExtendedKey.VIDEO}> <Tab icon highlight id={SettingsDialogExtendedKey.VIDEO} aria-label={t(`tabs.${SettingsDialogExtendedKey.VIDEO}`)}>
<RiVideoOnLine /> <RiVideoOnLine aria-hidden="true" />
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.VIDEO}`)} {isWideScreen && t(`tabs.${SettingsDialogExtendedKey.VIDEO}`)}
</Tab> </Tab>
<Tab icon highlight id={SettingsDialogExtendedKey.GENERAL}> <Tab icon highlight id={SettingsDialogExtendedKey.GENERAL} aria-label={t(`tabs.${SettingsDialogExtendedKey.GENERAL}`)}>
<RiSettings3Line /> <RiSettings3Line aria-hidden="true" />
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.GENERAL}`)} {isWideScreen && t(`tabs.${SettingsDialogExtendedKey.GENERAL}`)}
</Tab> </Tab>
<Tab icon highlight id={SettingsDialogExtendedKey.NOTIFICATIONS}> <Tab icon highlight id={SettingsDialogExtendedKey.NOTIFICATIONS} aria-label={t(`tabs.${SettingsDialogExtendedKey.NOTIFICATIONS}`)}>
<RiNotification3Line /> <RiNotification3Line aria-hidden="true" />
{isWideScreen && {isWideScreen &&
t(`tabs.${SettingsDialogExtendedKey.NOTIFICATIONS}`)} t(`tabs.${SettingsDialogExtendedKey.NOTIFICATIONS}`)}
</Tab> </Tab>
<Tab icon highlight id={SettingsDialogExtendedKey.SHORTCUTS}> <Tab icon highlight id={SettingsDialogExtendedKey.SHORTCUTS} aria-label={t(`tabs.${SettingsDialogExtendedKey.SHORTCUTS}`)}>
<RiKeyboardBoxLine /> <RiKeyboardBoxLine aria-hidden="true" />
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.SHORTCUTS}`)} {isWideScreen && t(`tabs.${SettingsDialogExtendedKey.SHORTCUTS}`)}
</Tab> </Tab>
{isAdminOrOwner && ( {isAdminOrOwner && (
<Tab icon highlight id={SettingsDialogExtendedKey.TRANSCRIPTION}> <Tab icon highlight id={SettingsDialogExtendedKey.TRANSCRIPTION} aria-label={t(`tabs.${SettingsDialogExtendedKey.TRANSCRIPTION}`)}>
<Icon type="symbols" name="speech_to_text" /> <Icon type="symbols" name="speech_to_text" />
{isWideScreen && {isWideScreen &&
t(`tabs.${SettingsDialogExtendedKey.TRANSCRIPTION}`)} t(`tabs.${SettingsDialogExtendedKey.TRANSCRIPTION}`)}
</Tab> </Tab>
)} )}
<Tab icon highlight id={SettingsDialogExtendedKey.ACCESSIBILITY}> <Tab icon highlight id={SettingsDialogExtendedKey.ACCESSIBILITY} aria-label={t(`tabs.${SettingsDialogExtendedKey.ACCESSIBILITY}`)}>
<RiEyeLine /> <RiEyeLine aria-hidden="true" />
{isWideScreen && {isWideScreen &&
t(`tabs.${SettingsDialogExtendedKey.ACCESSIBILITY}`)} t(`tabs.${SettingsDialogExtendedKey.ACCESSIBILITY}`)}
</Tab> </Tab>
+2 -1
View File
@@ -106,7 +106,8 @@
"columnShortcut": "Shortcut" "columnShortcut": "Shortcut"
}, },
"dialog": { "dialog": {
"heading": "Settings" "heading": "Settings",
"tablistLabel": "Sections"
}, },
"language": { "language": {
"heading": "Language", "heading": "Language",
+2 -1
View File
@@ -106,7 +106,8 @@
"columnShortcut": "Raccourci" "columnShortcut": "Raccourci"
}, },
"dialog": { "dialog": {
"heading": "Paramètres" "heading": "Paramètres",
"tablistLabel": "Sections"
}, },
"language": { "language": {
"heading": "Langue", "heading": "Langue",