fixup! ✨(frontend) re-focus toolbar on shortcut when already open

This commit is contained in:
Cyril
2026-02-25 08:29:41 +01:00
parent fcd998390d
commit eee4fbcfa5
2 changed files with 74 additions and 75 deletions
+2 -3
View File
@@ -16,13 +16,12 @@ and this project adheres to
- ♻️(frontend) replace custom reactions toolbar with react aria popover #985
- 🔒️(frontend) uninstall curl from the frontend production image #987
- 💄(frontend) add focus ring to reaction emoji buttons
- 💄(frontend) add focus ring to reaction emoji buttons #1002
- ✨(frontend) introduce a shortcut settings tab #975
- 🚚(frontend) rename "wellknown" directory to "well-known" #1009
- 🌐(frontend) localize SR modifier labels #1010
- ⬆️(backend) update python dependencies #1011
- ♿️(a11y) fix focus ring on tab container components #1012
- ♿️(frontend) fix focus ring on tab container components #1012
- ♻️(frontend) use react aria toolbar for arrow key navigation #1005
## [1.8.0] - 2026-02-20
@@ -12,11 +12,7 @@ import {
} from '@/features/rooms/livekit/components/ReactionPortal'
import { getEmojiLabel } from '@/features/rooms/livekit/utils/reactionUtils'
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
import {
Popover as RACPopover,
DialogTrigger,
Toolbar,
} from 'react-aria-components'
import { Popover as RACPopover, Toolbar } from 'react-aria-components'
import { FocusScope } from '@react-aria/focus'
import { Participant } from 'livekit-client'
import useRateLimiter from '@/hooks/useRateLimiter'
@@ -44,6 +40,7 @@ export const ReactionsToggle = () => {
const [reactions, setReactions] = useState<Reaction[]>([])
const instanceIdRef = useRef(0)
const room = useRoomContext()
const triggerRef = useRef<HTMLButtonElement>(null)
const [isOpen, setIsOpen] = useState(false)
@@ -126,73 +123,76 @@ export const ReactionsToggle = () => {
return (
<>
<div className={css({ position: 'relative' })}>
<DialogTrigger isOpen={isOpen} onOpenChange={setIsOpen}>
<ToggleButton
id="reaction-toggle"
square
variant="primaryDark"
aria-label={t('button')}
tooltip={t('button')}
isSelected={isOpen}
onChange={setIsOpen}
>
<RiEmotionLine />
</ToggleButton>
<RACPopover
placement="top"
offset={8}
isNonModal
shouldCloseOnInteractOutside={() => false}
className={css({
borderRadius: '8px',
padding: '0.35rem',
backgroundColor: 'primaryDark.50',
'&[data-entering]': {
animation: 'fade 200ms ease',
},
'&[data-exiting]': {
animation: 'fade 200ms ease-in reverse',
},
})}
>
{/* eslint-disable-next-line jsx-a11y/no-autofocus -- FocusScope autoFocus is programmatic focus for overlays, not the HTML autofocus attribute */}
<FocusScope autoFocus restoreFocus>
<div onKeyDownCapture={handleToolbarKeyDown}>
<Toolbar
orientation="horizontal"
aria-label={t('button')}
className={css({
display: 'flex',
gap: '0.5rem',
})}
>
{Object.values(Emoji).map((emoji, index) => (
<Button
key={index}
onPress={() => debouncedSendReaction(emoji)}
aria-label={t('send', { emoji: getEmojiLabel(emoji, t) })}
variant="primaryTextDark"
size="sm"
square
data-attr={`send-reaction-${emoji}`}
>
<img
src={`/assets/reactions/${emoji}.png`}
alt=""
className={css({
width: '28px',
height: '28px',
pointerEvents: 'none',
userSelect: 'none',
})}
/>
</Button>
))}
</Toolbar>
</div>
</FocusScope>
</RACPopover>
</DialogTrigger>
<ToggleButton
ref={triggerRef}
id="reaction-toggle"
square
variant="primaryDark"
aria-label={t('button')}
tooltip={t('button')}
isSelected={isOpen}
onChange={setIsOpen}
>
<RiEmotionLine />
</ToggleButton>
<RACPopover
triggerRef={triggerRef}
isOpen={isOpen}
onOpenChange={setIsOpen}
placement="top"
offset={8}
isNonModal
shouldCloseOnInteractOutside={() => false}
className={css({
borderRadius: '8px',
padding: '0.35rem',
backgroundColor: 'primaryDark.50',
'&[data-entering]': {
animation: 'fade 200ms ease',
},
'&[data-exiting]': {
animation: 'fade 200ms ease-in reverse',
},
})}
>
{/* eslint-disable-next-line jsx-a11y/no-autofocus -- FocusScope autoFocus is programmatic focus for overlays, not the HTML autofocus attribute */}
<FocusScope autoFocus restoreFocus>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- handles Tab exit and arrow wrapping for the portal-rendered toolbar */}
<div onKeyDownCapture={handleToolbarKeyDown}>
<Toolbar
orientation="horizontal"
aria-label={t('button')}
className={css({
display: 'flex',
gap: '0.5rem',
})}
>
{Object.values(Emoji).map((emoji, index) => (
<Button
key={index}
onPress={() => debouncedSendReaction(emoji)}
aria-label={t('send', { emoji: getEmojiLabel(emoji, t) })}
variant="primaryTextDark"
size="sm"
square
data-attr={`send-reaction-${emoji}`}
>
<img
src={`/assets/reactions/${emoji}.pPng`}
alt=""
className={css({
width: '28px',
height: '28px',
pointerEvents: 'none',
userSelect: 'none',
})}
/>
</Button>
))}
</Toolbar>
</div>
</FocusScope>
</RACPopover>
</div>
<ReactionPortals reactions={reactions} />
</>