mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-30 20:29:07 +00:00
♿️(frontend) add escape key hook for overlays
Use shared hook to close side panel and reactions
This commit is contained in:
@@ -14,6 +14,7 @@ import { Admin } from './Admin'
|
|||||||
import { Tools } from './Tools'
|
import { Tools } from './Tools'
|
||||||
import { Info } from './Info'
|
import { Info } from './Info'
|
||||||
import { useSidePanelRef } from '../hooks/useSidePanelRef'
|
import { useSidePanelRef } from '../hooks/useSidePanelRef'
|
||||||
|
import { useEscapeKey } from '@/hooks/useEscapeKey'
|
||||||
import { HStack } from '@/styled-system/jsx'
|
import { HStack } from '@/styled-system/jsx'
|
||||||
|
|
||||||
type StyledSidePanelProps = {
|
type StyledSidePanelProps = {
|
||||||
@@ -155,6 +156,22 @@ const SidePanelContent = () => {
|
|||||||
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
|
||||||
const panelRef = useSidePanelRef()
|
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 (
|
return (
|
||||||
<StyledSidePanel
|
<StyledSidePanel
|
||||||
title={t(`heading.${activeSubPanelId || activePanelId}`)}
|
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 { Toolbar as RACToolbar } from 'react-aria-components'
|
||||||
import { Participant } from 'livekit-client'
|
import { Participant } from 'livekit-client'
|
||||||
import useRateLimiter from '@/hooks/useRateLimiter'
|
import useRateLimiter from '@/hooks/useRateLimiter'
|
||||||
|
import { useEscapeKey } from '@/hooks/useEscapeKey'
|
||||||
|
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
// eslint-disable-next-line react-refresh/only-export-components
|
||||||
export enum Emoji {
|
export enum Emoji {
|
||||||
@@ -37,6 +38,7 @@ export const ReactionsToggle = () => {
|
|||||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
|
||||||
const [reactions, setReactions] = useState<Reaction[]>([])
|
const [reactions, setReactions] = useState<Reaction[]>([])
|
||||||
const instanceIdRef = useRef(0)
|
const instanceIdRef = useRef(0)
|
||||||
|
const triggerRef = useRef<HTMLButtonElement | null>(null)
|
||||||
const room = useRoomContext()
|
const room = useRoomContext()
|
||||||
|
|
||||||
const [isVisible, setIsVisible] = useState(false)
|
const [isVisible, setIsVisible] = useState(false)
|
||||||
@@ -102,6 +104,22 @@ export const ReactionsToggle = () => {
|
|||||||
}
|
}
|
||||||
}, [isVisible, isRendered])
|
}, [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@@ -114,6 +132,7 @@ export const ReactionsToggle = () => {
|
|||||||
variant="primaryDark"
|
variant="primaryDark"
|
||||||
aria-label={t('button')}
|
aria-label={t('button')}
|
||||||
tooltip={t('button')}
|
tooltip={t('button')}
|
||||||
|
ref={triggerRef}
|
||||||
onPress={() => setIsVisible(!isVisible)}
|
onPress={() => setIsVisible(!isVisible)}
|
||||||
>
|
>
|
||||||
<RiEmotionLine />
|
<RiEmotionLine />
|
||||||
|
|||||||
@@ -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])
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user