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.8KB

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