♿️(frontend) restore focus to trigger button when panel closes
improves keyboard navigation and accessibility consistency Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
@@ -164,7 +164,7 @@ export const SidePanel = () => {
|
|||||||
<Panel isOpen={isChatOpen} keepAlive={true}>
|
<Panel isOpen={isChatOpen} keepAlive={true}>
|
||||||
<Chat />
|
<Chat />
|
||||||
</Panel>
|
</Panel>
|
||||||
<Panel isOpen={isToolsOpen}>
|
<Panel isOpen={isToolsOpen} keepAlive={true}>
|
||||||
<Tools />
|
<Tools />
|
||||||
</Panel>
|
</Panel>
|
||||||
<Panel isOpen={isAdminOpen}>
|
<Panel isOpen={isAdminOpen}>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { A, Div, Text } from '@/primitives'
|
|||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
import { Button as RACButton } from 'react-aria-components'
|
import { Button as RACButton } from 'react-aria-components'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode, useEffect, useRef } from 'react'
|
||||||
import { SubPanelId, useSidePanel } from '../hooks/useSidePanel'
|
import { SubPanelId, useSidePanel } from '../hooks/useSidePanel'
|
||||||
import {
|
import {
|
||||||
useIsRecordingModeEnabled,
|
useIsRecordingModeEnabled,
|
||||||
@@ -94,10 +94,49 @@ const ToolButton = ({
|
|||||||
|
|
||||||
export const Tools = () => {
|
export const Tools = () => {
|
||||||
const { data } = useConfig()
|
const { data } = useConfig()
|
||||||
const { openTranscript, openScreenRecording, activeSubPanelId } =
|
const { openTranscript, openScreenRecording, activeSubPanelId, isToolsOpen } =
|
||||||
useSidePanel()
|
useSidePanel()
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'moreTools' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'moreTools' })
|
||||||
|
|
||||||
|
// Restore focus to the element that opened the Tools panel
|
||||||
|
// following the same pattern as Chat.
|
||||||
|
const toolsTriggerRef = useRef<HTMLElement | null>(null)
|
||||||
|
const prevIsToolsOpenRef = useRef(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const wasOpen = prevIsToolsOpenRef.current
|
||||||
|
const isOpen = isToolsOpen
|
||||||
|
|
||||||
|
// Tools just opened
|
||||||
|
if (!wasOpen && isOpen) {
|
||||||
|
const activeEl = document.activeElement as HTMLElement | null
|
||||||
|
|
||||||
|
// If the active element is a MenuItem (DIV) that will be unmounted when the menu closes,
|
||||||
|
// find the "more options" button ("Plus d'options") that opened the menu
|
||||||
|
if (activeEl?.tagName === 'DIV') {
|
||||||
|
toolsTriggerRef.current = document.querySelector<HTMLButtonElement>(
|
||||||
|
'#room-options-trigger'
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// For direct button clicks (e.g. "Plus d'outils"), use the active element as is
|
||||||
|
toolsTriggerRef.current = activeEl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tools just closed
|
||||||
|
if (wasOpen && !isOpen) {
|
||||||
|
const trigger = toolsTriggerRef.current
|
||||||
|
if (trigger && document.contains(trigger)) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
trigger.focus({ preventScroll: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
toolsTriggerRef.current = null
|
||||||
|
}
|
||||||
|
|
||||||
|
prevIsToolsOpenRef.current = isOpen
|
||||||
|
}, [isToolsOpen])
|
||||||
|
|
||||||
const isTranscriptEnabled = useIsRecordingModeEnabled(
|
const isTranscriptEnabled = useIsRecordingModeEnabled(
|
||||||
RecordingMode.Transcript
|
RecordingMode.Transcript
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const OptionsButton = () => {
|
|||||||
return (
|
return (
|
||||||
<Menu variant="dark">
|
<Menu variant="dark">
|
||||||
<Button
|
<Button
|
||||||
|
id="room-options-trigger"
|
||||||
square
|
square
|
||||||
variant="primaryDark"
|
variant="primaryDark"
|
||||||
aria-label={t('options.buttonLabel')}
|
aria-label={t('options.buttonLabel')}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export function MobileControlBar({
|
|||||||
/>
|
/>
|
||||||
<HandToggle />
|
<HandToggle />
|
||||||
<Button
|
<Button
|
||||||
|
id="room-options-trigger"
|
||||||
square
|
square
|
||||||
variant="primaryDark"
|
variant="primaryDark"
|
||||||
aria-label={t('options.buttonLabel')}
|
aria-label={t('options.buttonLabel')}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const LateralMenu = () => {
|
|||||||
return (
|
return (
|
||||||
<DialogTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
|
<DialogTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
|
||||||
<Button
|
<Button
|
||||||
|
id="controlbar-more-options-trigger"
|
||||||
square
|
square
|
||||||
variant="secondaryDark"
|
variant="secondaryDark"
|
||||||
aria-label={t('controls.moreOptions')}
|
aria-label={t('controls.moreOptions')}
|
||||||
|
|||||||
Reference in New Issue
Block a user