theluyuan 38ba663466
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled
init
2025-09-02 14:49:16 +08: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;