️(frontend) add escape key hook for overlays

Use shared hook to close side panel and reactions
This commit is contained in:
Cyril
2026-01-15 12:35:40 +01:00
parent f5cc76861d
commit 5fa287c2b0
3 changed files with 72 additions and 0 deletions
@@ -14,6 +14,7 @@ import { Admin } from './Admin'
import { Tools } from './Tools'
import { Info } from './Info'
import { useSidePanelRef } from '../hooks/useSidePanelRef'
import { useEscapeKey } from '@/hooks/useEscapeKey'
import { HStack } from '@/styled-system/jsx'
type StyledSidePanelProps = {
@@ -155,6 +156,22 @@ const SidePanelContent = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
const panelRef = useSidePanelRef()
useEscapeKey(
() => {
// Close subpanel + panel together for a consistent Escape behavior
if (isSubPanelOpen) {
layoutStore.activeSubPanelId = null
layoutStore.activePanelId = null
return
}
layoutStore.activePanelId = null
},
{
isActive: isSidePanelOpen,
capture: true,
}
)
return (
<StyledSidePanel
title={t(`heading.${activeSubPanelId || activePanelId}`)}
@@ -14,6 +14,7 @@ import { getEmojiLabel } from '@/features/rooms/livekit/utils/reactionUtils'
import { Toolbar as RACToolbar } from 'react-aria-components'
import { Participant } from 'livekit-client'
import useRateLimiter from '@/hooks/useRateLimiter'
import { useEscapeKey } from '@/hooks/useEscapeKey'
// eslint-disable-next-line react-refresh/only-export-components
export enum Emoji {
@@ -37,6 +38,7 @@ export const ReactionsToggle = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const [reactions, setReactions] = useState<Reaction[]>([])
const instanceIdRef = useRef(0)
const triggerRef = useRef<HTMLButtonElement | null>(null)
const room = useRoomContext()
const [isVisible, setIsVisible] = useState(false)
@@ -102,6 +104,22 @@ export const ReactionsToggle = () => {
}
}, [isVisible, isRendered])
useEscapeKey(
() => {
// Mirror the trigger button behavior (Enter toggles open/close)
triggerRef.current?.click()
requestAnimationFrame(() => {
triggerRef.current?.focus({ preventScroll: true })
})
},
{
isActive: isVisible,
capture: true,
preventDefault: true,
stopPropagation: true,
}
)
return (
<>
<div
@@ -114,6 +132,7 @@ export const ReactionsToggle = () => {
variant="primaryDark"
aria-label={t('button')}
tooltip={t('button')}
ref={triggerRef}
onPress={() => setIsVisible(!isVisible)}
>
<RiEmotionLine />
+36
View File
@@ -0,0 +1,36 @@
import { useEffect, useRef } from 'react'
type UseEscapeKeyOptions = {
isActive: boolean
capture?: boolean
preventDefault?: boolean
stopPropagation?: boolean
}
export const useEscapeKey = (
handler: () => void,
{
isActive,
capture = false,
preventDefault = false,
stopPropagation = false,
}: UseEscapeKeyOptions
) => {
const handleRef = useRef(handler)
handleRef.current = handler
useEffect(() => {
if (!isActive) return
const onKeyDown = (event: KeyboardEvent) => {
if (event.key !== 'Escape') return
if (preventDefault) event.preventDefault()
if (stopPropagation) event.stopPropagation()
handleRef.current()
}
document.addEventListener('keydown', onKeyDown, capture)
return () => {
document.removeEventListener('keydown', onKeyDown, capture)
}
}, [capture, isActive, preventDefault, stopPropagation])
}