From 0737974f6dddddc37e203a77ce75a789e43c912f Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Mon, 18 May 2026 15:01:00 +0200 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20refactor=20react?= =?UTF-8?q?ion=20keyboard=20navigation=20into=20a=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract keyboard navigation logic for reactions into a separate component/file to reduce file size and improve maintainability. --- .../toolbar/ReactionsKeyboardNavigation.tsx | 62 +++++++++++++++++++ .../components/toolbar/ReactionsToolbar.tsx | 57 ++--------------- .../reactions/hooks/useReactionsToolbar.ts | 3 + 3 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 src/frontend/src/features/reactions/components/toolbar/ReactionsKeyboardNavigation.tsx diff --git a/src/frontend/src/features/reactions/components/toolbar/ReactionsKeyboardNavigation.tsx b/src/frontend/src/features/reactions/components/toolbar/ReactionsKeyboardNavigation.tsx new file mode 100644 index 00000000..57d301af --- /dev/null +++ b/src/frontend/src/features/reactions/components/toolbar/ReactionsKeyboardNavigation.tsx @@ -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) => { + const comingFromOutside = !e.currentTarget.contains(e.relatedTarget) + if (comingFromOutside) { + focusManager?.focusFirst() + } + } + + const onKeyDown = (e: React.KeyboardEvent) => { + 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 ( + + ) +} diff --git a/src/frontend/src/features/reactions/components/toolbar/ReactionsToolbar.tsx b/src/frontend/src/features/reactions/components/toolbar/ReactionsToolbar.tsx index 6432009c..5cc3f7fb 100644 --- a/src/frontend/src/features/reactions/components/toolbar/ReactionsToolbar.tsx +++ b/src/frontend/src/features/reactions/components/toolbar/ReactionsToolbar.tsx @@ -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) => { - const comingFromOutside = !e.currentTarget.contains(e.relatedTarget) - if (comingFromOutside) { - focusManager?.focusFirst() - } - } - - const onKeyDown = (e: React.KeyboardEvent) => { - 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 ( - - ) -} - export const ReactionsToolbar = () => { const { isOpen } = useReactionsToolbar() const shouldMount = useDelayUnmount(isOpen, 300) @@ -142,13 +93,13 @@ export const ReactionsToolbar = () => { {/* eslint-disable-next-line jsx-a11y/no-autofocus*/} - + {Object.values(Emoji).map((emoji) => ( ))} - + ) diff --git a/src/frontend/src/features/reactions/hooks/useReactionsToolbar.ts b/src/frontend/src/features/reactions/hooks/useReactionsToolbar.ts index 2b16f7f6..ff60b29d 100644 --- a/src/frontend/src/features/reactions/hooks/useReactionsToolbar.ts +++ b/src/frontend/src/features/reactions/hooks/useReactionsToolbar.ts @@ -9,5 +9,8 @@ export const useReactionsToolbar = () => { toggle: () => { layoutStore.showReactionsToolbar = !layoutSnap.showReactionsToolbar }, + close: () => { + layoutStore.showReactionsToolbar = false + }, } }