All files / components/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 903x 3x 3x 3x 3x 3x   3x   3x                       3x 3x             3x                             3x       3x 3x     3x       3x 3x   3x 3x 3x 3x 3x   3x 3x     3x 3x       6x                           3x  
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;