├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── __tests__ ├── .gitkeep ├── helpers │ ├── setup-browser-env.js │ └── setup-enzyme.js └── popup.test.js ├── dist ├── ActionButton.react.js ├── Bem.js ├── ButtonsSpace.react.js ├── Constants.js ├── Footer.react.js ├── Header.react.js ├── Popup.react.js ├── Store.js └── index.js ├── example ├── index.jsx ├── template.html └── webpack.config.js ├── package.json ├── src ├── ActionButton.react.js ├── Bem.js ├── ButtonsSpace.react.js ├── Constants.js ├── Footer.react.js ├── Header.react.js ├── Popup.react.js ├── Store.js └── index.js ├── style.css ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | ["@babel/preset-react", { 5 | "runtime": "automatic" 6 | }] 7 | ], 8 | "plugins": [ 9 | "@babel/plugin-proposal-class-properties", 10 | "@babel/plugin-proposal-object-rest-spread" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true, 6 | "node": true 7 | }, 8 | "rules": { 9 | "indent": ["error", 4], 10 | "react/jsx-indent": ["error", 4], 11 | "react/jsx-indent-props": ["error", 4], 12 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 13 | "import/no-unresolved": [2, {"ignore": ["src/"]}] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | yarn-error.log 5 | .vscode 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Minutemailer AB 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | React Popup 3 |

4 | 5 | 6 |

7 | 8 | npm 9 | 10 | 11 | npm 12 | 13 | 14 | npm 15 | 16 |

17 | 18 |

19 | Simple and powerful react popup component.
Part of Minutemailer.com - Marketing Simplified 20 |

21 |

 

22 | 23 | ## Breaking changes in 0.9.x 24 | 25 | The popup and overlay is now two separate layers to allow more customization. See [demo css](https://github.com/minutemailer/react-popup/blob/gh-pages/popup.example.css) for styling example. 26 | 27 | ## Global API approach 28 | 29 | The idea behind `react-popup` is to use it as a drop-in replacement for the native `window.alert`. With the similarity of only displaying one popup at a time. This is why we use a global API to control the component instead of rendering it inside components. Maybe this is an anti-pattern, maybe it's not. Feel free to discuss it by opening an issue if one doesn't already exist. 30 | 31 | ## Install 32 | 33 | Install it with npm (or yarn) (`npm install react-popup --save`). The component is API driven and means that you only render it once, on a global level. Here's a simple example: 34 | 35 | ```jsx 36 | import React from 'react'; 37 | import ReactDom from 'react-dom'; 38 | import Popup from 'react-popup'; 39 | 40 | ReactDom.render( 41 | , 42 | document.getElementById('popupContainer') 43 | ); 44 | 45 | Popup.alert('Hello'); 46 | ``` 47 | 48 | ## Documentation 49 | 50 | Documentation and demo can be found here: http://minutemailer.github.io/react-popup/ 51 | 52 |

 

53 |

54 | Minutemailer 55 |

56 | -------------------------------------------------------------------------------- /__tests__/.gitkeep: -------------------------------------------------------------------------------- 1 | .gitkeep -------------------------------------------------------------------------------- /__tests__/helpers/setup-browser-env.js: -------------------------------------------------------------------------------- 1 | import browserEnv from 'browser-env'; 2 | browserEnv(['window', 'document', 'navigator']); 3 | 4 | global.requestAnimationFrame = (callback) => { 5 | setTimeout(callback, 0); 6 | }; 7 | -------------------------------------------------------------------------------- /__tests__/helpers/setup-enzyme.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /__tests__/popup.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import React from 'react'; 3 | import { shallow } from 'enzyme'; 4 | import Popup from '../src/Popup.react'; 5 | 6 | test('Initialization works', (t) => { 7 | const component = shallow(); 8 | 9 | t.true(component.hasClass('popup')); 10 | t.is(component.find('.popup__overlay').length, 1); 11 | }); 12 | 13 | test('Display and hide popup', (t) => { 14 | const component = shallow(); 15 | 16 | Popup.create({}); 17 | component.update(); 18 | 19 | t.is(component.find('.popup__box').length, 1); 20 | 21 | Popup.close(); 22 | component.update(); 23 | 24 | t.is(component.find('.popup__box').length, 0); 25 | }); 26 | -------------------------------------------------------------------------------- /dist/ActionButton.react.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports["default"] = void 0; 9 | 10 | var _react = _interopRequireDefault(require("react")); 11 | 12 | var _propTypes = _interopRequireDefault(require("prop-types")); 13 | 14 | var _jsxRuntime = require("react/jsx-runtime"); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 17 | 18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 19 | 20 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 21 | 22 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 23 | 24 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } 25 | 26 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 27 | 28 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 29 | 30 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } 31 | 32 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 33 | 34 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 35 | 36 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 37 | 38 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 39 | 40 | var PopupAction = /*#__PURE__*/function (_React$Component) { 41 | _inherits(PopupAction, _React$Component); 42 | 43 | var _super = _createSuper(PopupAction); 44 | 45 | function PopupAction() { 46 | _classCallCheck(this, PopupAction); 47 | 48 | return _super.apply(this, arguments); 49 | } 50 | 51 | _createClass(PopupAction, [{ 52 | key: "handleClick", 53 | value: function handleClick() { 54 | return this.props.onClick(); 55 | } 56 | }, { 57 | key: "render", 58 | value: function render() { 59 | var _this = this; 60 | 61 | var className = this.props.className; 62 | 63 | if (this.props.url && this.props.url !== '#') { 64 | return /*#__PURE__*/(0, _jsxRuntime.jsx)("a", { 65 | href: this.props.url, 66 | target: "_blank", 67 | className: className, 68 | children: this.props.children 69 | }); 70 | } 71 | 72 | return /*#__PURE__*/(0, _jsxRuntime.jsx)("button", { 73 | onClick: function onClick() { 74 | return _this.handleClick(); 75 | }, 76 | className: className, 77 | children: this.props.children 78 | }); 79 | } 80 | }]); 81 | 82 | return PopupAction; 83 | }(_react["default"].Component); 84 | 85 | _defineProperty(PopupAction, "defaultProps", { 86 | onClick: function onClick() {}, 87 | className: 'btn', 88 | url: null 89 | }); 90 | 91 | PopupAction.propTypes = { 92 | onClick: _propTypes["default"].func, 93 | className: _propTypes["default"].string, 94 | children: _propTypes["default"].node.isRequired, 95 | url: _propTypes["default"].string 96 | }; 97 | var _default = PopupAction; 98 | exports["default"] = _default; -------------------------------------------------------------------------------- /dist/Bem.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.modifier = exports.element = void 0; 7 | 8 | var element = function element(el, base) { 9 | return "".concat(base, "__").concat(el); 10 | }; 11 | 12 | exports.element = element; 13 | 14 | var modifier = function modifier(modifiers, base) { 15 | if (!modifiers) { 16 | return null; 17 | } 18 | 19 | var finalClass = []; 20 | var classNames = modifiers.split(' '); 21 | classNames.forEach(function (singleClass) { 22 | finalClass.push("".concat(base, "--").concat(singleClass)); 23 | }); 24 | return finalClass.join(' '); 25 | }; 26 | 27 | exports.modifier = modifier; -------------------------------------------------------------------------------- /dist/ButtonsSpace.react.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports["default"] = void 0; 9 | 10 | var _react = _interopRequireDefault(require("react")); 11 | 12 | var _propTypes = _interopRequireDefault(require("prop-types")); 13 | 14 | var _ActionButton = _interopRequireDefault(require("./ActionButton.react")); 15 | 16 | var _Bem = require("./Bem"); 17 | 18 | var _jsxRuntime = require("react/jsx-runtime"); 19 | 20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 21 | 22 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 23 | 24 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 25 | 26 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 27 | 28 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } 29 | 30 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 31 | 32 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 33 | 34 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } 35 | 36 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 37 | 38 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 39 | 40 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 41 | 42 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 43 | 44 | var PopupFooterButtons = /*#__PURE__*/function (_React$Component) { 45 | _inherits(PopupFooterButtons, _React$Component); 46 | 47 | var _super = _createSuper(PopupFooterButtons); 48 | 49 | function PopupFooterButtons() { 50 | _classCallCheck(this, PopupFooterButtons); 51 | 52 | return _super.apply(this, arguments); 53 | } 54 | 55 | _createClass(PopupFooterButtons, [{ 56 | key: "onOk", 57 | value: function onOk() { 58 | return this.props.onOk(); 59 | } 60 | }, { 61 | key: "onClose", 62 | value: function onClose() { 63 | return this.props.onClose(); 64 | } 65 | }, { 66 | key: "buttonClick", 67 | value: function buttonClick(action) { 68 | return this.props.buttonClick(action); 69 | } 70 | }, { 71 | key: "render", 72 | value: function render() { 73 | var _this = this; 74 | 75 | if (!this.props.buttons) { 76 | return null; 77 | } 78 | 79 | var btns = []; 80 | this.props.buttons.forEach(function (btn, i) { 81 | var url = btn.url ? btn.url : null; 82 | var key = i; 83 | 84 | if (typeof btn === 'string') { 85 | if (btn === 'ok') { 86 | btns.push( /*#__PURE__*/(0, _jsxRuntime.jsx)(_ActionButton["default"], { 87 | className: "".concat(_this.props.btnClass, " ").concat(_this.props.btnClass, "--ok"), 88 | onClick: function onClick() { 89 | return _this.onOk(); 90 | }, 91 | children: _this.props.defaultOk 92 | }, key)); 93 | } else if (btn === 'cancel') { 94 | btns.push( /*#__PURE__*/(0, _jsxRuntime.jsx)(_ActionButton["default"], { 95 | className: "".concat(_this.props.btnClass, " ").concat(_this.props.btnClass, "--cancel"), 96 | onClick: function onClick() { 97 | return _this.onClose(); 98 | }, 99 | children: _this.props.defaultCancel 100 | }, key)); 101 | } 102 | } else if ( /*#__PURE__*/_react["default"].isValidElement(btn)) { 103 | btns.push(btn); 104 | } else { 105 | var className = "".concat(_this.props.btnClass, " ").concat((0, _Bem.modifier)(btn.className, _this.props.btnClass)); 106 | var btnComponent = /*#__PURE__*/(0, _jsxRuntime.jsx)(_ActionButton["default"], { 107 | className: className, 108 | url: url, 109 | onClick: function onClick() { 110 | return _this.buttonClick(btn.action); 111 | }, 112 | children: btn.text 113 | }, key); 114 | btns.push(btnComponent); 115 | } 116 | }); 117 | return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", { 118 | className: this.props.className, 119 | children: btns 120 | }); 121 | } 122 | }]); 123 | 124 | return PopupFooterButtons; 125 | }(_react["default"].Component); 126 | 127 | exports["default"] = PopupFooterButtons; 128 | 129 | _defineProperty(PopupFooterButtons, "defaultProps", { 130 | buttons: null, 131 | className: null, 132 | onOk: function onOk() {}, 133 | onClose: function onClose() {}, 134 | buttonClick: function buttonClick() {}, 135 | btnClass: null, 136 | defaultOk: null, 137 | defaultCancel: null 138 | }); 139 | 140 | PopupFooterButtons.propTypes = { 141 | buttons: _propTypes["default"].arrayOf(_propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].object])), 142 | className: _propTypes["default"].string, 143 | onOk: _propTypes["default"].func, 144 | onClose: _propTypes["default"].func, 145 | buttonClick: _propTypes["default"].func, 146 | btnClass: _propTypes["default"].string, 147 | defaultOk: _propTypes["default"].string, 148 | defaultCancel: _propTypes["default"].string 149 | }; -------------------------------------------------------------------------------- /dist/Constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | var _default = { 8 | SHOW: 'SHOW', 9 | CLOSE: 'CLOSE', 10 | REFRESH: 'REFRESH' 11 | }; 12 | exports["default"] = _default; -------------------------------------------------------------------------------- /dist/Footer.react.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _propTypes = _interopRequireDefault(require("prop-types")); 9 | 10 | var _ButtonsSpace = _interopRequireDefault(require("./ButtonsSpace.react")); 11 | 12 | var _jsxRuntime = require("react/jsx-runtime"); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 15 | 16 | var PopupFooter = function PopupFooter(props) { 17 | if (!props.buttons) { 18 | return null; 19 | } 20 | 21 | return /*#__PURE__*/(0, _jsxRuntime.jsxs)("footer", { 22 | className: props.className, 23 | children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_ButtonsSpace["default"], { 24 | buttonClick: props.buttonClick, 25 | onOk: props.onOk, 26 | onClose: props.onClose, 27 | className: "".concat(props.className, "__left-space"), 28 | btnClass: props.btnClass, 29 | defaultOk: props.defaultOk, 30 | defaultCancel: props.defaultCancel, 31 | buttons: props.buttons.left 32 | }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_ButtonsSpace["default"], { 33 | buttonClick: props.buttonClick, 34 | onOk: props.onOk, 35 | onClose: props.onClose, 36 | className: "".concat(props.className, "__right-space"), 37 | btnClass: props.btnClass, 38 | defaultOk: props.defaultOk, 39 | defaultCancel: props.defaultCancel, 40 | buttons: props.buttons.right 41 | })] 42 | }); 43 | }; 44 | 45 | PopupFooter.propTypes = { 46 | buttons: _propTypes["default"].shape({ 47 | left: _propTypes["default"].arrayOf(_propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].object])), 48 | right: _propTypes["default"].arrayOf(_propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].object])) 49 | }), 50 | className: _propTypes["default"].string, 51 | btnClass: _propTypes["default"].string, 52 | onOk: _propTypes["default"].func, 53 | onClose: _propTypes["default"].func, 54 | buttonClick: _propTypes["default"].func, 55 | defaultOk: _propTypes["default"].string, 56 | defaultCancel: _propTypes["default"].string 57 | }; 58 | PopupFooter.defaultProps = { 59 | buttons: null, 60 | className: null, 61 | btnClass: null, 62 | defaultOk: null, 63 | defaultCancel: null, 64 | buttonClick: function buttonClick() {}, 65 | onOk: function onOk() {}, 66 | onClose: function onClose() {} 67 | }; 68 | var _default = PopupFooter; 69 | exports["default"] = _default; -------------------------------------------------------------------------------- /dist/Header.react.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _propTypes = _interopRequireDefault(require("prop-types")); 9 | 10 | var _jsxRuntime = require("react/jsx-runtime"); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 13 | 14 | var PopupHeader = function PopupHeader(props) { 15 | if (!props.title) { 16 | return null; 17 | } 18 | 19 | return /*#__PURE__*/(0, _jsxRuntime.jsx)("header", { 20 | className: props.className, 21 | children: /*#__PURE__*/(0, _jsxRuntime.jsx)("h1", { 22 | className: "".concat(props.className, "__title"), 23 | children: props.title 24 | }) 25 | }); 26 | }; 27 | 28 | PopupHeader.defaultProps = { 29 | title: null, 30 | className: null 31 | }; 32 | PopupHeader.propTypes = { 33 | title: _propTypes["default"].string, 34 | className: _propTypes["default"].string 35 | }; 36 | var _default = PopupHeader; 37 | exports["default"] = _default; -------------------------------------------------------------------------------- /dist/Popup.react.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports["default"] = void 0; 9 | 10 | var _react = _interopRequireDefault(require("react")); 11 | 12 | var _propTypes = _interopRequireDefault(require("prop-types")); 13 | 14 | var _keymaster = _interopRequireDefault(require("keymaster")); 15 | 16 | var _Store = _interopRequireDefault(require("./Store")); 17 | 18 | var _Header = _interopRequireDefault(require("./Header.react")); 19 | 20 | var _Footer = _interopRequireDefault(require("./Footer.react")); 21 | 22 | var _Constants = _interopRequireDefault(require("./Constants")); 23 | 24 | var _Bem = require("./Bem"); 25 | 26 | var _jsxRuntime = require("react/jsx-runtime"); 27 | 28 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 29 | 30 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 31 | 32 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 33 | 34 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 35 | 36 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } 37 | 38 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 39 | 40 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 41 | 42 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } 43 | 44 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 45 | 46 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 47 | 48 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 49 | 50 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 51 | 52 | var defaultKeyFilter = _keymaster["default"].filter; 53 | var Store = new _Store["default"](); 54 | 55 | var handleClose = function handleClose() { 56 | _keymaster["default"].deleteScope('react-popup'); 57 | 58 | _keymaster["default"].filter = defaultKeyFilter; 59 | Store.close(); 60 | }; 61 | 62 | var initialState = { 63 | id: null, 64 | title: null, 65 | buttons: null, 66 | content: null, 67 | visible: false, 68 | className: null, 69 | noOverlay: false, 70 | position: false, 71 | closeOnOutsideClick: true, 72 | onClose: function onClose() {}, 73 | onShow: function onShow() {} 74 | }; 75 | 76 | var Popup = /*#__PURE__*/function (_React$Component) { 77 | _inherits(Popup, _React$Component); 78 | 79 | var _super = _createSuper(Popup); 80 | 81 | function Popup(props) { 82 | var _this; 83 | 84 | _classCallCheck(this, Popup); 85 | 86 | _this = _super.call(this, props); 87 | initialState.closeOnOutsideClick = _this.props.closeOnOutsideClick; 88 | _this.state = initialState; 89 | _this.bound = { 90 | onShow: _this.onShow.bind(_assertThisInitialized(_this)), 91 | onClose: _this.onClose.bind(_assertThisInitialized(_this)), 92 | onRefresh: _this.onRefresh.bind(_assertThisInitialized(_this)), 93 | containerClick: _this.containerClick.bind(_assertThisInitialized(_this)), 94 | handleButtonClick: _this.handleButtonClick.bind(_assertThisInitialized(_this)) 95 | }; 96 | _this.boxRef = null; 97 | _this.defaultKeyBindings = { 98 | ok: _this.props.defaultOkKey, 99 | cancel: _this.props.defaultCancelKey 100 | }; 101 | return _this; 102 | } 103 | 104 | _createClass(Popup, [{ 105 | key: "componentDidMount", 106 | value: function componentDidMount() { 107 | Store.on(_Constants["default"].SHOW, this.bound.onShow); 108 | Store.on(_Constants["default"].CLOSE, this.bound.onClose); 109 | Store.on(_Constants["default"].REFRESH, this.bound.onRefresh); 110 | } 111 | }, { 112 | key: "componentDidUpdate", 113 | value: function componentDidUpdate() { 114 | if (this.boxRef) { 115 | this.boxRef.focus(); 116 | } 117 | 118 | this.setPosition(this.state.position); 119 | } 120 | }, { 121 | key: "componentWillUnmount", 122 | value: function componentWillUnmount() { 123 | Store.removeListener(_Constants["default"].SHOW, this.bound.onShow); 124 | Store.removeListener(_Constants["default"].CLOSE, this.bound.onClose); 125 | Store.removeListener(_Constants["default"].REFRESH, this.bound.onRefresh); 126 | 127 | _keymaster["default"].deleteScope('react-popup'); 128 | 129 | _keymaster["default"].filter = defaultKeyFilter; 130 | } 131 | /** 132 | * Refresh popup position 133 | * @param position 134 | * @private 135 | */ 136 | 137 | }, { 138 | key: "onRefresh", 139 | value: function onRefresh(position) { 140 | this.setPosition(position); 141 | } 142 | /** 143 | * On popup close 144 | * @private 145 | */ 146 | 147 | }, { 148 | key: "onClose", 149 | value: function onClose() { 150 | _keymaster["default"].deleteScope('react-popup'); 151 | 152 | _keymaster["default"].filter = defaultKeyFilter; 153 | this.state.onClose(this.state.id, this.state.title); 154 | this.setState(initialState); 155 | } 156 | /** 157 | * On popup show 158 | * @private 159 | */ 160 | 161 | }, { 162 | key: "onShow", 163 | value: function onShow(id) { 164 | var _this2 = this; 165 | 166 | _keymaster["default"].deleteScope('react-popup'); 167 | 168 | _keymaster["default"].filter = function () { 169 | return true; 170 | }; 171 | 172 | var popup = Store.activePopup(); 173 | 174 | if (popup.buttons && !Object.prototype.hasOwnProperty.call(popup.buttons, 'left')) { 175 | popup.buttons.left = []; 176 | } 177 | 178 | if (popup.buttons && !Object.prototype.hasOwnProperty.call(popup.buttons, 'right')) { 179 | popup.buttons.right = []; 180 | } 181 | 182 | this.setState({ 183 | id: id, 184 | title: popup.title, 185 | content: popup.content, 186 | buttons: popup.buttons, 187 | visible: true, 188 | className: popup.className, 189 | noOverlay: popup.noOverlay, 190 | position: popup.position, 191 | closeOnOutsideClick: popup.closeOnOutsideClick, 192 | onClose: popup.onClose, 193 | onShow: popup.onShow 194 | }, function () { 195 | _keymaster["default"].setScope('react-popup'); 196 | 197 | _this2.state.onShow(_this2.state.id, _this2.state.title); 198 | 199 | if (_this2.props.escToClose) { 200 | (0, _keymaster["default"])('esc', 'react-popup', _this2.handleKeyEvent.bind(_this2, 'cancel', _this2.state.id)); 201 | } 202 | 203 | if (_this2.state.buttons) { 204 | if (_this2.state.buttons.left.length) { 205 | _this2.state.buttons.left.forEach(function (button) { 206 | return _this2.bindKeyEvents(button); 207 | }); 208 | } 209 | 210 | if (_this2.state.buttons.right.length) { 211 | _this2.state.buttons.right.forEach(function (button) { 212 | return _this2.bindKeyEvents(button); 213 | }); 214 | } 215 | } 216 | }); 217 | } 218 | }, { 219 | key: "setPosition", 220 | value: function setPosition(position) { 221 | var box = this.boxRef; 222 | var boxPosition = position; 223 | 224 | if (!box) { 225 | return; 226 | } 227 | 228 | if (!boxPosition) { 229 | boxPosition = this.state.position; 230 | } 231 | 232 | if (!boxPosition) { 233 | box.style.opacity = 1; 234 | box.style.top = null; 235 | box.style.left = null; 236 | box.style.margin = null; 237 | return; 238 | } 239 | 240 | if (typeof boxPosition === 'function') { 241 | boxPosition.call(null, box); 242 | return; 243 | } 244 | 245 | box.style.top = "".concat(parseInt(boxPosition.y, 10), "px"); 246 | box.style.left = "".concat(parseInt(boxPosition.x, 10), "px"); 247 | box.style.margin = 0; 248 | box.style.opacity = 1; 249 | } 250 | /** 251 | * Handle container click 252 | * @param e 253 | * @private 254 | */ 255 | 256 | }, { 257 | key: "containerClick", 258 | value: function containerClick() { 259 | if (this.state.closeOnOutsideClick) { 260 | handleClose(); 261 | } 262 | } 263 | }, { 264 | key: "bindKeyEvents", 265 | value: function bindKeyEvents(button) { 266 | var code = null; 267 | 268 | if (typeof button === 'string') { 269 | code = this.defaultKeyBindings[button]; 270 | } else if (Object.prototype.hasOwnProperty.call(button, 'key')) { 271 | code = button.key; 272 | } 273 | 274 | if (this.props.escToClose && code === 'esc') { 275 | return; 276 | } 277 | 278 | if (code) { 279 | (0, _keymaster["default"])(code, 'react-popup', this.handleKeyEvent.bind(this, button, this.state.id)); 280 | } 281 | } 282 | }, { 283 | key: "handleKeyEvent", 284 | value: function handleKeyEvent(button, id, e) { 285 | var excludeTags = ['INPUT', 'TEXTAREA', 'BUTTON']; 286 | 287 | if (this.state.id !== id || button.key === 'enter' && excludeTags.indexOf(e.target.tagName) >= 0) { 288 | return true; 289 | } 290 | 291 | if (typeof button === 'string') { 292 | handleClose(); 293 | } else if (Object.prototype.hasOwnProperty.call(button, 'action')) { 294 | this.handleButtonClick(button.action); 295 | } 296 | 297 | return false; 298 | } 299 | /** 300 | * Handle button clicks 301 | * @param action 302 | * @returns {*} 303 | * @private 304 | */ 305 | 306 | }, { 307 | key: "handleButtonClick", 308 | value: function handleButtonClick(action) { 309 | if (typeof action === 'function') { 310 | return action.call(this, Store); 311 | } 312 | 313 | return null; 314 | } 315 | }, { 316 | key: "className", 317 | value: function className(_className) { 318 | return (0, _Bem.element)(_className, this.props.className); 319 | } 320 | }, { 321 | key: "render", 322 | value: function render() { 323 | var _this3 = this; 324 | 325 | var className = this.props.className; 326 | var box = null; 327 | var overlayStyle = {}; 328 | 329 | if (this.state.visible) { 330 | var closeBtn = null; 331 | className += " ".concat(this.props.className, "--visible"); 332 | 333 | if (this.props.closeBtn) { 334 | closeBtn = /*#__PURE__*/(0, _jsxRuntime.jsx)("button", { 335 | onClick: handleClose, 336 | className: "".concat(this.props.className, "__close"), 337 | children: this.props.closeHtml 338 | }); 339 | } 340 | 341 | var boxClass = this.className('box'); 342 | 343 | if (this.state.className) { 344 | boxClass += " ".concat((0, _Bem.modifier)(this.state.className, boxClass)); 345 | } 346 | 347 | box = /*#__PURE__*/(0, _jsxRuntime.jsxs)("article", { 348 | role: "dialog", 349 | tabIndex: "-1", 350 | ref: function ref(el) { 351 | _this3.boxRef = el; 352 | }, 353 | style: { 354 | opacity: 0, 355 | outline: 'none' 356 | }, 357 | className: boxClass, 358 | children: [closeBtn, /*#__PURE__*/(0, _jsxRuntime.jsx)(_Header["default"], { 359 | title: this.state.title, 360 | className: this.className('box__header') 361 | }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", { 362 | className: this.className('box__body'), 363 | children: this.state.content 364 | }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Footer["default"], { 365 | className: this.className('box__footer'), 366 | btnClass: this.props.btnClass, 367 | buttonClick: this.bound.handleButtonClick, 368 | onClose: handleClose, 369 | onOk: handleClose, 370 | defaultOk: this.props.defaultOk, 371 | defaultCancel: this.props.defaultCancel, 372 | buttons: this.state.buttons 373 | })] 374 | }); 375 | } 376 | 377 | if (this.state.noOverlay) { 378 | overlayStyle.background = 'transparent'; 379 | } 380 | 381 | return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", { 382 | className: className, 383 | children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", { 384 | role: "presentation", 385 | onClick: this.bound.containerClick, 386 | className: this.className('overlay'), 387 | style: overlayStyle 388 | }), box] 389 | }); 390 | } 391 | }], [{ 392 | key: "addShowListener", 393 | value: function addShowListener(callback) { 394 | Store.on(_Constants["default"].SHOW, callback); 395 | } 396 | }, { 397 | key: "removeShowListener", 398 | value: function removeShowListener(callback) { 399 | Store.removeListener(_Constants["default"].SHOW, callback); 400 | } 401 | }, { 402 | key: "addCloseListener", 403 | value: function addCloseListener(callback) { 404 | Store.on(_Constants["default"].CLOSE, callback); 405 | } 406 | }, { 407 | key: "removeCloseListener", 408 | value: function removeCloseListener(callback) { 409 | Store.removeListener(_Constants["default"].CLOSE, callback); 410 | } 411 | }, { 412 | key: "register", 413 | value: function register(data) { 414 | var id = Store.getId(); 415 | Store.popups[id] = Object.assign({}, initialState, data); 416 | return id; 417 | } 418 | }, { 419 | key: "queue", 420 | value: function queue(id) { 421 | if (!Object.prototype.hasOwnProperty.call(Store.popups, id)) { 422 | return false; 423 | } 424 | /** Add popup to queue */ 425 | 426 | 427 | Store.queue.push(id); 428 | /** Dispatch queue */ 429 | 430 | Store.dispatch(); 431 | return id; 432 | } 433 | }, { 434 | key: "create", 435 | value: function create(data, bringToFront) { 436 | /** Register popup */ 437 | var id = this.register(data); 438 | /** Queue popup */ 439 | 440 | if (bringToFront === true) { 441 | var currentlyActive = Store.active; 442 | Store.active = null; 443 | this.queue(id); 444 | this.queue(currentlyActive); 445 | Store.dispatch(); 446 | } else { 447 | this.queue(id); 448 | } 449 | 450 | return id; 451 | } 452 | }, { 453 | key: "alert", 454 | value: function alert(text, title, bringToFront) { 455 | var data = { 456 | title: title, 457 | content: text, 458 | buttons: { 459 | right: ['ok'] 460 | } 461 | }; 462 | return this.create(data, bringToFront); 463 | } 464 | }, { 465 | key: "close", 466 | value: function close() { 467 | Store.close(); 468 | } 469 | }, { 470 | key: "registerPlugin", 471 | value: function registerPlugin(name, callback) { 472 | Store.plugins[name] = callback.bind(this); 473 | } 474 | }, { 475 | key: "plugins", 476 | value: function plugins() { 477 | return Store.plugins; 478 | } 479 | }, { 480 | key: "refreshPosition", 481 | value: function refreshPosition(position) { 482 | return Store.refreshPosition(position); 483 | } 484 | }, { 485 | key: "clearQueue", 486 | value: function clearQueue() { 487 | return Store.clearQueue(); 488 | } 489 | }]); 490 | 491 | return Popup; 492 | }(_react["default"].Component); 493 | 494 | _defineProperty(Popup, "defaultProps", { 495 | className: 'mm-popup', 496 | btnClass: 'mm-popup__btn', 497 | closeBtn: true, 498 | closeHtml: null, 499 | defaultOk: 'Ok', 500 | defaultOkKey: 'enter', 501 | defaultCancel: 'Cancel', 502 | defaultCancelKey: 'esc', 503 | closeOnOutsideClick: true, 504 | escToClose: true, 505 | onClose: function onClose() {}, 506 | onShow: function onShow() {} 507 | }); 508 | 509 | Popup.propTypes = { 510 | className: _propTypes["default"].string, 511 | btnClass: _propTypes["default"].string, 512 | closeBtn: _propTypes["default"].bool, 513 | closeHtml: _propTypes["default"].node, 514 | defaultOk: _propTypes["default"].string, 515 | defaultOkKey: _propTypes["default"].string, 516 | defaultCancel: _propTypes["default"].string, 517 | defaultCancelKey: _propTypes["default"].string, 518 | closeOnOutsideClick: _propTypes["default"].bool, 519 | escToClose: _propTypes["default"].bool, 520 | onClose: _propTypes["default"].func, 521 | onShow: _propTypes["default"].func 522 | }; 523 | var _default = Popup; 524 | exports["default"] = _default; -------------------------------------------------------------------------------- /dist/Store.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports["default"] = void 0; 9 | 10 | var _events = require("events"); 11 | 12 | var _Constants = _interopRequireDefault(require("./Constants")); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 15 | 16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 17 | 18 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 19 | 20 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 21 | 22 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } 23 | 24 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 25 | 26 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 27 | 28 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } 29 | 30 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 31 | 32 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 33 | 34 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 35 | 36 | var PopupStore = /*#__PURE__*/function (_EventEmitter) { 37 | _inherits(PopupStore, _EventEmitter); 38 | 39 | var _super = _createSuper(PopupStore); 40 | 41 | function PopupStore(props) { 42 | var _this; 43 | 44 | _classCallCheck(this, PopupStore); 45 | 46 | _this = _super.call(this, props); 47 | _this.id = 1; 48 | _this.popups = {}; 49 | _this.queue = []; 50 | _this.active = null; 51 | _this.plugins = {}; 52 | return _this; 53 | } 54 | /** 55 | * Get popup ID 56 | */ 57 | 58 | 59 | _createClass(PopupStore, [{ 60 | key: "getId", 61 | value: function getId() { 62 | return "id_".concat(this.id++); 63 | } 64 | /** 65 | * Get active popup 66 | * @returns {*} 67 | */ 68 | 69 | }, { 70 | key: "activePopup", 71 | value: function activePopup() { 72 | return this.popups[this.active]; 73 | } 74 | /** 75 | * Close current popup 76 | */ 77 | 78 | }, { 79 | key: "close", 80 | value: function close() { 81 | if (!this.active) { 82 | return false; 83 | } 84 | 85 | var id = this.active; 86 | this.active = null; 87 | this.emit(_Constants["default"].CLOSE, id); 88 | this.dispatch(); 89 | this.value = null; 90 | return id; 91 | } 92 | /** 93 | * Dispatch next popup in queue 94 | */ 95 | 96 | }, { 97 | key: "dispatch", 98 | value: function dispatch() { 99 | if (this.active || this.queue.length < 1) { 100 | return false; 101 | } 102 | 103 | var id = this.queue.shift(); 104 | /** Set active */ 105 | 106 | this.active = id; 107 | this.emit(_Constants["default"].SHOW, id); 108 | return true; 109 | } 110 | /** 111 | * Refresh popup position 112 | * @param position 113 | */ 114 | 115 | }, { 116 | key: "refreshPosition", 117 | value: function refreshPosition(position) { 118 | this.emit(_Constants["default"].REFRESH, position); 119 | } 120 | /** 121 | * Clear queue 122 | */ 123 | 124 | }, { 125 | key: "clearQueue", 126 | value: function clearQueue() { 127 | this.queue = []; 128 | } 129 | }]); 130 | 131 | return PopupStore; 132 | }(_events.EventEmitter); 133 | 134 | exports["default"] = PopupStore; -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "default", { 7 | enumerable: true, 8 | get: function get() { 9 | return _Popup["default"]; 10 | } 11 | }); 12 | 13 | var _Popup = _interopRequireDefault(require("./Popup.react")); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } -------------------------------------------------------------------------------- /example/index.jsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import Popup from '../src'; 4 | import '../style.css'; 5 | 6 | const root = createRoot(document.querySelector('#root')); 7 | 8 | function App() { 9 | useEffect(() => { 10 | Popup.create({ 11 | title: 'Hello World!', 12 | onShow: (id, title) => { 13 | console.log('Callback: onShow', id, title); 14 | }, 15 | onClose: (id, title) => { 16 | console.log('Callback: onClose', id, title); 17 | }, 18 | content: ( 19 |
20 | It takes more than just a good looking body. You've got to have the heart and soul to go with it. 21 |
22 | ), 23 | className: 'alert', 24 | buttons: { 25 | left: ['cancel'], 26 | right: [ 27 | 🦄, 28 | { 29 | text: 'Ok!', 30 | className: 'success', 31 | action: Popup.close 32 | } 33 | ] 34 | }, 35 | }); 36 | }, []); 37 | 38 | return ; 39 | } 40 | 41 | root.render(); 42 | -------------------------------------------------------------------------------- /example/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= htmlWebpackPlugin.options.title %> 7 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: resolve(__dirname), 6 | resolve: { 7 | extensions: ['.js', '.jsx'], 8 | }, 9 | devServer: { 10 | compress: true, 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.jsx?$/, 16 | exclude: /node_modules/, 17 | use: { 18 | loader: 'babel-loader', 19 | }, 20 | }, 21 | { 22 | test: /\.css$/, 23 | use: [ 24 | { loader: 'style-loader' }, 25 | { loader: 'css-loader' }, 26 | ], 27 | }, 28 | ], 29 | }, 30 | plugins: [ 31 | new HtmlWebpackPlugin({ 32 | title: 'Popup', 33 | template: resolve(__dirname, './template.html'), 34 | }), 35 | ], 36 | }; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-popup", 3 | "version": "0.11.2", 4 | "description": "React popup component from Minutemailer", 5 | "main": "dist", 6 | "scripts": { 7 | "lint": "eslint src/*.js", 8 | "test": "ava", 9 | "start": "webpack-dev-server --config example/webpack.config.js", 10 | "build": "NODE_ENV=production babel src --out-dir dist" 11 | }, 12 | "files": [ 13 | "dist", 14 | "style.css" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/minutemailer/react-popup.git" 19 | }, 20 | "keywords": [ 21 | "react", 22 | "popup", 23 | "js", 24 | "javascript", 25 | "minutemailer", 26 | "react-component" 27 | ], 28 | "author": "Tobias Bleckert (Minutemailer)", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/minutemailer/react-popup/issues" 32 | }, 33 | "homepage": "https://github.com/minutemailer/react-popup", 34 | "peerDependencies": { 35 | "prop-types": "^15.6.0", 36 | "react": "^17.0.2 || ^18.0.0", 37 | "react-dom": "^17.0.2 || ^18.0.0" 38 | }, 39 | "devDependencies": { 40 | "@babel/cli": "^7.17.0", 41 | "@babel/core": "^7.17.2", 42 | "@babel/plugin-proposal-class-properties": "^7.16.7", 43 | "@babel/plugin-proposal-object-rest-spread": "^7.16.7", 44 | "@babel/preset-env": "^7.16.11", 45 | "@babel/preset-react": "^7.16.7", 46 | "@babel/register": "^7.17.0", 47 | "ava": "^1.0.0-beta.4", 48 | "babel-core": "^7.0.0-0", 49 | "babel-eslint": "^8.2.1", 50 | "babel-loader": "^8.2.3", 51 | "browser-env": "^3.3.0", 52 | "css-loader": "^0.28.9", 53 | "enzyme": "^3.11.0", 54 | "enzyme-adapter-react-16": "^1.15.6", 55 | "eslint": "^4.11.0", 56 | "eslint-config-airbnb": "^16.1.0", 57 | "eslint-plugin-import": "^2.8.0", 58 | "eslint-plugin-jsx-a11y": "^6.0.3", 59 | "eslint-plugin-react": "^7.6.1", 60 | "html-webpack-plugin": "^2.30.1", 61 | "react": "^18.2.0", 62 | "react-dom": "^18.2.0", 63 | "style-loader": "^0.20.1", 64 | "webpack": "^3.10.0", 65 | "webpack-dev-server": "^2.11.1" 66 | }, 67 | "dependencies": { 68 | "keymaster": "^1.6.2" 69 | }, 70 | "ava": { 71 | "require": [ 72 | "@babel/register", 73 | "./__tests__/helpers/setup-browser-env.js", 74 | "./__tests__/helpers/setup-enzyme.js" 75 | ] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ActionButton.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class PopupAction extends React.Component { 5 | static defaultProps = { 6 | onClick: () => {}, 7 | className: 'btn', 8 | url: null, 9 | }; 10 | 11 | handleClick() { 12 | return this.props.onClick(); 13 | } 14 | 15 | render() { 16 | const { className } = this.props; 17 | 18 | if (this.props.url && this.props.url !== '#') { 19 | return ({this.props.children}); 20 | } 21 | 22 | return ( 23 | 26 | ); 27 | } 28 | } 29 | 30 | PopupAction.propTypes = { 31 | onClick: PropTypes.func, 32 | className: PropTypes.string, 33 | children: PropTypes.node.isRequired, 34 | url: PropTypes.string, 35 | }; 36 | 37 | export default PopupAction; 38 | -------------------------------------------------------------------------------- /src/Bem.js: -------------------------------------------------------------------------------- 1 | const element = (el, base) => `${base}__${el}`; 2 | 3 | const modifier = (modifiers, base) => { 4 | if (!modifiers) { 5 | return null; 6 | } 7 | 8 | const finalClass = []; 9 | const classNames = modifiers.split(' '); 10 | 11 | classNames.forEach((singleClass) => { 12 | finalClass.push(`${base}--${singleClass}`); 13 | }); 14 | 15 | return finalClass.join(' '); 16 | }; 17 | 18 | export { modifier, element }; 19 | -------------------------------------------------------------------------------- /src/ButtonsSpace.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ActionButton from './ActionButton.react'; 4 | import { modifier } from './Bem'; 5 | 6 | export default class PopupFooterButtons extends React.Component { 7 | static defaultProps = { 8 | buttons: null, 9 | className: null, 10 | onOk: () => {}, 11 | onClose: () => {}, 12 | buttonClick: () => {}, 13 | btnClass: null, 14 | defaultOk: null, 15 | defaultCancel: null, 16 | }; 17 | 18 | onOk() { 19 | return this.props.onOk(); 20 | } 21 | 22 | onClose() { 23 | return this.props.onClose(); 24 | } 25 | 26 | buttonClick(action) { 27 | return this.props.buttonClick(action); 28 | } 29 | 30 | render() { 31 | if (!this.props.buttons) { 32 | return null; 33 | } 34 | 35 | const btns = []; 36 | 37 | this.props.buttons.forEach((btn, i) => { 38 | const url = (btn.url) ? btn.url : null; 39 | const key = i; 40 | 41 | if (typeof btn === 'string') { 42 | if (btn === 'ok') { 43 | btns.push( this.onOk()}>{this.props.defaultOk}); 44 | } else if (btn === 'cancel') { 45 | btns.push( this.onClose()}>{this.props.defaultCancel}); 46 | } 47 | } else if (React.isValidElement(btn)) { 48 | btns.push(btn); 49 | } else { 50 | const className = `${this.props.btnClass} ${modifier(btn.className, this.props.btnClass)}`; 51 | const btnComponent = ( 52 | this.buttonClick(btn.action)} 57 | > 58 | {btn.text} 59 | 60 | ); 61 | 62 | btns.push(btnComponent); 63 | } 64 | }); 65 | 66 | return ( 67 |
68 | {btns} 69 |
70 | ); 71 | } 72 | } 73 | 74 | PopupFooterButtons.propTypes = { 75 | buttons: PropTypes.arrayOf(PropTypes.oneOfType([ 76 | PropTypes.string, 77 | PropTypes.object, 78 | ])), 79 | className: PropTypes.string, 80 | onOk: PropTypes.func, 81 | onClose: PropTypes.func, 82 | buttonClick: PropTypes.func, 83 | btnClass: PropTypes.string, 84 | defaultOk: PropTypes.string, 85 | defaultCancel: PropTypes.string, 86 | }; 87 | -------------------------------------------------------------------------------- /src/Constants.js: -------------------------------------------------------------------------------- 1 | export default { 2 | SHOW: 'SHOW', 3 | CLOSE: 'CLOSE', 4 | REFRESH: 'REFRESH', 5 | }; 6 | -------------------------------------------------------------------------------- /src/Footer.react.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import ButtonsSpace from './ButtonsSpace.react'; 3 | 4 | const PopupFooter = (props) => { 5 | if (!props.buttons) { 6 | return null; 7 | } 8 | 9 | return ( 10 |
11 | 21 | 22 | 32 |
33 | ); 34 | }; 35 | 36 | PopupFooter.propTypes = { 37 | buttons: PropTypes.shape({ 38 | left: PropTypes.arrayOf(PropTypes.oneOfType([ 39 | PropTypes.string, 40 | PropTypes.object, 41 | ])), 42 | right: PropTypes.arrayOf(PropTypes.oneOfType([ 43 | PropTypes.string, 44 | PropTypes.object, 45 | ])), 46 | }), 47 | className: PropTypes.string, 48 | btnClass: PropTypes.string, 49 | onOk: PropTypes.func, 50 | onClose: PropTypes.func, 51 | buttonClick: PropTypes.func, 52 | defaultOk: PropTypes.string, 53 | defaultCancel: PropTypes.string, 54 | }; 55 | 56 | PopupFooter.defaultProps = { 57 | buttons: null, 58 | className: null, 59 | btnClass: null, 60 | defaultOk: null, 61 | defaultCancel: null, 62 | buttonClick: () => {}, 63 | onOk: () => {}, 64 | onClose: () => {}, 65 | }; 66 | 67 | export default PopupFooter; 68 | -------------------------------------------------------------------------------- /src/Header.react.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | const PopupHeader = (props) => { 4 | if (!props.title) { 5 | return null; 6 | } 7 | 8 | return ( 9 |
10 |

{props.title}

11 |
12 | ); 13 | }; 14 | 15 | PopupHeader.defaultProps = { 16 | title: null, 17 | className: null, 18 | }; 19 | 20 | PopupHeader.propTypes = { 21 | title: PropTypes.string, 22 | className: PropTypes.string, 23 | }; 24 | 25 | export default PopupHeader; 26 | -------------------------------------------------------------------------------- /src/Popup.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import key from 'keymaster'; 4 | import PopupStore from './Store'; 5 | import Header from './Header.react'; 6 | import Footer from './Footer.react'; 7 | import Constants from './Constants'; 8 | import { element, modifier } from './Bem'; 9 | 10 | const defaultKeyFilter = key.filter; 11 | const Store = new PopupStore(); 12 | 13 | const handleClose = () => { 14 | key.deleteScope('react-popup'); 15 | key.filter = defaultKeyFilter; 16 | 17 | Store.close(); 18 | }; 19 | 20 | const initialState = { 21 | id: null, 22 | title: null, 23 | buttons: null, 24 | content: null, 25 | visible: false, 26 | className: null, 27 | noOverlay: false, 28 | position: false, 29 | closeOnOutsideClick: true, 30 | onClose: () => {}, 31 | onShow: () => {}, 32 | }; 33 | 34 | class Popup extends React.Component { 35 | static defaultProps = { 36 | className: 'mm-popup', 37 | btnClass: 'mm-popup__btn', 38 | closeBtn: true, 39 | closeHtml: null, 40 | defaultOk: 'Ok', 41 | defaultOkKey: 'enter', 42 | defaultCancel: 'Cancel', 43 | defaultCancelKey: 'esc', 44 | closeOnOutsideClick: true, 45 | escToClose: true, 46 | onClose: () => {}, 47 | onShow: () => {}, 48 | }; 49 | 50 | static addShowListener(callback) { 51 | Store.on(Constants.SHOW, callback); 52 | } 53 | 54 | static removeShowListener(callback) { 55 | Store.removeListener(Constants.SHOW, callback); 56 | } 57 | 58 | static addCloseListener(callback) { 59 | Store.on(Constants.CLOSE, callback); 60 | } 61 | 62 | static removeCloseListener(callback) { 63 | Store.removeListener(Constants.CLOSE, callback); 64 | } 65 | 66 | static register(data) { 67 | const id = Store.getId(); 68 | 69 | Store.popups[id] = Object.assign({}, initialState, data); 70 | 71 | return id; 72 | } 73 | 74 | static queue(id) { 75 | if (!Object.prototype.hasOwnProperty.call(Store.popups, id)) { 76 | return false; 77 | } 78 | 79 | /** Add popup to queue */ 80 | Store.queue.push(id); 81 | 82 | /** Dispatch queue */ 83 | Store.dispatch(); 84 | 85 | return id; 86 | } 87 | 88 | static create(data, bringToFront) { 89 | /** Register popup */ 90 | const id = this.register(data); 91 | 92 | /** Queue popup */ 93 | if (bringToFront === true) { 94 | const currentlyActive = Store.active; 95 | 96 | Store.active = null; 97 | this.queue(id); 98 | this.queue(currentlyActive); 99 | Store.dispatch(); 100 | } else { 101 | this.queue(id); 102 | } 103 | 104 | return id; 105 | } 106 | 107 | static alert(text, title, bringToFront) { 108 | const data = { 109 | title, 110 | content: text, 111 | buttons: { 112 | right: ['ok'], 113 | }, 114 | }; 115 | 116 | return this.create(data, bringToFront); 117 | } 118 | 119 | static close() { 120 | Store.close(); 121 | } 122 | 123 | static registerPlugin(name, callback) { 124 | Store.plugins[name] = callback.bind(this); 125 | } 126 | 127 | static plugins() { 128 | return Store.plugins; 129 | } 130 | 131 | static refreshPosition(position) { 132 | return Store.refreshPosition(position); 133 | } 134 | 135 | static clearQueue() { 136 | return Store.clearQueue(); 137 | } 138 | 139 | constructor(props) { 140 | super(props); 141 | 142 | initialState.closeOnOutsideClick = this.props.closeOnOutsideClick; 143 | 144 | this.state = initialState; 145 | 146 | this.bound = { 147 | onShow: this.onShow.bind(this), 148 | onClose: this.onClose.bind(this), 149 | onRefresh: this.onRefresh.bind(this), 150 | containerClick: this.containerClick.bind(this), 151 | handleButtonClick: this.handleButtonClick.bind(this), 152 | }; 153 | 154 | this.boxRef = null; 155 | 156 | this.defaultKeyBindings = { 157 | ok: this.props.defaultOkKey, 158 | cancel: this.props.defaultCancelKey, 159 | }; 160 | } 161 | 162 | componentDidMount() { 163 | Store.on(Constants.SHOW, this.bound.onShow); 164 | Store.on(Constants.CLOSE, this.bound.onClose); 165 | Store.on(Constants.REFRESH, this.bound.onRefresh); 166 | } 167 | 168 | componentDidUpdate() { 169 | if (this.boxRef) { 170 | this.boxRef.focus(); 171 | } 172 | 173 | this.setPosition(this.state.position); 174 | } 175 | 176 | componentWillUnmount() { 177 | Store.removeListener(Constants.SHOW, this.bound.onShow); 178 | Store.removeListener(Constants.CLOSE, this.bound.onClose); 179 | Store.removeListener(Constants.REFRESH, this.bound.onRefresh); 180 | key.deleteScope('react-popup'); 181 | key.filter = defaultKeyFilter; 182 | } 183 | 184 | /** 185 | * Refresh popup position 186 | * @param position 187 | * @private 188 | */ 189 | onRefresh(position) { 190 | this.setPosition(position); 191 | } 192 | 193 | /** 194 | * On popup close 195 | * @private 196 | */ 197 | onClose() { 198 | key.deleteScope('react-popup'); 199 | key.filter = defaultKeyFilter; 200 | this.state.onClose(this.state.id, this.state.title); 201 | this.setState(initialState); 202 | } 203 | 204 | /** 205 | * On popup show 206 | * @private 207 | */ 208 | onShow(id) { 209 | key.deleteScope('react-popup'); 210 | 211 | key.filter = () => true; 212 | 213 | const popup = Store.activePopup(); 214 | 215 | if (popup.buttons && !Object.prototype.hasOwnProperty.call(popup.buttons, 'left')) { 216 | popup.buttons.left = []; 217 | } 218 | 219 | if (popup.buttons && !Object.prototype.hasOwnProperty.call(popup.buttons, 'right')) { 220 | popup.buttons.right = []; 221 | } 222 | 223 | this.setState({ 224 | id, 225 | title: popup.title, 226 | content: popup.content, 227 | buttons: popup.buttons, 228 | visible: true, 229 | className: popup.className, 230 | noOverlay: popup.noOverlay, 231 | position: popup.position, 232 | closeOnOutsideClick: popup.closeOnOutsideClick, 233 | onClose: popup.onClose, 234 | onShow: popup.onShow, 235 | }, () => { 236 | key.setScope('react-popup'); 237 | this.state.onShow(this.state.id, this.state.title); 238 | 239 | if (this.props.escToClose) { 240 | key('esc', 'react-popup', this.handleKeyEvent.bind(this, 'cancel', this.state.id)); 241 | } 242 | 243 | if (this.state.buttons) { 244 | if (this.state.buttons.left.length) { 245 | this.state.buttons.left.forEach(button => this.bindKeyEvents(button)); 246 | } 247 | 248 | if (this.state.buttons.right.length) { 249 | this.state.buttons.right.forEach(button => this.bindKeyEvents(button)); 250 | } 251 | } 252 | }); 253 | } 254 | 255 | setPosition(position) { 256 | const box = this.boxRef; 257 | let boxPosition = position; 258 | 259 | if (!box) { 260 | return; 261 | } 262 | 263 | if (!boxPosition) { 264 | boxPosition = this.state.position; 265 | } 266 | 267 | if (!boxPosition) { 268 | box.style.opacity = 1; 269 | box.style.top = null; 270 | box.style.left = null; 271 | box.style.margin = null; 272 | 273 | return; 274 | } 275 | 276 | if (typeof boxPosition === 'function') { 277 | boxPosition.call(null, box); 278 | 279 | return; 280 | } 281 | 282 | box.style.top = `${parseInt(boxPosition.y, 10)}px`; 283 | box.style.left = `${parseInt(boxPosition.x, 10)}px`; 284 | box.style.margin = 0; 285 | box.style.opacity = 1; 286 | } 287 | 288 | /** 289 | * Handle container click 290 | * @param e 291 | * @private 292 | */ 293 | containerClick() { 294 | if (this.state.closeOnOutsideClick) { 295 | handleClose(); 296 | } 297 | } 298 | 299 | bindKeyEvents(button) { 300 | let code = null; 301 | 302 | if (typeof button === 'string') { 303 | code = this.defaultKeyBindings[button]; 304 | } else if (Object.prototype.hasOwnProperty.call(button, 'key')) { 305 | code = button.key; 306 | } 307 | 308 | if (this.props.escToClose && code === 'esc') { 309 | return; 310 | } 311 | 312 | if (code) { 313 | key(code, 'react-popup', this.handleKeyEvent.bind(this, button, this.state.id)); 314 | } 315 | } 316 | 317 | handleKeyEvent(button, id, e) { 318 | const excludeTags = ['INPUT', 'TEXTAREA', 'BUTTON']; 319 | 320 | if (this.state.id !== id || (button.key === 'enter' && excludeTags.indexOf(e.target.tagName) >= 0)) { 321 | return true; 322 | } 323 | 324 | if (typeof button === 'string') { 325 | handleClose(); 326 | } else if (Object.prototype.hasOwnProperty.call(button, 'action')) { 327 | this.handleButtonClick(button.action); 328 | } 329 | 330 | return false; 331 | } 332 | 333 | /** 334 | * Handle button clicks 335 | * @param action 336 | * @returns {*} 337 | * @private 338 | */ 339 | handleButtonClick(action) { 340 | if (typeof action === 'function') { 341 | return action.call(this, Store); 342 | } 343 | 344 | return null; 345 | } 346 | 347 | className(className) { 348 | return element(className, this.props.className); 349 | } 350 | 351 | render() { 352 | let { className } = this.props; 353 | let box = null; 354 | const overlayStyle = {}; 355 | 356 | if (this.state.visible) { 357 | let closeBtn = null; 358 | 359 | className += ` ${this.props.className}--visible`; 360 | 361 | if (this.props.closeBtn) { 362 | closeBtn = ; 363 | } 364 | 365 | let boxClass = this.className('box'); 366 | 367 | if (this.state.className) { 368 | boxClass += ` ${modifier(this.state.className, boxClass)}`; 369 | } 370 | 371 | box = ( 372 |
{ this.boxRef = el; }} style={{ opacity: 0, outline: 'none' }} className={boxClass}> 373 | {closeBtn} 374 |
375 | 376 |
377 | {this.state.content} 378 |
379 | 380 |
390 |
391 | ); 392 | } 393 | 394 | if (this.state.noOverlay) { 395 | overlayStyle.background = 'transparent'; 396 | } 397 | 398 | return ( 399 |
400 |
401 | {box} 402 |
403 | ); 404 | } 405 | } 406 | 407 | Popup.propTypes = { 408 | className: PropTypes.string, 409 | btnClass: PropTypes.string, 410 | closeBtn: PropTypes.bool, 411 | closeHtml: PropTypes.node, 412 | defaultOk: PropTypes.string, 413 | defaultOkKey: PropTypes.string, 414 | defaultCancel: PropTypes.string, 415 | defaultCancelKey: PropTypes.string, 416 | closeOnOutsideClick: PropTypes.bool, 417 | escToClose: PropTypes.bool, 418 | onClose: PropTypes.func, 419 | onShow: PropTypes.func, 420 | }; 421 | 422 | export default Popup; 423 | -------------------------------------------------------------------------------- /src/Store.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import Constants from './Constants'; 3 | 4 | export default class PopupStore extends EventEmitter { 5 | constructor(props) { 6 | super(props); 7 | 8 | this.id = 1; 9 | this.popups = {}; 10 | this.queue = []; 11 | this.active = null; 12 | this.plugins = {}; 13 | } 14 | 15 | /** 16 | * Get popup ID 17 | */ 18 | getId() { 19 | return `id_${this.id++}`; 20 | } 21 | 22 | /** 23 | * Get active popup 24 | * @returns {*} 25 | */ 26 | activePopup() { 27 | return this.popups[this.active]; 28 | } 29 | 30 | /** 31 | * Close current popup 32 | */ 33 | close() { 34 | if (!this.active) { 35 | return false; 36 | } 37 | 38 | const id = this.active; 39 | this.active = null; 40 | 41 | this.emit(Constants.CLOSE, id); 42 | this.dispatch(); 43 | 44 | this.value = null; 45 | 46 | return id; 47 | } 48 | 49 | /** 50 | * Dispatch next popup in queue 51 | */ 52 | dispatch() { 53 | if (this.active || this.queue.length < 1) { 54 | return false; 55 | } 56 | 57 | const id = this.queue.shift(); 58 | 59 | /** Set active */ 60 | this.active = id; 61 | 62 | this.emit(Constants.SHOW, id); 63 | 64 | return true; 65 | } 66 | 67 | /** 68 | * Refresh popup position 69 | * @param position 70 | */ 71 | refreshPosition(position) { 72 | this.emit(Constants.REFRESH, position); 73 | } 74 | 75 | /** 76 | * Clear queue 77 | */ 78 | clearQueue() { 79 | this.queue = []; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Popup.react'; 2 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | .mm-popup { 2 | display: none; 3 | } 4 | 5 | .mm-popup--visible { 6 | display: block; 7 | } 8 | 9 | .mm-popup__overlay { 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | width: 100%; 14 | height: 100%; 15 | z-index: 1000; 16 | overflow: auto; 17 | background: rgba(0, 0, 0, .1); 18 | } 19 | 20 | .mm-popup__close { 21 | position: absolute; 22 | top: 15px; 23 | right: 20px; 24 | padding: 0; 25 | width: 20px; 26 | height: 20px; 27 | cursor: pointer; 28 | outline: none; 29 | text-align: center; 30 | border-radius: 10px; 31 | border: none; 32 | text-indent: -9999px; 33 | background: transparent url('data:image/png;base64,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') no-repeat center center; 34 | background-size: 100%; 35 | margin: 0; 36 | } 37 | 38 | .mm-popup__input { 39 | display: block; 40 | width: 100%; 41 | height: 30px; 42 | border-radius: 3px; 43 | background: #f5f5f5; 44 | border: 1px solid #e9ebec; 45 | outline: none; 46 | -moz-box-sizing: border-box !important; 47 | -webkit-box-sizing: border-box !important; 48 | box-sizing: border-box !important; 49 | font-size: 14px; 50 | padding: 0 12px; 51 | color: #808080; 52 | } 53 | 54 | .mm-popup__btn { 55 | border-radius: 3px; 56 | -moz-box-sizing: border-box; 57 | -webkit-box-sizing: border-box; 58 | box-sizing: border-box; 59 | padding: 0 10px; 60 | margin: 0; 61 | line-height: 32px; 62 | height: 32px; 63 | border: 1px solid #666; 64 | text-align: center; 65 | display: inline-block; 66 | font-size: 12px; 67 | font-weight: 400; 68 | color: #333; 69 | background: transparent; 70 | outline: none; 71 | text-decoration: none; 72 | cursor: pointer; 73 | font-family: "Open Sans", sans-serif; 74 | } 75 | 76 | .mm-popup__btn--success { 77 | background-color: #27ae60; 78 | border-color: #27ae60; 79 | color: #fff; 80 | } 81 | 82 | .mm-popup__btn--danger { 83 | background-color: #c5545c; 84 | border-color: #c5545c; 85 | color: #fff; 86 | } 87 | 88 | .mm-popup__box { 89 | width: 350px; 90 | position: fixed; 91 | top: 10%; 92 | left: 50%; 93 | margin-left: -175px; 94 | background: #fff; 95 | box-shadow: 0px 5px 20px 0px rgba(126, 137, 140, 0.20); 96 | border-radius: 5px; 97 | border: 1px solid #B8C8CC; 98 | overflow: hidden; 99 | z-index: 1001; 100 | } 101 | 102 | .mm-popup__box__header { 103 | padding: 15px 20px; 104 | background: #EDF5F7; 105 | color: #454B4D; 106 | } 107 | 108 | .mm-popup__box__header__title { 109 | margin: 0; 110 | font-size: 16px; 111 | text-align: left; 112 | font-weight: 600; 113 | } 114 | 115 | .mm-popup__box__body { 116 | padding: 20px; 117 | line-height: 1.4; 118 | font-size: 14px; 119 | color: #454B4D; 120 | background: #fff; 121 | position: relative; 122 | z-index: 2; 123 | } 124 | 125 | .mm-popup__box__body p { 126 | margin: 0 0 5px; 127 | } 128 | 129 | .mm-popup__box__footer { 130 | overflow: hidden; 131 | padding: 40px 20px 20px; 132 | } 133 | 134 | .mm-popup__box__footer__right-space { 135 | float: right; 136 | } 137 | 138 | .mm-popup__box__footer__right-space .mm-popup__btn { 139 | margin-left: 5px; 140 | } 141 | 142 | .mm-popup__box__footer__left-space { 143 | float: left; 144 | } 145 | 146 | .mm-popup__box__footer__left-space .mm-popup__btn { 147 | margin-right: 5px; 148 | } 149 | 150 | .mm-popup__box--popover { 151 | width: 300px; 152 | margin-left: -150px; 153 | } 154 | 155 | .mm-popup__box--popover .mm-popup__close { 156 | position: absolute; 157 | top: 5px; 158 | right: 5px; 159 | padding: 0; 160 | width: 20px; 161 | height: 20px; 162 | cursor: pointer; 163 | outline: none; 164 | text-align: center; 165 | border-radius: 10px; 166 | border: none; 167 | text-indent: -9999px; 168 | background: transparent url('data:image/png;base64,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') no-repeat center center; 169 | background-size: 100%; 170 | margin: 0; 171 | z-index: 3; 172 | } 173 | 174 | .mm-popup__box--popover .mm-popup__box__body { 175 | padding: 20px; 176 | } 177 | 178 | @media (max-width: 420px) { 179 | .mm-popup__box { 180 | width: auto; 181 | left: 10px; 182 | right: 10px; 183 | top: 10px; 184 | margin-left: 0; 185 | } 186 | .mm-popup__box__footer__left-space { 187 | float: none; 188 | } 189 | .mm-popup__box__footer__right-space { 190 | float: none; 191 | } 192 | .mm-popup__box__footer { 193 | padding-top: 30px; 194 | } 195 | .mm-popup__box__footer .mm-popup__btn { 196 | display: block; 197 | width: 100%; 198 | text-align: center; 199 | margin-top: 10px; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | module.exports = { 4 | entry: './src', 5 | output: { 6 | path: resolve(__dirname, 'dist'), 7 | filename: 'bundle.js', 8 | libraryTarget: 'commonjs2', 9 | }, 10 | resolve: { 11 | extensions: ['.js', '.jsx'], 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.jsx?$/, 17 | exclude: /node_modules/, 18 | use: { 19 | loader: 'babel-loader', 20 | }, 21 | }, 22 | ], 23 | }, 24 | externals: ['react', 'keymaster'], 25 | }; 26 | --------------------------------------------------------------------------------