Files
jitsi-meet/react/features/toolbox/components/web/DialogPortal.ts
Hristo Terezov 1556f1b81a ref(responsive-ui): rename clientWidth to videoSpaceWidth.
Currently the clientWidth is not representing the window width but it is representing the available video space width since we are subtracting the width of the participants pane and chat area.
2025-05-06 09:40:54 -05:00

127 lines
3.6 KiB
TypeScript

import { ReactNode, useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import { useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { debounce } from '../../../base/config/functions.any';
import { ZINDEX_DIALOG_PORTAL } from '../../constants';
interface IProps {
/**
* The component(s) to be displayed within the drawer portal.
*/
children: ReactNode;
/**
* Custom class name to apply on the container div.
*/
className?: string;
/**
* Function used to get the reference to the container div.
*/
getRef?: Function;
/**
* Function called when the portal target becomes actually visible.
*/
onVisible?: Function;
/**
* Function used to get the updated size info of the container on it's resize.
*/
setSize?: Function;
/**
* Custom style to apply to the container div.
*/
style?: any;
/**
* The selector for the element we consider the content container.
* This is used to determine the correct size of the portal content.
*/
targetSelector?: string;
}
/**
* Component meant to render a drawer at the bottom of the screen,
* by creating a portal containing the component's children.
*
* @returns {ReactElement}
*/
function DialogPortal({ children, className, style, getRef, setSize, targetSelector, onVisible }: IProps) {
const videoSpaceWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].videoSpaceWidth);
const [ portalTarget ] = useState(() => {
const portalDiv = document.createElement('div');
portalDiv.style.visibility = 'hidden';
return portalDiv;
});
const timerRef = useRef<number>();
useEffect(() => {
if (style) {
for (const styleProp of Object.keys(style)) {
const objStyle: any = portalTarget.style;
objStyle[styleProp] = style[styleProp];
}
}
if (className) {
portalTarget.className = className;
}
}, [ style, className ]);
useEffect(() => {
if (portalTarget && getRef) {
getRef(portalTarget);
portalTarget.style.zIndex = `${ZINDEX_DIALOG_PORTAL}`;
}
}, [ portalTarget, getRef ]);
useEffect(() => {
const size = {
width: 1,
height: 1
};
const debouncedResizeCallback = debounce((entries: ResizeObserverEntry[]) => {
const { contentRect } = entries[0];
if (contentRect.width !== size.width || contentRect.height !== size.height) {
setSize?.(contentRect);
clearTimeout(timerRef.current);
timerRef.current = window.setTimeout(() => {
portalTarget.style.visibility = 'visible';
onVisible?.();
}, 100);
}
}, 20); // 20ms delay
// Create and observe ResizeObserver
const observer = new ResizeObserver(debouncedResizeCallback);
const target = targetSelector ? portalTarget.querySelector(targetSelector) : portalTarget;
if (document.body) {
document.body.appendChild(portalTarget);
observer.observe(target ?? portalTarget);
}
return () => {
observer.unobserve(target ?? portalTarget);
if (document.body) {
document.body.removeChild(portalTarget);
}
};
}, [ videoSpaceWidth ]);
return ReactDOM.createPortal(
children,
portalTarget
);
}
export default DialogPortal;