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 2.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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. /**
  29. * Component meant to render a drawer at the bottom of the screen,
  30. * by creating a portal containing the component's children.
  31. *
  32. * @returns {ReactElement}
  33. */
  34. function DialogPortal({ children, className, style, getRef, setSize }: Props) {
  35. const clientWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].clientWidth);
  36. const [ portalTarget ] = useState(() => {
  37. const portalDiv = document.createElement('div');
  38. portalDiv.style.visibility = 'hidden';
  39. return portalDiv;
  40. });
  41. const timerRef = useRef<number>();
  42. useEffect(() => {
  43. if (style) {
  44. for (const styleProp of Object.keys(style)) {
  45. const objStyle: any = portalTarget.style;
  46. objStyle[styleProp] = style[styleProp];
  47. }
  48. }
  49. if (className) {
  50. portalTarget.className = className;
  51. }
  52. }, [ style, className ]);
  53. useEffect(() => {
  54. if (portalTarget && getRef) {
  55. getRef(portalTarget);
  56. portalTarget.style.zIndex = `${ZINDEX_DIALOG_PORTAL}`;
  57. }
  58. }, [ portalTarget ]);
  59. useEffect(() => {
  60. const size = {
  61. width: 1,
  62. height: 1
  63. };
  64. const observer = new ResizeObserver(entries => {
  65. const { contentRect } = entries[0];
  66. if (contentRect.width !== size.width || contentRect.height !== size.height) {
  67. setSize?.(contentRect);
  68. clearTimeout(timerRef.current);
  69. timerRef.current = window.setTimeout(() => {
  70. portalTarget.style.visibility = 'visible';
  71. }, 100);
  72. }
  73. });
  74. if (document.body) {
  75. document.body.appendChild(portalTarget);
  76. observer.observe(portalTarget);
  77. }
  78. return () => {
  79. observer.unobserve(portalTarget);
  80. if (document.body) {
  81. document.body.removeChild(portalTarget);
  82. }
  83. };
  84. }, [ clientWidth ]);
  85. return ReactDOM.createPortal(
  86. children,
  87. portalTarget
  88. );
  89. }
  90. export default DialogPortal;