From 5fa287c2b087f87bab0c9c4c6638a859a302e516 Mon Sep 17 00:00:00 2001 From: Cyril Date: Thu, 15 Jan 2026 12:35:40 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BF=EF=B8=8F(frontend)=20add=20escape=20k?= =?UTF-8?q?ey=20hook=20for=20overlays?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use shared hook to close side panel and reactions --- .../rooms/livekit/components/SidePanel.tsx | 17 +++++++++ .../components/controls/ReactionsToggle.tsx | 19 ++++++++++ src/frontend/src/hooks/useEscapeKey.ts | 36 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/frontend/src/hooks/useEscapeKey.ts diff --git a/src/frontend/src/features/rooms/livekit/components/SidePanel.tsx b/src/frontend/src/features/rooms/livekit/components/SidePanel.tsx index f52c1fa4..4fbb0171 100644 --- a/src/frontend/src/features/rooms/livekit/components/SidePanel.tsx +++ b/src/frontend/src/features/rooms/livekit/components/SidePanel.tsx @@ -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 ( { const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' }) const [reactions, setReactions] = useState([]) const instanceIdRef = useRef(0) + const triggerRef = useRef(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 ( <>
{ variant="primaryDark" aria-label={t('button')} tooltip={t('button')} + ref={triggerRef} onPress={() => setIsVisible(!isVisible)} > diff --git a/src/frontend/src/hooks/useEscapeKey.ts b/src/frontend/src/hooks/useEscapeKey.ts new file mode 100644 index 00000000..362d2581 --- /dev/null +++ b/src/frontend/src/hooks/useEscapeKey.ts @@ -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]) +}