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

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