You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DialogPortal.ts 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import { ReactNode, useEffect, useRef, useState } from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { useSelector } from 'react-redux';
  4. import { IReduxState } from '../../../app/types';
  5. import { ZINDEX_DIALOG_PORTAL } from '../../constants';
  6. type Props = {
  7. /**
  8. * The component(s) to be displayed within the drawer portal.
  9. */
  10. children: ReactNode;
  11. /**
  12. * Custom class name to apply on the container div.
  13. */
  14. className?: string;
  15. /**
  16. * Function used to get the reference to the container div.
  17. */
  18. getRef?: Function;
  19. /**
  20. * Function used to get the updated size info of the container on it's resize.
  21. */
  22. setSize?: Function;
  23. /**
  24. * Custom style to apply to the container div.
  25. */
  26. style?: any;
  27. /**
  28. * The selector for the element we consider the content container.
  29. * This is used to determine the correct size of the portal content.
  30. */
  31. targetSelector?: string;
  32. };
  33. /**
  34. * Component meant to render a drawer at the bottom of the screen,
  35. * by creating a portal containing the component's children.
  36. *
  37. * @returns {ReactElement}
  38. */
  39. function DialogPortal({ children, className, style, getRef, setSize, targetSelector }: Props) {
  40. const clientWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].clientWidth);
  41. const [ portalTarget ] = useState(() => {
  42. const portalDiv = document.createElement('div');
  43. portalDiv.style.visibility = 'hidden';
  44. return portalDiv;
  45. });
  46. const timerRef = useRef<number>();
  47. useEffect(() => {
  48. if (style) {
  49. for (const styleProp of Object.keys(style)) {
  50. const objStyle: any = portalTarget.style;
  51. objStyle[styleProp] = style[styleProp];
  52. }
  53. }
  54. if (className) {
  55. portalTarget.className = className;
  56. }
  57. }, [ style, className ]);
  58. useEffect(() => {
  59. if (portalTarget && getRef) {
  60. getRef(portalTarget);
  61. portalTarget.style.zIndex = `${ZINDEX_DIALOG_PORTAL}`;
  62. }
  63. }, [ portalTarget ]);
  64. useEffect(() => {
  65. const size = {
  66. width: 1,
  67. height: 1
  68. };
  69. const observer = new ResizeObserver(entries => {
  70. const { contentRect } = entries[0];
  71. if (contentRect.width !== size.width || contentRect.height !== size.height) {
  72. setSize?.(contentRect);
  73. clearTimeout(timerRef.current);
  74. timerRef.current = window.setTimeout(() => {
  75. portalTarget.style.visibility = 'visible';
  76. }, 100);
  77. }
  78. });
  79. const target = targetSelector ? portalTarget.querySelector(targetSelector) : portalTarget;
  80. if (document.body) {
  81. document.body.appendChild(portalTarget);
  82. observer.observe(target ?? portalTarget);
  83. }
  84. return () => {
  85. observer.unobserve(target ?? portalTarget);
  86. if (document.body) {
  87. document.body.removeChild(portalTarget);
  88. }
  89. };
  90. }, [ clientWidth ]);
  91. return ReactDOM.createPortal(
  92. children,
  93. portalTarget
  94. );
  95. }
  96. export default DialogPortal;