diff --git a/README.md b/README.md
index a6ac5f2..7cfb847 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,8 @@ React-portal
## Features
-- transports its child into a new React component and appends it to the **document.body** (creates a new independent React tree)
+- transports its child into a new React component and appends it to specified DOM element (**document.body** by default)
+- creates a new independent React tree
- can be opened by the prop **isOpened**
- can be opened after a click on an element that you pass through the prop **openByClickOn** (and then it takes care of the open/close state)
- doesn't leave any mess in DOM after closing
@@ -37,6 +38,7 @@ npm install react react-dom react-portal --save
```
## Usage
+
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
@@ -92,6 +94,9 @@ with `onClick` handler that triggers portal opening. **How to close the portal t
### Optional
+#### target: HTMLElement
+The element you want portal to be appended to.
+
#### closeOnEsc: bool
If true, the portal can be closed by the key ESC.
diff --git a/lib/portal.js b/lib/portal.js
index fcfb7cc..9f44981 100644
--- a/lib/portal.js
+++ b/lib/portal.js
@@ -90,7 +90,7 @@ export default class Portal extends React.Component {
const resetPortalState = () => {
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
- document.body.removeChild(this.node);
+ (this.props.target || document.body).removeChild(this.node);
}
this.portal = null;
this.node = null;
@@ -145,7 +145,7 @@ export default class Portal extends React.Component {
this.node = document.createElement('div');
// apply CSS before the node is added to the DOM to avoid needless reflows
this.applyClassNameAndStyle(props);
- document.body.appendChild(this.node);
+ (props.target || document.body).appendChild(this.node);
} else {
// update CSS when new props arrive
this.applyClassNameAndStyle(props);
@@ -177,6 +177,7 @@ Portal.propTypes = {
className: React.PropTypes.string,
style: React.PropTypes.object,
children: React.PropTypes.element.isRequired,
+ target: React.PropTypes.instanceOf(window.HTMLElement),
openByClickOn: React.PropTypes.element,
closeOnEsc: React.PropTypes.bool,
closeOnOutsideClick: React.PropTypes.bool,
diff --git a/test/portal_spec.js b/test/portal_spec.js
index d634eca..2237438 100644
--- a/test/portal_spec.js
+++ b/test/portal_spec.js
@@ -29,13 +29,6 @@ describe('react-portal', () => {
/*eslint-enable */
});
- it('should append portal with children to the document.body', () => {
- const wrapper = mount(Hi
);
- assert.equal(wrapper.instance().node.firstElementChild.tagName, 'P');
- assert.equal(document.body.lastElementChild, wrapper.instance().node);
- assert.equal(document.body.childElementCount, 1);
- });
-
it('should open when this.openPortal() is called (used to programmatically open portal)', () => {
const wrapper = mount(Hi
);
assert.equal(document.body.childElementCount, 0);
@@ -231,4 +224,39 @@ describe('react-portal', () => {
assert.equal(document.body.childElementCount, 0);
});
});
+
+ describe('target', () => {
+ context('when target is not set', () => {
+ it('should append portal with children to the document.body', () => {
+ const wrapper = mount(Hi
);
+ assert.equal(wrapper.instance().node.firstElementChild.tagName, 'P');
+ assert.equal(document.body.lastElementChild, wrapper.instance().node);
+ assert.equal(document.body.childElementCount, 1);
+ });
+ });
+
+ context('when target is set', () => {
+ context('when layer passed to component as a prop', () => {
+ it('should append portal with children to the target', () => {
+ const modalLayer = document.createElement('div');
+ document.body.appendChild(modalLayer);
+
+ const wrapper = mount(Hi
);
+ assert.equal(modalLayer.getElementsByTagName('p')[0].textContent, 'Hi');
+ assert.equal(modalLayer.lastElementChild, wrapper.instance().node);
+ assert.equal(modalLayer.childElementCount, 1);
+ });
+
+ it('should remove portal from the target when isOpened set to false', () => {
+ const modalLayer = document.createElement('div');
+ document.body.appendChild(modalLayer);
+
+ const wrapper = mount(Hi
);
+ assert.equal(modalLayer.getElementsByTagName('p')[0].textContent, 'Hi');
+ wrapper.setProps({ isOpened: false });
+ assert(!modalLayer.getElementsByTagName('p')[0]);
+ });
+ });
+ });
+ });
});