From ed39758ab49b6afa9c8b06609fbfe712b6258c4c Mon Sep 17 00:00:00 2001 From: Cyril Date: Thu, 19 Feb 2026 09:39:08 +0100 Subject: [PATCH] wip --- .../controls/Options/SettingsMenuItem.tsx | 5 ++++- .../components/SettingsDialogExtended.tsx | 16 ++++++++++++++-- src/frontend/src/primitives/Dialog.tsx | 1 + src/frontend/src/styles/index.css | 12 ++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/features/rooms/livekit/components/controls/Options/SettingsMenuItem.tsx b/src/frontend/src/features/rooms/livekit/components/controls/Options/SettingsMenuItem.tsx index 41e44d64..152b9830 100644 --- a/src/frontend/src/features/rooms/livekit/components/controls/Options/SettingsMenuItem.tsx +++ b/src/frontend/src/features/rooms/livekit/components/controls/Options/SettingsMenuItem.tsx @@ -11,7 +11,10 @@ export const SettingsMenuItem = () => { return ( openSettingsDialog()} + onAction={() => { + // Let MenuTrigger close first to avoid stacked overlays (menu + dialog). + window.setTimeout(() => openSettingsDialog(), 0) + }} > {t('settings')} diff --git a/src/frontend/src/features/settings/components/SettingsDialogExtended.tsx b/src/frontend/src/features/settings/components/SettingsDialogExtended.tsx index 7018f90d..c6e4d1d7 100644 --- a/src/frontend/src/features/settings/components/SettingsDialogExtended.tsx +++ b/src/frontend/src/features/settings/components/SettingsDialogExtended.tsx @@ -19,7 +19,7 @@ import { GeneralTab } from './tabs/GeneralTab' import { AudioTab } from './tabs/AudioTab' import { VideoTab } from './tabs/VideoTab' import { TranscriptionTab } from './tabs/TranscriptionTab' -import { useRef } from 'react' +import { useLayoutEffect, useRef } from 'react' import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery' import { SettingsDialogExtendedKey } from '@/features/settings/type' import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner' @@ -61,8 +61,20 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => { const { t } = useTranslation('settings') const dialogEl = useRef(null) - const isWideScreen = useMediaQuery('(min-width: 800px)') // fixme - hardcoded 50rem in pixel + useLayoutEffect(() => { + if (!props.isOpen) return + console.warn('[a11y] useLayoutEffect fired, dialogEl:', dialogEl.current) + console.warn('[a11y] tabs found:', dialogEl.current?.querySelectorAll('[role="tab"]').length) + const selected = dialogEl.current?.querySelector( + '[role="tab"][aria-selected="true"]', + ) + console.warn('[a11y] selected tab:', selected?.textContent) + selected?.focus({ preventScroll: true }) + console.warn('[a11y] activeElement after focus:', document.activeElement?.tagName, document.activeElement?.getAttribute('role')) + }, [props.isOpen]) + + const isWideScreen = useMediaQuery('(min-width: 800px)') // fixme - hardcoded 50rem in pixel const isAdminOrOwner = useIsAdminOrOwner() return ( diff --git a/src/frontend/src/primitives/Dialog.tsx b/src/frontend/src/primitives/Dialog.tsx index 4b0cc523..18cd63d9 100644 --- a/src/frontend/src/primitives/Dialog.tsx +++ b/src/frontend/src/primitives/Dialog.tsx @@ -46,6 +46,7 @@ const StyledRACDialog = styled(RACDialog, { width: 'full', height: 'full', pointerEvents: 'none', + outline: 'none', // focus moves to tab, avoid outline on container }, }) diff --git a/src/frontend/src/styles/index.css b/src/frontend/src/styles/index.css index 2e2835d9..4c5f52fc 100644 --- a/src/frontend/src/styles/index.css +++ b/src/frontend/src/styles/index.css @@ -22,6 +22,18 @@ body, outline: 2px solid transparent; } +/* Suppress focus ring on dialog/tabpanel containers - focus moves to tab */ +[role='dialog']:focus, +[role='dialog'][data-focus-visible], +[role='tabpanel'][data-focus-visible], +[role='tablist'][data-focus-visible], +[role='dialog'] + [data-rac][data-focus-visible]:not([role='tab']):not(button):not(a):not( + input + ):not(select):not(textarea) { + outline: none !important; +} + [data-rac][data-focus-visible]:not(label, .react-aria-Select), [data-rac][data-restore-focus-visible]:not(label, .react-aria-Select), :is(a, button, input[type='text'], select, textarea):not(