All files / Portal Portal.tsx

100% Statements 28/28
62.5% Branches 5/8
100% Functions 4/4
100% Lines 28/28

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 90179x 179x 179x 179x 179x 179x   179x   179x                       179x 179x             179x                             44x       44x 44x     44x       44x 44x   44x 44x 44x 44x 44x   44x 44x     43x 43x       90x                           179x  
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;