All files / Portal Portal.tsx

100% Statements 21/21
75% Branches 6/8
100% Functions 3/3
100% Lines 21/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101                  26x   26x                           26x 26x       26x                                   203x       203x 203x     47x       47x 46x   47x 47x 47x 47x 47x   47x 47x     45x 45x       252x                                            
import React from 'react';
import _, { omit } from 'lodash';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
 
import { StandardProps } from '../../util/component-types';
import { lucidClassNames } from '../../util/style-helpers';
import classNames from 'classnames';
 
const cx = lucidClassNames.bind('&-Portal');
 
const { any, node, string } = PropTypes;
 
export interface IPortalProps
	extends StandardProps,
		React.HTMLProps<HTMLDivElement> {
	/** The `id` of the portal element that is appended to `document.body`. */
	portalId?: string;
}
 
interface IPortalState {
	isReady: boolean;
}
 
class Portal extends React.Component<IPortalProps, IPortalState, {}> {
	static displayName = 'Portal';
	static peek = {
		description: `A \`Portal\` component is used to render content in a container that is appended to \`document.body\`.`,
		categories: ['utility'],
	};
	static propTypes = {
		/**
			any valid React children
		*/
		children: node,
 
		/**
			Appended to the component-specific class names set on the root element.
			Value is run through the \`classnames\` library.
		*/
		className: any,
 
		/**
			The \`id\` of the portal element that is appended to \`document.body\`.
		*/
		portalId: string,
	};
 
	state = {
		isReady: false,
	};
 
	manuallyCreatedPortal: boolean = false;
	portalElement: HTMLElement = document.createElement('div');
 
	componentDidMount(): void {
		const { portalId } = this.props;
 
		let portalElement;
 
		if (portalId) {
			portalElement = document.getElementById(portalId);
		}
		if (!portalElement) {
			this.manuallyCreatedPortal = true;
			portalElement = document.createElement('div');
			portalElement.id = portalId as string;
			document.body.appendChild(portalElement);
		}
		this.portalElement = portalElement;
		this.setState({ isReady: true });
	}
	componentWillUnmount(): void {
		if (this.manuallyCreatedPortal) {
			this.portalElement.remove();
		}
	}
	render(): React.ReactNode {
		return this.state.isReady
			? ReactDOM.createPortal(
					<div
						data-test-id={this.props.className}
						className={classNames(cx('&'), this.props.className)}
						{...omit(this.props, [
							'className',
							'children',
							'portalId',
							'initialState',
							'callbackId',
						])}
					>
						{this.props.children}
					</div>,
					this.portalElement
			  )
			: null;
	}
}
 
export default Portal;