From fa6967635d6aec5e470d3e8cc035ba56e75cfbd3 Mon Sep 17 00:00:00 2001 From: Cyril Date: Wed, 21 Jan 2026 12:41:18 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F(feat)=20extract=20pip=20opti?= =?UTF-8?q?ons=20menu=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit keep pip options layout reusable and isolated --- .../controls/Options/OptionsButton.tsx | 40 ++++++++++++++ .../controls/Options/PipOptionsMenu.tsx | 54 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/frontend/src/features/rooms/livekit/components/controls/Options/PipOptionsMenu.tsx diff --git a/src/frontend/src/features/rooms/livekit/components/controls/Options/OptionsButton.tsx b/src/frontend/src/features/rooms/livekit/components/controls/Options/OptionsButton.tsx index 715dcd30..8b56a951 100644 --- a/src/frontend/src/features/rooms/livekit/components/controls/Options/OptionsButton.tsx +++ b/src/frontend/src/features/rooms/livekit/components/controls/Options/OptionsButton.tsx @@ -2,9 +2,49 @@ import { useTranslation } from 'react-i18next' import { RiMoreFill } from '@remixicon/react' import { Button, Menu } from '@/primitives' import { OptionsMenuItems } from './OptionsMenuItems' +import { useOverlayPortalContainer } from '@/primitives/useOverlayPortalContainer' +import { useEffect, useRef, useState } from 'react' +import { PipOptionsMenu } from './PipOptionsMenu' export const OptionsButton = () => { const { t } = useTranslation('rooms') + const portalContainer = useOverlayPortalContainer() + const isInPiP = + portalContainer && portalContainer.ownerDocument !== document + const [isOpen, setIsOpen] = useState(false) + const wrapperRef = useRef(null) + + useEffect(() => { + if (!isOpen || !isInPiP) return + const doc = wrapperRef.current?.ownerDocument ?? document + + const handlePointerDown = (event: PointerEvent) => { + const target = event.target as Node | null + const wrapper = wrapperRef.current + if (!wrapper || !target) return + if (!wrapper.contains(target)) { + setIsOpen(false) + } + } + + doc.addEventListener('pointerdown', handlePointerDown, true) + return () => { + doc.removeEventListener('pointerdown', handlePointerDown, true) + } + }, [isInPiP, isOpen]) + + + + if (isInPiP) { + return ( + + ) + } return ( diff --git a/src/frontend/src/features/rooms/livekit/components/controls/Options/PipOptionsMenu.tsx b/src/frontend/src/features/rooms/livekit/components/controls/Options/PipOptionsMenu.tsx new file mode 100644 index 00000000..a2e42b22 --- /dev/null +++ b/src/frontend/src/features/rooms/livekit/components/controls/Options/PipOptionsMenu.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import { RiMoreFill } from '@remixicon/react' +import { Box, Button } from '@/primitives' +import { css } from '@/styled-system/css' +import { OptionsMenuItems } from './OptionsMenuItems' + +type PipOptionsMenuProps = { + wrapperRef: React.RefObject + isOpen: boolean + setIsOpen: (isOpen: boolean) => void + label: string +} + +export const PipOptionsMenu = ({ + wrapperRef, + isOpen, + setIsOpen, + label, +}: PipOptionsMenuProps) => { + return ( +
+ + {isOpen && ( +
+ + + +
+ )} +
+ ) +} \ No newline at end of file