diff --git a/src/frontend/src/features/pip/components/PipReactionPortals.tsx b/src/frontend/src/features/pip/components/PipReactionPortals.tsx
new file mode 100644
index 00000000..9d6aa219
--- /dev/null
+++ b/src/frontend/src/features/pip/components/PipReactionPortals.tsx
@@ -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) => (
+
+ ))}
+ >
+ )
+}
+
+const PipFloatingReaction = ({ reaction }: { reaction: Reaction }) => {
+ const containerRef = useRef(null)
+ const speed = useMemo(() => Math.random() * 1.5 + 0.5, [])
+ const scale = useMemo(() => Math.max(Math.random() + 0.5, 1), [])
+
+ return (
+
+
+
+ )
+}
diff --git a/src/frontend/src/features/pip/components/PipView.tsx b/src/frontend/src/features/pip/components/PipView.tsx
index 555d7bbc..77e0f7b8 100644
--- a/src/frontend/src/features/pip/components/PipView.tsx
+++ b/src/frontend/src/features/pip/components/PipView.tsx
@@ -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 = () => {
+
diff --git a/src/frontend/src/features/pip/components/reactions/PipReactionsPill.tsx b/src/frontend/src/features/pip/components/reactions/PipReactionsPill.tsx
index 438901f7..2bce4a56 100644
--- a/src/frontend/src/features/pip/components/reactions/PipReactionsPill.tsx
+++ b/src/frontend/src/features/pip/components/reactions/PipReactionsPill.tsx
@@ -56,15 +56,14 @@ export const PipReactionsPill = ({ isOpen, availableWidth }: Props) => {
{hasOverflow && (
- {canGoLeft && (
- paginate('left')}
- aria-label={t('previousReactions')}
- >
-
-
- )}
+ paginate('left')}
+ aria-label={t('previousReactions')}
+ disabled={!canGoLeft}
+ >
+
+
)}
@@ -74,15 +73,14 @@ export const PipReactionsPill = ({ isOpen, availableWidth }: Props) => {
{hasOverflow && (
- {canGoRight && (
- paginate('right')}
- aria-label={t('nextReactions')}
- >
-
-
- )}
+ paginate('right')}
+ aria-label={t('nextReactions')}
+ disabled={!canGoRight}
+ >
+
+
)}
@@ -156,5 +154,10 @@ const ArrowButton = styled('button', {
opacity: 1,
backgroundColor: 'primaryDark.300',
},
+ _disabled: {
+ opacity: 0.3,
+ cursor: 'default',
+ pointerEvents: 'none',
+ },
},
})
diff --git a/src/frontend/src/features/reactions/components/ReactionsToggle.tsx b/src/frontend/src/features/reactions/components/ReactionsToggle.tsx
index 27bfe4cf..7d289fd3 100644
--- a/src/frontend/src/features/reactions/components/ReactionsToggle.tsx
+++ b/src/frontend/src/features/reactions/components/ReactionsToggle.tsx
@@ -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 (
diff --git a/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/DesktopControlBar.tsx b/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/DesktopControlBar.tsx
index 13d19a71..0c6f43e0 100644
--- a/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/DesktopControlBar.tsx
+++ b/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/DesktopControlBar.tsx
@@ -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) {
const browserSupportsScreenSharing = supportsScreenSharing()
const desktopControlBarEl = useRef(null)
+ const { isOpen: isPiPOpen } = useRoomPiP()
const { toggleFullScreen, isFullscreenAvailable } = useFullScreen({})
useRegisterKeyboardShortcut({
id: 'focus-toolbar',
+ isDisabled: isPiPOpen,
handler: () => {
const root = desktopControlBarEl.current
if (!root) return
diff --git a/src/frontend/src/features/shortcuts/useRegisterKeyboardShortcut.ts b/src/frontend/src/features/shortcuts/useRegisterKeyboardShortcut.ts
index 0e8ab03c..32c38643 100644
--- a/src/frontend/src/features/shortcuts/useRegisterKeyboardShortcut.ts
+++ b/src/frontend/src/features/shortcuts/useRegisterKeyboardShortcut.ts
@@ -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])
}