mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-06 00:47:47 +00:00
♻️(frontend) refactor reaction keyboard navigation into a component
Extract keyboard navigation logic for reactions into a separate component/file to reduce file size and improve maintainability.
This commit is contained in:
committed by
aleb_the_flash
parent
71f76a81e9
commit
0737974f6d
+62
@@ -0,0 +1,62 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useFocusManager } from '@react-aria/focus'
|
||||
import { getFirstControlBarFocusable } from '@/utils/dom'
|
||||
import { REACTIONS_TOOLBAR_ID } from '../../constants'
|
||||
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
toggleId?: string
|
||||
controlBarId?: string
|
||||
}
|
||||
|
||||
export const ReactionsKeyboardNavigation = ({
|
||||
children,
|
||||
toggleId = 'reactions-toggle',
|
||||
controlBarId = 'control-bar',
|
||||
}: Props) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
|
||||
const focusManager = useFocusManager()
|
||||
const { close } = useReactionsToolbar()
|
||||
|
||||
const onFocus = (e: React.FocusEvent<HTMLDivElement>) => {
|
||||
const comingFromOutside = !e.currentTarget.contains(e.relatedTarget)
|
||||
if (comingFromOutside) {
|
||||
focusManager?.focusFirst()
|
||||
}
|
||||
}
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
focusManager?.focusNext({ wrap: true })
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
focusManager?.focusPrevious({ wrap: true })
|
||||
break
|
||||
case 'Escape':
|
||||
e.preventDefault()
|
||||
document.getElementById(toggleId)?.focus()
|
||||
close()
|
||||
break
|
||||
case 'Tab':
|
||||
if (!e.shiftKey) {
|
||||
e.preventDefault()
|
||||
getFirstControlBarFocusable(controlBarId)?.focus()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id={REACTIONS_TOOLBAR_ID}
|
||||
role="toolbar"
|
||||
aria-label={t('toolbar')}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={onFocus}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import { FocusScope, useFocusManager } from '@react-aria/focus'
|
||||
import { REACTIONS_TOOLBAR_ID } from '../../constants'
|
||||
import { FocusScope } from '@react-aria/focus'
|
||||
import { useReactionsToolbar } from '../../hooks/useReactionsToolbar'
|
||||
import { ReactionButton } from './ReactionButton'
|
||||
import { Emoji } from '../../types'
|
||||
import { styled } from '@/styled-system/jsx'
|
||||
import { layoutStore } from '@/stores/layout'
|
||||
import { getFirstControlBarFocusable } from '@/utils/dom'
|
||||
import { useIsMobile } from '@/utils/useIsMobile'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useDelayUnmount } from '@/hooks/useDelayUnmount'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ReactionsKeyboardNavigation } from './ReactionsKeyboardNavigation'
|
||||
|
||||
const Container = styled('div', {
|
||||
base: {
|
||||
@@ -86,52 +83,6 @@ const Strip = ({ children }: { children: React.ReactNode }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const KeyboardNavigation = ({ children }: { children: React.ReactNode }) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
|
||||
const focusManager = useFocusManager()
|
||||
|
||||
const onFocus = (e: React.FocusEvent<HTMLDivElement>) => {
|
||||
const comingFromOutside = !e.currentTarget.contains(e.relatedTarget)
|
||||
if (comingFromOutside) {
|
||||
focusManager?.focusFirst()
|
||||
}
|
||||
}
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
focusManager?.focusNext({ wrap: true })
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
focusManager?.focusPrevious({ wrap: true })
|
||||
break
|
||||
case 'Escape':
|
||||
e.preventDefault()
|
||||
document.getElementById('reactions-toggle')?.focus()
|
||||
layoutStore.showReactionsToolbar = false
|
||||
break
|
||||
case 'Tab':
|
||||
if (!e.shiftKey) {
|
||||
e.preventDefault()
|
||||
getFirstControlBarFocusable('control-bar')?.focus()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id={REACTIONS_TOOLBAR_ID}
|
||||
role="toolbar"
|
||||
aria-label={t('toolbar')}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocus={onFocus}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ReactionsToolbar = () => {
|
||||
const { isOpen } = useReactionsToolbar()
|
||||
const shouldMount = useDelayUnmount(isOpen, 300)
|
||||
@@ -142,13 +93,13 @@ export const ReactionsToolbar = () => {
|
||||
<Container>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-autofocus*/}
|
||||
<FocusScope autoFocus>
|
||||
<KeyboardNavigation>
|
||||
<ReactionsKeyboardNavigation>
|
||||
<Strip>
|
||||
{Object.values(Emoji).map((emoji) => (
|
||||
<ReactionButton key={emoji} emoji={emoji} />
|
||||
))}
|
||||
</Strip>
|
||||
</KeyboardNavigation>
|
||||
</ReactionsKeyboardNavigation>
|
||||
</FocusScope>
|
||||
</Container>
|
||||
)
|
||||
|
||||
@@ -9,5 +9,8 @@ export const useReactionsToolbar = () => {
|
||||
toggle: () => {
|
||||
layoutStore.showReactionsToolbar = !layoutSnap.showReactionsToolbar
|
||||
},
|
||||
close: () => {
|
||||
layoutStore.showReactionsToolbar = false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user