React Portal
The portal
approach that transports its child into a new React component and attaches it to document.body. This is useful for modals or popovers.
Demo: https://trendmicro-frontend.github.io/react-portal
- Install the latest version of react and react-portal:
npm install --save react @trendmicro/react-portal
- Install react-portal` with @trendmicro scope:
import Portal from '@trendmicro/react-portal';
// Use LegacyPortal if you need cross-frame rendering support.
import LegacyPortal from '@trendmicro/react-portal/LegacyPortal';
<Portal>
This text is transported to the end of document.body.
</Portal>
<Portal node={document.body && document.body.querySelector('#modal-container')}>
This text is transported to a DOM element.
</Portal>
<LegacyPortal
node={window.top.document && window.top.document.querySelector('#modal-container')}
>
This text is transported to a DOM element within the top window document.
</LegacyPortal>
We recommend using styled-components to make style changes, like so:
import PropTypes from 'prop-types';
import styled, { keyframes } from 'styled-components';
const Overlay = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
color: #fff;
background-color: rgba(0, 0, 0, .7);
`;
const VerticallyCenter = styled.div`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`;
const fadeIn = keyframes`
from {
transform: scale(.25);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
`;
const fadeOut = keyframes`
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(.25);
opacity: 0;
}
`;
const Fade = styled.div`
display: inline-block;
visibility: ${props => (props.out ? 'hidden' : 'visible')};
animation: ${props => (props.out ? fadeOut : fadeIn)} ${props => (props.timeout / 1000).toFixed(2)}s linear;
transition: visibility ${props => (props.timeout / 1000).toFixed(2)}s linear;
`;
Fade.propTypes = {
out: PropTypes.bool,
timeout: PropTypes.number
};
Fade.defaultProps = {
out: false,
timeout: 150
};
Then you can nest components in the following way:
<Portal>
<Overlay>
<VerticallyCenter>
<Modal>
Your modal content goes here
</Modal>
</VeticallyCenter>
</Overlay>
</Portal>
<Portal>
<Overlay>
<VerticallyCenter>
<Fade timeout={150}>
<Modal>
Your modal content goes here
</Modal>
</Fade>
</VeticallyCenter>
</Overlay>
</Portal>
<LegacyPortal
node={window.top.document && window.top.document.querySelector('#modal-container')}
>
<Overlay>
<Fade timeout={150}>
<Modal>
Your modal content goes here
</Modal>
</Fade>
</Overlay>
</LegacyPortal>
persistStyles = () => {
const parent = window.top;
if (parent === window) {
return;
}
const parentDocument = parent.document;
const parentHead = parentDocument.getElementsByTagName('head')[0];
const parentStyles = Array.prototype.slice.call(parentDocument.getElementsByTagName('style') || []);
parentStyles.forEach(style => {
if (style.getAttribute('data-cloned')) {
style.parentNode.removeChild(style);
}
});
const now = Date.now();
const styles = document.getElementsByTagName('style');
for (let i = 0; i < styles.length; ++i) {
const style = styles[i].cloneNode(true);
style.setAttribute('data-cloned', true);
style.setAttribute('data-ctime', now);
parentHead.appendChild(style);
}
};
const target = document.head;
const config = {
attributes: true,
attributeOldValue: false,
characterData: true,
characterDataOldValue: false,
childList: true,
subtree: true
};
this.observer = new MutationObserver(mutations => {
this.persistStyles();
});
this.observer.observe(target, config);
See a complete example at https://github.com/trendmicro-frontend/react-portal/blob/master/examples/iframe.jsx
Name | Type | Default | Description |
---|---|---|---|
node | DOM node | document.body | A root DOM node to render a React element. |
Name | Type | Default | Description |
---|---|---|---|
node | DOM node | document.body | A root DOM node to render a React element. |
MIT