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]) }