🐛(frontend) fix PiP reactions rendering and shortcut ownership

Show reactions in PiP and prevent main shortcuts from overriding PiP.
This commit is contained in:
Cyril
2026-05-06 12:49:27 +02:00
parent 184e957565
commit afaaec4be1
6 changed files with 87 additions and 21 deletions
@@ -0,0 +1,51 @@
import { useMemo, useRef } from 'react'
import { useSnapshot } from 'valtio'
import { css } from '@/styled-system/css'
import { reactionsStore } from '@/stores/reactions'
import { FloatingReaction } from '@/features/reactions/components/ReactionPortals'
import type { Reaction } from '@/features/reactions/types'
/**
* Renders floating emoji reactions inside the PiP window.
* Reads the same shared reactionsStore used by the main window.
*/
export const PipReactionPortals = () => {
const { reactions } = useSnapshot(reactionsStore)
return (
<>
{reactions.map((reaction) => (
<PipFloatingReaction key={reaction.id} reaction={reaction} />
))}
</>
)
}
const PipFloatingReaction = ({ reaction }: { reaction: Reaction }) => {
const containerRef = useRef<HTMLDivElement>(null)
const speed = useMemo(() => Math.random() * 1.5 + 0.5, [])
const scale = useMemo(() => Math.max(Math.random() + 0.5, 1), [])
return (
<div
ref={containerRef}
className={css({
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
pointerEvents: 'none',
overflow: 'hidden',
})}
>
<FloatingReaction
emoji={reaction.emoji}
speed={speed}
scale={scale}
name={reaction.participantName}
isLocal={reaction.isLocal}
/>
</div>
)
}
@@ -13,6 +13,7 @@ import { PipReactionsToolbar } from './PipReactionsToolbar'
import { PipStage } from './layouts/PipStage'
import { PipNotificationOverlay } from './notifications/PipNotificationOverlay'
import { PipConnectionStateToast } from './notifications/PipConnectionStateToast'
import { PipReactionPortals } from './PipReactionPortals'
export const PipView = () => {
const browserSupportsScreenSharing = supportsScreenSharing()
@@ -49,6 +50,7 @@ export const PipView = () => {
<PipReactionsToolbar />
<PipControlBar showScreenShare={browserSupportsScreenSharing} />
<SidePanel store={pipLayoutStore} />
<PipReactionPortals />
<OverlayStack>
<PipConnectionStateToast />
<PipNotificationOverlay />
@@ -56,15 +56,14 @@ export const PipReactionsPill = ({ isOpen, availableWidth }: Props) => {
<Pill isVisible={isVisible}>
{hasOverflow && (
<ArrowSlot>
{canGoLeft && (
<ArrowButton
type="button"
onClick={() => paginate('left')}
aria-label={t('previousReactions')}
>
<RiArrowLeftSLine size={16} />
</ArrowButton>
)}
<ArrowButton
type="button"
onClick={() => paginate('left')}
aria-label={t('previousReactions')}
disabled={!canGoLeft}
>
<RiArrowLeftSLine size={16} />
</ArrowButton>
</ArrowSlot>
)}
<EmojiRow>
@@ -74,15 +73,14 @@ export const PipReactionsPill = ({ isOpen, availableWidth }: Props) => {
</EmojiRow>
{hasOverflow && (
<ArrowSlot>
{canGoRight && (
<ArrowButton
type="button"
onClick={() => paginate('right')}
aria-label={t('nextReactions')}
>
<RiArrowRightSLine size={16} />
</ArrowButton>
)}
<ArrowButton
type="button"
onClick={() => paginate('right')}
aria-label={t('nextReactions')}
disabled={!canGoRight}
>
<RiArrowRightSLine size={16} />
</ArrowButton>
</ArrowSlot>
)}
</Pill>
@@ -156,5 +154,10 @@ const ArrowButton = styled('button', {
opacity: 1,
backgroundColor: 'primaryDark.300',
},
_disabled: {
opacity: 0.3,
cursor: 'default',
pointerEvents: 'none',
},
},
})
@@ -7,6 +7,7 @@ import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKey
import { REACTIONS_TOOLBAR_ID } from '../constants'
import { useReactionsToolbar } from '../hooks/useReactionsToolbar'
import { layoutStore } from '@/stores/layout'
import { useRoomPiP } from '@/features/pip/hooks/useRoomPiP'
const focusReactionsToolbar = () => {
document
@@ -17,6 +18,7 @@ const focusReactionsToolbar = () => {
export const ReactionsToggle = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'controls.reactions' })
const { isOpen: isPiPOpen } = useRoomPiP()
const { isOpen, toggle } = useReactionsToolbar()
@@ -31,6 +33,7 @@ export const ReactionsToggle = () => {
useRegisterKeyboardShortcut({
id: 'reaction',
handler: handleShortcut,
isDisabled: isPiPOpen,
})
return (
@@ -16,17 +16,20 @@ import { VideoDeviceControl } from '../../components/controls/Device/VideoDevice
import { AudioDevicesControl } from '../../components/controls/Device/AudioDevicesControl'
import { ReactionsToggle } from '@/features/reactions/components/ReactionsToggle'
import { ControlBarRegion } from '@/features/layout/components/ControlBarRegion'
import { useRoomPiP } from '@/features/pip/hooks/useRoomPiP'
export function DesktopControlBar({
onDeviceError,
}: Readonly<ControlBarAuxProps>) {
const browserSupportsScreenSharing = supportsScreenSharing()
const desktopControlBarEl = useRef<HTMLDivElement>(null)
const { isOpen: isPiPOpen } = useRoomPiP()
const { toggleFullScreen, isFullscreenAvailable } = useFullScreen({})
useRegisterKeyboardShortcut({
id: 'focus-toolbar',
isDisabled: isPiPOpen,
handler: () => {
const root = desktopControlBarEl.current
if (!root) return
@@ -19,10 +19,14 @@ export const useRegisterKeyboardShortcut = ({
const descriptor = getShortcutDescriptorById(id)
if (!descriptor?.shortcut) return
const formattedKey = formatShortcutKey(descriptor.shortcut)
if (isDisabled) {
keyboardShortcutsStore.shortcuts.delete(formattedKey)
} else {
if (!isDisabled) {
keyboardShortcutsStore.shortcuts.set(formattedKey, handler)
}
return () => {
// Remove only if this is still the registered handler
if (keyboardShortcutsStore.shortcuts.get(formattedKey) === handler) {
keyboardShortcutsStore.shortcuts.delete(formattedKey)
}
}
}, [handler, id, isDisabled])
}