diff --git a/example/example.js b/example/example.js
index 3b980bd..7f73fce 100644
--- a/example/example.js
+++ b/example/example.js
@@ -40,7 +40,6 @@ class App extends React.Component {
this.setState({popupVisible: false});
}
onPopupLayout (currentLayout) {
-
return [
(window.innerWidth - CUSTOM_DIMENSION) / 2,
(window.innerHeight - CUSTOM_DIMENSION) / 2,
diff --git a/package.json b/package.json
index 639036d..78395ae 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@bthlabs/react-custom-popup",
- "version": "1.0.2",
+ "version": "1.0.3",
"private": false,
"description": "React component for simply building custom popups and modals.",
"main": "./lib/react-custom-popup.js",
diff --git a/src/Popup.js b/src/Popup.js
index e46289c..2a15090 100644
--- a/src/Popup.js
+++ b/src/Popup.js
@@ -25,61 +25,80 @@ import PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
-export const Popup = (props) => {
- const className = ClassName('bthlabs-react-custom-popup', props.className, {
- 'bthlabs-rcp-visible': props.visible
- });
+export class Popup extends React.Component {
+ constructor (props) {
+ super(props);
- let layout = [0, 0];
- const body = document.body;
- const innerStyle = {};
- if (props.visible && props.anchor && props.anchor.current) {
- const box = props.anchor.current.getBoundingClientRect();
- const document_ = document.documentElement;
-
- const scrollTop = (
- window.pageYOffset || document_.scrollTop || body.scrollTop
- );
- const scrollLeft = (
- window.pageXOffset || document_.scrollLeft || body.scrollLeft
- );
-
- const clientTop = document_.clientTop || body.clientTop || 0;
- const clientLeft = document_.clientLeft || body.clientLeft || 0;
-
- layout[0] = box.left + scrollLeft - clientLeft;
- layout[1] = box.top + scrollTop - clientTop;
+ this.state = {
+ layout: [0, 0]
+ };
}
-
- if (props.visible) {
- if (typeof props.onLayout === 'function') {
- layout = props.onLayout(layout);
- }
-
- innerStyle.left = `${layout[0]}px`;
- innerStyle.top = `${layout[1]}px`;
-
- if (layout[2]) {
- innerStyle.width = `${layout[2]}px`;
- }
-
- if (layout[3]) {
- innerStyle.height = `${layout[3]}px`;
+ componentDidUpdate (prevProps, prevState) {
+ if (prevProps.visible !== this.props.visible && this.props.visible) {
+ this.layout();
}
}
+ layout () {
+ let layout = [0, 0];
+ const body = document.body;
+ if (this.props.visible && this.props.anchor && this.props.anchor.current) {
+ const box = this.props.anchor.current.getBoundingClientRect();
+ const document_ = document.documentElement;
- return ReactDOM.createPortal(
-
- {!props.hideOverlay &&
-
+ const scrollTop = (
+ window.pageYOffset || document_.scrollTop || body.scrollTop
+ );
+ const scrollLeft = (
+ window.pageXOffset || document_.scrollLeft || body.scrollLeft
+ );
+
+ const clientTop = document_.clientTop || body.clientTop || 0;
+ const clientLeft = document_.clientLeft || body.clientLeft || 0;
+
+ layout[0] = box.left + scrollLeft - clientLeft;
+ layout[1] = box.top + scrollTop - clientTop;
+ }
+
+ if (this.props.visible) {
+ if (typeof this.props.onLayout === 'function') {
+ layout = this.props.onLayout(layout);
}
-
- {props.children}
-
-
,
- props.domNode || body
- );
-};
+
+ this.setState({layout: layout});
+ }
+ }
+ render () {
+ const className = ClassName('bthlabs-react-custom-popup', this.props.className, {
+ 'bthlabs-rcp-visible': this.props.visible
+ });
+
+ const innerStyle = {};
+ if (this.props.visible) {
+ innerStyle.left = `${this.state.layout[0]}px`;
+ innerStyle.top = `${this.state.layout[1]}px`;
+
+ if (this.state.layout[2]) {
+ innerStyle.width = `${this.state.layout[2]}px`;
+ }
+
+ if (this.state.layout[3]) {
+ innerStyle.height = `${this.state.layout[3]}px`;
+ }
+ }
+
+ return ReactDOM.createPortal(
+
+ {!this.props.hideOverlay &&
+
+ }
+
+ {this.props.children}
+
+
,
+ this.props.domNode || document.body
+ );
+ }
+}
Popup.propTypes = {
anchor: PropTypes.object,
diff --git a/tests/Popup.spec.js b/tests/Popup.spec.js
index fa3f4a0..6fb9928 100644
--- a/tests/Popup.spec.js
+++ b/tests/Popup.spec.js
@@ -48,144 +48,208 @@ describe('Popup', () => {
document.body.removeChild(mountNode);
});
- it('should render with a custom class', () => {
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
+ describe('componentDidUpdate', () => {
+ it('should not layout if visibility did not change', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
+ spyOn(component.instance(), 'layout');
- const popup = component.find('.bthlabs-react-custom-popup');
- expect(popup.hasClass('spam')).toBe(true);
+ component.instance().componentDidUpdate({visible: false});
+ expect(component.instance().layout).not.toHaveBeenCalled();
+ });
+
+ it('should not layout if it became hidden', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
+ spyOn(component.instance(), 'layout');
+
+ component.instance().componentDidUpdate({visible: true});
+ expect(component.instance().layout).not.toHaveBeenCalled();
+ });
+
+ it('should not layout if it became visible', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
+ spyOn(component.instance(), 'layout');
+
+ component.instance().componentDidUpdate({visible: false});
+ expect(component.instance().layout).toHaveBeenCalled();
+ });
});
- it('should render as invisible', () => {
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
+ describe('layout', () => {
+ it('should apply default layout if rendering without anchor', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
+ component.instance().layout();
- const popup = component.find('.bthlabs-react-custom-popup');
- expect(popup.hasClass('bthlabs-rcp-visible')).toBe(false);
+ expect(component.state('layout')).toEqual([0, 0]);
+ });
+
+ it('should not call onLayout when invisible', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
+ component.instance().layout();
+
+ expect(onLayout).not.toHaveBeenCalled();
+ });
+
+ it('should layout according to the anchor', () => {
+ const component = mount(, {attachTo: mountNode});
+ component.setState({popupVisible: true});
+
+ const popup = component.find(Popup);
+ expect(popup.instance().state.layout).not.toEqual([0, 0]);
+
+ component.unmount();
+ });
+
+ it('should call onLayout to allow for customization of the inner layer layout', () => {
+ onLayout = onLayout.and.returnValue([20, 20]);
+
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
+ component.instance().layout();
+
+ expect(onLayout).toHaveBeenCalledWith([0, 0]);
+ expect(component.state('layout')).toEqual([20, 20]);
+ });
+
+ it('should allow the onLayout callback to specify height and width', () => {
+ onLayout = onLayout.and.returnValue([20, 20, 100, 100]);
+
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
+ component.instance().layout();
+
+ expect(component.state('layout')).toEqual([20, 20, 100, 100]);
+ });
});
- it('should render as visible', () => {
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
+ describe('render', () => {
+ it('should render with a custom class', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
- const popup = component.find('.bthlabs-react-custom-popup');
- expect(popup.hasClass('bthlabs-rcp-visible')).toBe(true);
- });
+ const popup = component.find('.bthlabs-react-custom-popup');
+ expect(popup.hasClass('spam')).toBe(true);
+ });
- it('should allow hiding the overlay', () => {
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
+ it('should render as invisible', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
- const overlay = component.find('.bthlabs-rcp-overlay');
- expect(overlay.exists()).toBe(false);
- });
+ const popup = component.find('.bthlabs-react-custom-popup');
+ expect(popup.hasClass('bthlabs-rcp-visible')).toBe(false);
+ });
- it('should configure and render the overlay', () => {
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
+ it('should render as visible', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
- const overlay = component.find('.bthlabs-rcp-overlay');
- expect(overlay.exists()).toBe(true);
- expect(overlay.prop('onClick')).toBe(onOverlayClick);
- });
+ const popup = component.find('.bthlabs-react-custom-popup');
+ expect(popup.hasClass('bthlabs-rcp-visible')).toBe(true);
+ });
- it('should not layout the inner layer when invisible', () => {
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
+ it('should allow hiding the overlay', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
- const inner = component.find('.bthlabs-rcp-inner');
- expect(inner.prop('style')).toEqual({});
- });
+ const overlay = component.find('.bthlabs-rcp-overlay');
+ expect(overlay.exists()).toBe(false);
+ });
- it('should not call onLayout when invisible', () => {
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
+ it('should configure and render the overlay', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
- const inner = component.find('.bthlabs-rcp-inner');
- expect(onLayout).not.toHaveBeenCalled();
- });
+ const overlay = component.find('.bthlabs-rcp-overlay');
+ expect(overlay.exists()).toBe(true);
+ expect(overlay.prop('onClick')).toBe(onOverlayClick);
+ });
- it('should apply default layout to the inner layer if rendering without anchor', () => {
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
+ it('should not apply layout style on the inner layer when invisible', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
- const inner = component.find('.bthlabs-rcp-inner');
- expect(inner.prop('style')).toEqual({left: '0px', top: '0px'});
- });
+ const inner = component.find('.bthlabs-rcp-inner');
+ expect(inner.prop('style')).toEqual({});
+ });
- it('should layout the inner layer according to the anchor', () => {
- const component = mount(, {attachTo: mountNode});
- component.setState({popupVisible: true});
+ it('should apply layout style on the inner layer when visible', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
+ component.setState({layout: [20, 20]});
- const popup = component.find(Popup);
- const inner = popup.find('.bthlabs-rcp-inner');
- expect(inner.prop('style').left).not.toEqual('0px');
- expect(inner.prop('style').top).not.toEqual('0px');
+ const inner = component.find('.bthlabs-rcp-inner');
+ expect(inner.prop('style').left).toEqual('20px');
+ expect(inner.prop('style').top).toEqual('20px');
+ });
- component.unmount();
- });
+ it('should set the inner layer height and width if onLayout specified them', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
+ component.setState({layout: [20, 20, 100, 100]});
- it('should call onLayout to allow for customization of the inner layer layout', () => {
- onLayout = onLayout.and.returnValue([20, 20]);
+ const inner = component.find('.bthlabs-rcp-inner');
+ expect(inner.prop('style').height).toEqual('100px');
+ expect(inner.prop('style').width).toEqual('100px');
+ });
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
+ it('should render the children in the inner layer', () => {
+ const component = shallow(
+
+ HERE POPUP CONTENT BE
+
+ );
- const inner = component.find('.bthlabs-rcp-inner');
- expect(onLayout).toHaveBeenCalledWith([0, 0]);
- expect(inner.prop('style')).toEqual({left: '20px', top: '20px'});
- });
-
- it('should set the inner layour height and width if onLayout specified them', () => {
- onLayout = onLayout.and.returnValue([20, 20, 100, 100]);
-
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
-
- const inner = component.find('.bthlabs-rcp-inner');
- expect(onLayout).toHaveBeenCalledWith([0, 0]);
- expect(inner.prop('style').height).toEqual('100px');
- expect(inner.prop('style').width).toEqual('100px');
- });
-
- it('should render the children in the inner layer', () => {
- const component = shallow(
-
- HERE POPUP CONTENT BE
-
- );
-
- const inner = component.find('.bthlabs-rcp-inner');
- expect(inner.contains(HERE POPUP CONTENT BE)).toBe(true);
+ const inner = component.find('.bthlabs-rcp-inner');
+ expect(inner.contains(HERE POPUP CONTENT BE)).toBe(true);
+ });
});
});