All files / Portal Portal.tsx

100% Statements 21/21
62.5% Branches 5/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              179x   179x                       179x 179x             179x                             44x       44x 44x     44x       44x 44x   44x 44x 44x 44x 44x   44x 44x     43x 43x       90x                              
import React from 'react';
import PropTypes from 'react-peek/prop-types';
import ReactDOM from 'react-dom';
import { omitProps, 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;
 
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 = {
		children: node`
			any valid React children
		`,
 
		className: any`
			Appended to the component-specific class names set on the root element.
			Value is run through the \`classnames\` library.
		`,
 
		portalId: string`
			The \`id\` of the portal element that is appended to \`document.body\`.
		`,
	};
 
	state = {
		isReady: false,
	};
 
	manuallyCreatedPortal: boolean = false;
	portalElement: HTMLElement = document.createElement('div');
 
	componentDidMount(): void {
		const { portalId } = this.props;
 
		let portalElement;
 
		Eif (portalId) {
			portalElement = document.getElementById(portalId);
		}
		Eif (!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 {
		Eif (this.manuallyCreatedPortal) {
			this.portalElement.remove();
		}
	}
	render(): React.ReactNode {
		return this.state.isReady
			? ReactDOM.createPortal(
					<div
						className={classNames(cx('&'), this.props.className)}
						{...omitProps(this.props, undefined, Object.keys(Portal.propTypes))}
					>
						{this.props.children}
					</div>,
					this.portalElement
			  )
			: null;
	}
}
 
export default Portal;