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.js 2.6KB

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