├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── example
├── App.jsx
├── Fork.jsx
├── ModalSample.jsx
├── index.html
├── index.js
├── store
│ ├── index.js
│ ├── modules
│ │ └── user
│ │ │ ├── actions.js
│ │ │ └── index.js
│ └── reducers.js
└── styles.css
├── package.json
├── src
├── Modal.jsx
├── components
│ ├── Content
│ │ ├── components
│ │ │ ├── Footer
│ │ │ │ ├── components
│ │ │ │ │ └── Button
│ │ │ │ │ │ └── index.jsx
│ │ │ │ └── index.jsx
│ │ │ ├── Header
│ │ │ │ └── index.jsx
│ │ │ └── Main
│ │ │ │ └── index.jsx
│ │ └── index.jsx
│ └── ModalDefault
│ │ └── index.jsx
├── index.js
├── modules
│ ├── actions.js
│ ├── index.js
│ └── selectors.js
└── styled.js
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env", "react"],
3 | "plugins": ["transform-class-properties", "ramda"]
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # misc
10 | .DS_Store
11 |
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 |
16 | /libs
17 | bundle.js
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-present Nghiep
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [DEPRECATED] REACT-REDUX-MODAL-FLEX
2 |
3 | [](https://www.npmjs.com/package/react-redux-modal-flex)
4 | [](https://www.npmjs.com/package/react-redux-modal-flex)
5 |
6 | > Make easy a modal/popup with Redux.
7 |
8 | :warning: This will work only with React 16.3+ :warning:
9 |
10 | If you're looking for a version for React 16.2 (for individual pages) use [1.x branch](https://github.com/nghiepit/react-redux-modal-flex/tree/1.x) branch.
11 |
12 | ## Demo
13 |
14 | [https://react-redux-modal-flex.netlify.com](https://react-redux-modal-flex.netlify.com)
15 |
16 | ## Features
17 |
18 | - Responsive
19 | - Easy custom `animation` effect by [Animate.css](https://daneden.github.io/animate.css/)
20 |
21 | ## Installation
22 |
23 | To install the stable version you can use:
24 |
25 | ```sh
26 | $ yarn add react-redux-modal-flex
27 | ```
28 |
29 | ## Example
30 |
31 | ### Step 1:
32 |
33 | `rootReducer.js`
34 |
35 | ```js
36 | import { combineReducers } from "redux";
37 | import { reducer as modal } from "react-redux-modal-flex";
38 | import todos from "./todos";
39 |
40 | export default combineReducers({
41 | todos,
42 | modal: modal({
43 | classContent: "modal-content",
44 | animation: "zoomIn",
45 | duration: 200,
46 | mask: true,
47 | /* initial state, see API reference */
48 | }),
49 | });
50 | ```
51 |
52 | ### Step 2:
53 |
54 | `App.js`
55 |
56 | ```jsx
57 | import Modal from "react-redux-modal-flex";
58 |
59 | class App extends React.Component {
60 | render() {
61 | return (
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | );
72 | }
73 | }
74 | ```
75 |
76 | ### Step 3:
77 |
78 | Any `Container` you want to use
79 |
80 | ```jsx
81 | import { connect } from "react-redux";
82 | import { actions as ModalActions } from "react-redux-modal-flex";
83 |
84 | class LoginModal extends React.Component {
85 | render() {
86 | return (
87 |
97 | );
98 | }
99 | }
100 |
101 | class Auth extends React.Component {
102 | render() {
103 | return (
104 |
105 |
Auth
106 |
119 |
120 | );
121 | }
122 | }
123 |
124 | export default connect(null, { toggleModal: ModalActions.toggleModal })(Auth);
125 | ```
126 |
127 | ## API
128 |
129 | - initState: you can overwrite default initial state
130 |
131 | ```js
132 | const initState = {
133 | classContent: "modal-content",
134 | animation: "zoomIn",
135 | duration: 300,
136 | mask: true,
137 | closeByMask: true,
138 | component: ModalDefault,
139 | title: "This is a title",
140 | closeBtn: true,
141 | textCancel: "Cancel",
142 | ok: {
143 | text: "OK",
144 | classOk: "modal-btn-ok",
145 | disabled: false,
146 | action: () => console.log("OK clicked"),
147 | },
148 | };
149 | ```
150 |
151 | - API
152 |
153 | ```js
154 | import Modal, {
155 | reducer as modal,
156 | actions as ModalActions,
157 | } from "react-redux-modal-flex";
158 | const { toggleModal, modifyOkModal } = ModalActions;
159 | ```
160 |
161 | - `` is component, using in our `App.js`
162 | - `reducer` using in our `rootReducer.js` you can custom default initial state
163 |
164 | ```js
165 | export default combineReducers({
166 | todos,
167 | modal: modal({
168 | textCancel: "Close",
169 | title: "My default title",
170 | }),
171 | });
172 | ```
173 |
174 | - `toggleModal` and `modifyOkModal` is action
175 |
176 | ## Usage
177 |
178 | - Open Modal by action `toggleModal(options)`
179 | - `options`: is object and look like the `initState` above
180 | - Example:
181 |
182 | ```jsx
183 | ...
184 | render() {
185 | return (
186 |
195 | );
196 | }
197 | ...
198 | ```
199 |
200 | - Close Modal `toggleModal(false)` or any value excepted object
201 | - Modify button `OK`: `modifyOkModal(options)` usage like `toggleModal`
202 | - Example:
203 |
204 | ```js
205 | onClick={() => this.props.modifyOkModal({
206 | text: 'Sign up',
207 | disabled: true
208 | })}
209 | ```
210 |
211 | - Hide `Header` if the `title` is null
212 | - Hide `Cancel` button if the `textCancel` is null
213 | - Hide `Ok` button if `ok: {text: null}`
214 | - Hide Footer if the `Cancel` and `Ok` are hidden
215 |
216 | ## License
217 |
218 | MIT
219 |
--------------------------------------------------------------------------------
/example/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {connect} from 'react-redux';
3 | import {pick} from 'ramda';
4 |
5 | import {actions as ModalActions} from '../src';
6 |
7 | import ModalSample from './ModalSample';
8 |
9 | class App extends React.Component {
10 | render() {
11 | return (
12 |
13 |
REACT-REDUX-MODAL-FLEX
14 |
Make easy a modal/popup with Redux
15 |
16 |
27 |
28 |
40 |
41 |
54 |
55 |
56 |
62 |
63 | );
64 | }
65 | }
66 |
67 | export default connect(
68 | null,
69 | pick(['toggleModal'], ModalActions),
70 | )(App);
71 |
--------------------------------------------------------------------------------
/example/Fork.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default () => (
4 |
8 |
27 |
28 | );
29 |
--------------------------------------------------------------------------------
/example/ModalSample.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class ModalSample extends React.Component {
4 | state = {
5 | style: {
6 | padding: '20px 0px',
7 | width: '500px',
8 | },
9 | };
10 |
11 | static defaultProps = {
12 | content: 'My modal popup',
13 | };
14 |
15 | render() {
16 | return {this.props.content}
;
17 | }
18 | }
19 |
20 | export default ModalSample;
21 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | REACT-REDUX-MODAL-FLEX
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 |
5 | import createStore from './store';
6 | import reducers from './store/reducers';
7 |
8 | import Modal from '../src';
9 |
10 | import App from './App';
11 | import Fork from './Fork';
12 |
13 | import './styles.css';
14 |
15 | ReactDOM.render(
16 |
17 |
18 |
19 |
20 |
21 |
22 | ,
23 | document.getElementById('app')
24 | );
25 |
--------------------------------------------------------------------------------
/example/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 |
3 | const __DEV__ = process.env.NODE_ENV !== 'production';
4 |
5 | const configureStore = reducers => {
6 | const middlewares = [];
7 |
8 | if (__DEV__) {
9 | // middlewares.push();
10 | }
11 |
12 | let composeEnhancers = compose;
13 |
14 | if (__DEV__) {
15 | if (typeof window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ === 'function') {
16 | composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
17 | }
18 | }
19 |
20 | return createStore(
21 | reducers,
22 | composeEnhancers(applyMiddleware(...middlewares))
23 | );
24 | };
25 |
26 | export default configureStore;
27 |
--------------------------------------------------------------------------------
/example/store/modules/user/actions.js:
--------------------------------------------------------------------------------
1 | export const LOGIN = 'user/LOGIN';
2 |
--------------------------------------------------------------------------------
/example/store/modules/user/index.js:
--------------------------------------------------------------------------------
1 | import * as actions from './actions';
2 |
3 | const initState = {
4 | username: 'username',
5 | password: 'mypassword',
6 | };
7 |
8 | export default (state = initState, action) => {
9 | switch (action.type) {
10 | case actions.LOGIN:
11 | return state;
12 |
13 | default:
14 | return state;
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/example/store/reducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import user from './modules/user';
4 | import { reducer as modal } from '../../src';
5 |
6 | const reducers = combineReducers({
7 | user,
8 | modal: modal({ textCancel: 'Close' }),
9 | });
10 |
11 | export default reducers;
12 |
--------------------------------------------------------------------------------
/example/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | font-family: 'Roboto', sans-serif;
9 | font-size: 16px;
10 | color: #525252;
11 | background: #f0fbff;
12 | }
13 |
14 | .App {
15 | position: fixed;
16 | top: 50%;
17 | left: 50%;
18 | transform: translate(-50%, -50%);
19 | }
20 |
21 | h1,
22 | h2,
23 | h3 {
24 | background-image: linear-gradient(
25 | 90deg,
26 | #f79533 0%,
27 | #f37055 15%,
28 | #ef4e7b 30%,
29 | #a166ab 44%,
30 | #5073b8 58%,
31 | #1098ad 72%,
32 | #07b39b 86%,
33 | #6dba82 100%
34 | );
35 | background-size: cover;
36 | -webkit-background-clip: text;
37 | -webkit-text-fill-color: transparent;
38 | background-clip: text;
39 | text-fill-color: transparent;
40 | font-weight: 100;
41 | }
42 |
43 | button {
44 | padding: 10px 20px;
45 | outline: none;
46 | background: #559ef1;
47 | color: #ffffff;
48 | border: none;
49 | font-size: 1.2rem;
50 | cursor: pointer;
51 | }
52 |
53 | .text-center {
54 | text-align: center;
55 | }
56 |
57 | .buttons {
58 | width: 100%;
59 | display: flex;
60 | justify-content: space-around;
61 | margin: 20px 0;
62 | }
63 |
64 | .animated.flipOutX,
65 | .animated.flipOutY,
66 | .animated.bounceIn,
67 | .animated.bounceOut {
68 | animation-duration: 0.75s;
69 | }
70 |
71 | @keyframes fadeIn {
72 | from {
73 | opacity: 0;
74 | }
75 |
76 | to {
77 | opacity: 1;
78 | }
79 | }
80 |
81 | .fadeIn {
82 | animation-name: fadeIn;
83 | }
84 |
85 | @keyframes bounceIn {
86 | from,
87 | 20%,
88 | 40%,
89 | 60%,
90 | 80%,
91 | to {
92 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
93 | }
94 |
95 | 0% {
96 | opacity: 0;
97 | transform: scale3d(0.3, 0.3, 0.3);
98 | }
99 |
100 | 20% {
101 | transform: scale3d(1.1, 1.1, 1.1);
102 | }
103 |
104 | 40% {
105 | transform: scale3d(0.9, 0.9, 0.9);
106 | }
107 |
108 | 60% {
109 | opacity: 1;
110 | transform: scale3d(1.03, 1.03, 1.03);
111 | }
112 |
113 | 80% {
114 | transform: scale3d(0.97, 0.97, 0.97);
115 | }
116 |
117 | to {
118 | opacity: 1;
119 | transform: scale3d(1, 1, 1);
120 | }
121 | }
122 |
123 | .bounceIn {
124 | animation-name: bounceIn;
125 | }
126 |
127 | .github-corner__svg {
128 | fill: #82888a;
129 | color: #2c2d31;
130 | position: absolute;
131 | top: 0;
132 | border: 0;
133 | right: 0;
134 | }
135 |
136 | .github-corner:hover .octo-arm {
137 | animation: octocat-wave 560ms ease-in-out;
138 | }
139 |
140 | @keyframes octocat-wave {
141 | 0%,
142 | 100% {
143 | transform: rotate(0);
144 | }
145 | 20%,
146 | 60% {
147 | transform: rotate(-25deg);
148 | }
149 | 40%,
150 | 80% {
151 | transform: rotate(10deg);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-modal-flex",
3 | "version": "2.0.3",
4 | "description": "Make easy a modal/popup with Redux",
5 | "main": "./libs/index.js",
6 | "files": [
7 | "libs"
8 | ],
9 | "scripts": {
10 | "clean": "rimraf lib",
11 | "dev": "webpack-dev-server",
12 | "build": "cross-env NODE_ENV=production webpack --progress --profile -p",
13 | "demo": "npm run build && mv bundle.js example/bundle.js",
14 | "lib": "babel src -d libs --source-maps",
15 | "prepublishOnly": "npm run lib"
16 | },
17 | "devDependencies": {
18 | "babel-cli": "^6.26.0",
19 | "babel-core": "6.26.3",
20 | "babel-loader": "^7.1.2",
21 | "babel-plugin-ramda": "^1.6.3",
22 | "babel-plugin-transform-class-properties": "^6.24.1",
23 | "babel-preset-env": "^1.7.0",
24 | "babel-preset-react": "^6.24.1",
25 | "classnames": "^2.2.6",
26 | "cross-env": "^5.2.0",
27 | "css-loader": "^0.28.7",
28 | "prop-types": "^15.0.0 || ^16.0.0",
29 | "react": "^16.3.0",
30 | "react-dom": "^16.3.0",
31 | "react-redux": "^4.0.0 || ^5.0.0",
32 | "redux": "^3.0.0 || ^4.0.0",
33 | "reselect": "^4.0.0",
34 | "rimraf": "^2.6.2",
35 | "style-loader": "^0.23.1",
36 | "styled-components": "^4.0.0",
37 | "webpack": "^3.10.0",
38 | "webpack-dev-server": "^2.9.7"
39 | },
40 | "peerDependencies": {
41 | "classnames": "^2.2.6",
42 | "prop-types": "^15.0.0 || ^16.0.0",
43 | "react": "^16.3.0",
44 | "react-dom": "^16.3.0",
45 | "react-redux": "^4.0.0 || ^5.0.0",
46 | "redux": "^3.0.0 || ^4.0.0",
47 | "reselect": "^4.0.0",
48 | "styled-components": "^4.0.0"
49 | },
50 | "homepage": "https://github.com/nghiepit/react-redux-modal-flex",
51 | "repository": {
52 | "type": "git",
53 | "url": "git+https://github.com/nghiepit/react-redux-modal-flex.git"
54 | },
55 | "bugs": {
56 | "url": "https://github.com/nghiepit/react-redux-modal-flex/issues"
57 | },
58 | "keywords": [
59 | "react",
60 | "redux",
61 | "modal",
62 | "popup",
63 | "dialog",
64 | "alert"
65 | ],
66 | "author": "Nghiep ",
67 | "license": "MIT"
68 | }
69 |
--------------------------------------------------------------------------------
/src/Modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import cls from 'classnames';
5 | import {connect} from 'react-redux';
6 | import {createStructuredSelector} from 'reselect';
7 | import {pick, is} from 'ramda';
8 |
9 | import {GlobalStyle} from './styled';
10 | import {actions as ModalActions, selectors as ModalSelectors} from './modules';
11 |
12 | import Content from './components/Content';
13 |
14 | const Wrapper = styled.div`
15 | position: fixed;
16 | width: 100%;
17 | height: 100%;
18 | top: 0px;
19 | left: 0px;
20 | overflow: hidden;
21 | z-index: 9999;
22 | display: ${props => (props.show ? 'block' : 'none')};
23 | `;
24 |
25 | class Modal extends React.Component {
26 | static propTypes = {
27 | show: PropTypes.bool.isRequired,
28 | Component: PropTypes.func,
29 | classContent: PropTypes.string.isRequired,
30 | animation: PropTypes.string.isRequired,
31 | duration: PropTypes.number.isRequired,
32 | mask: PropTypes.bool.isRequired,
33 | closeByMask: PropTypes.bool.isRequired,
34 | title: PropTypes.string,
35 | closeBtn: PropTypes.bool.isRequired,
36 | textOk: PropTypes.string,
37 | classOk: PropTypes.string.isRequired,
38 | actionOk: PropTypes.func,
39 | disabledOk: PropTypes.bool,
40 | textCancel: PropTypes.string,
41 | isFooter: PropTypes.bool.isRequired,
42 | toggleModal: PropTypes.func.isRequired,
43 | modifyOkModal: PropTypes.func.isRequired,
44 | };
45 |
46 | render() {
47 | const {
48 | show,
49 | classContent,
50 | animation,
51 | Component,
52 | toggleModal,
53 | modifyOkModal,
54 | } = this.props;
55 |
56 | return (
57 |
58 |
59 |
60 |
63 | {show && is(Function, Component) && (
64 |
68 | )}
69 |
70 |
71 |
72 | );
73 | }
74 | }
75 |
76 | export default connect(
77 | createStructuredSelector(
78 | pick(
79 | [
80 | 'show',
81 | 'Component',
82 | 'classContent',
83 | 'animation',
84 | 'duration',
85 | 'mask',
86 | 'closeByMask',
87 | 'title',
88 | 'closeBtn',
89 | 'textOk',
90 | 'classOk',
91 | 'actionOk',
92 | 'disabledOk',
93 | 'textCancel',
94 | 'isFooter',
95 | ],
96 | ModalSelectors,
97 | ),
98 | ),
99 | pick(['toggleModal', 'modifyOkModal'], ModalActions),
100 | )(Modal);
101 |
--------------------------------------------------------------------------------
/src/components/Content/components/Footer/components/Button/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import cls from 'classnames';
5 |
6 | const Wrapper = styled.span`
7 | color: #83878d;
8 | border-radius: 5px;
9 | cursor: pointer;
10 | padding: 8px 12px;
11 | text-transform: uppercase;
12 | transition: all 0.2s;
13 | &:hover {
14 | background: #f2fbff;
15 | color: #6d7379;
16 | }
17 | &.primary {
18 | color: #fc4a4a;
19 | }
20 | &.isDisabled {
21 | pointer-events: none;
22 | opacity: 0.6;
23 | }
24 | `;
25 |
26 | const Button = ({ children, isDisabled, onClick, primary, className }) => (
27 |
31 | {children}
32 |
33 | );
34 |
35 | Button.propTypes = {
36 | children: PropTypes.node.isRequired,
37 | primary: PropTypes.bool,
38 | isDisabled: PropTypes.bool,
39 | onClick: PropTypes.func,
40 | className: PropTypes.string,
41 | };
42 |
43 | export default Button;
44 |
--------------------------------------------------------------------------------
/src/components/Content/components/Footer/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 |
5 | import Button from './components/Button';
6 |
7 | const Wrapper = styled.footer`
8 | margin-top: auto;
9 | padding: 10px;
10 | border-top: 1px solid #e5e5e9;
11 | display: flex;
12 | justify-content: flex-end;
13 | align-items: center;
14 | flex: 1 0 auto;
15 | `;
16 |
17 | const Footer = ({
18 | textOk,
19 | classOk,
20 | textCancel,
21 | toggleModal,
22 | disabledOk,
23 | actionOk,
24 | }) => (
25 |
26 | {textCancel && (
27 |
28 | )}
29 | {textOk && (
30 |
38 | )}
39 |
40 | );
41 |
42 | Footer.propTypes = {
43 | textOk: PropTypes.string,
44 | classOk: PropTypes.string.isRequired,
45 | textCancel: PropTypes.string,
46 | toggleModal: PropTypes.func.isRequired,
47 | actionOk: PropTypes.func,
48 | disabledOk: PropTypes.bool.isRequired,
49 | className: PropTypes.string.isRequired,
50 | };
51 |
52 | export default Footer;
53 |
--------------------------------------------------------------------------------
/src/components/Content/components/Header/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 |
5 | const Wrapper = styled.h4`
6 | position: relative;
7 | flex: 1 0 auto;
8 | background: #fafafc;
9 | color: #111;
10 | border-bottom: 1px solid #ebebee;
11 | margin: 0px;
12 | padding: 6px 10px;
13 | font-weight: normal;
14 | text-align: left;
15 | `;
16 |
17 | const Close = styled.span`
18 | position: absolute;
19 | top: 50%;
20 | right: 3px;
21 | transform: translateY(-50%);
22 | cursor: pointer;
23 | right: 5px;
24 | width: 16px;
25 | height: 16px;
26 | &:before,
27 | &:after {
28 | position: absolute;
29 | content: '';
30 | height: 2px;
31 | width: 100%;
32 | top: 50%;
33 | left: 0;
34 | margin-top: -1px;
35 | background: #999;
36 | border-radius: 100%;
37 | transition: background 0.2s;
38 | }
39 | &:before {
40 | transform: rotate(45deg);
41 | }
42 | &:after {
43 | transform: rotate(-45deg);
44 | }
45 | &:hover:before,
46 | &:hover:after {
47 | background: #333;
48 | }
49 | `;
50 |
51 | const Header = ({ title, toggleModal, closeBtn }) =>
52 | title && (
53 |
54 | {title}
55 | {closeBtn && toggleModal(false)} />}
56 |
57 | );
58 |
59 | Header.propTypes = {
60 | title: PropTypes.string,
61 | toggleModal: PropTypes.func.isRequired,
62 | closeBtn: PropTypes.bool.isRequired,
63 | };
64 |
65 | export default Header;
66 |
--------------------------------------------------------------------------------
/src/components/Content/components/Main/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 |
5 | const Wrapper = styled.section`
6 | max-width: 100%;
7 | padding: 0px 10px;
8 | overflow: auto;
9 | > * {
10 | box-sizing: border-box;
11 | max-width: 100%;
12 | }
13 | `;
14 |
15 | const Main = ({ children }) => {children};
16 |
17 | Main.propTypes = {
18 | children: PropTypes.node,
19 | };
20 |
21 | export default Main;
22 |
--------------------------------------------------------------------------------
/src/components/Content/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 |
5 | import Header from './components/Header';
6 | import Footer from './components/Footer';
7 | import Main from './components/Main';
8 |
9 | const Wrapper = styled.section`
10 | position: absolute;
11 | width: 100%;
12 | height: 100%;
13 | top: 0px;
14 | left: 0px;
15 | display: flex;
16 | justify-content: center;
17 | align-items: center;
18 | background: rgba(0, 0, 0, 0.5);
19 | animation-duration: 0.2s;
20 | & > div.animated {
21 | display: flex;
22 | flex-direction: column;
23 | min-width: 320px;
24 | max-width: 98vw;
25 | max-height: 98vh;
26 | background: #fff;
27 | border-radius: 2px;
28 | border: 1px solid #ebebee;
29 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
30 | animation-duration: ${props => props.duration}ms;
31 | }
32 | `;
33 |
34 | class Content extends React.Component {
35 | static propTypes = {
36 | mask: PropTypes.bool.isRequired,
37 | className: PropTypes.string.isRequired,
38 | children: PropTypes.node,
39 | isFooter: PropTypes.bool.isRequired,
40 | duration: PropTypes.number.isRequired,
41 | toggleModal: PropTypes.func.isRequired,
42 | };
43 |
44 | onClick = ({ target }) => {
45 | const { mask, closeByMask, toggleModal } = this.props;
46 | if (mask && closeByMask && /modal-overlay/i.test(target.className)) {
47 | toggleModal(false);
48 | }
49 | };
50 |
51 | render() {
52 | const { mask, className, children, isFooter, duration } = this.props;
53 |
54 | return (
55 |
60 |
61 |
62 | {children}
63 | {isFooter && }
64 |
65 |
66 | );
67 | }
68 | }
69 |
70 | export default Content;
71 |
--------------------------------------------------------------------------------
/src/components/ModalDefault/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const ModalDefault = () => My modal content
;
4 |
5 | export default ModalDefault;
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Modal from './Modal';
2 | import reducer, {actions, selectors} from './modules';
3 |
4 | export {reducer, actions, selectors};
5 |
6 | export default Modal;
7 |
--------------------------------------------------------------------------------
/src/modules/actions.js:
--------------------------------------------------------------------------------
1 | export const TOGGLE_MODAL = 'modal/TOGGLE_MODAL';
2 | export const MODIFY_OK = 'modal/MODIFY_OK';
3 |
4 | export const toggleModal = payload => ({
5 | type: TOGGLE_MODAL,
6 | payload,
7 | });
8 |
9 | export const modifyOkModal = payload => ({
10 | type: MODIFY_OK,
11 | payload,
12 | });
13 |
--------------------------------------------------------------------------------
/src/modules/index.js:
--------------------------------------------------------------------------------
1 | import { mergeWithKey, merge, is, assoc } from 'ramda';
2 |
3 | import * as actions from './actions';
4 | import * as selectors from './selectors';
5 | import ModalDefault from '../components/ModalDefault';
6 |
7 | export { actions, selectors };
8 |
9 | export default (defaultState = {}) => {
10 | const enhancer = (k, l, r) => (k === 'ok' ? merge(l, r) : r);
11 | const initState = mergeWithKey(
12 | enhancer,
13 | {
14 | show: false,
15 | classContent: 'modal-content',
16 | animation: 'zoomIn',
17 | duration: 300,
18 | mask: true,
19 | closeByMask: true,
20 | component: ModalDefault,
21 | title: 'This is a title',
22 | closeBtn: true,
23 | textCancel: 'Cancel',
24 | ok: {
25 | text: 'OK',
26 | classOk: 'modal-btn-ok',
27 | disabled: false,
28 | action: () => console.log('OK clicked'),
29 | },
30 | },
31 | defaultState
32 | );
33 |
34 | return (state = initState, action) => {
35 | switch (action.type) {
36 | case actions.TOGGLE_MODAL:
37 | if (is(Object, action.payload)) {
38 | return mergeWithKey(
39 | enhancer,
40 | initState,
41 | merge({ show: true }, action.payload)
42 | );
43 | }
44 | return assoc('show', false, state);
45 | case actions.MODIFY_OK:
46 | return assoc('ok', merge(state.ok, action.payload), state);
47 | default:
48 | return state;
49 | }
50 | };
51 | };
52 |
--------------------------------------------------------------------------------
/src/modules/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | export const modal = state => state.modal;
4 |
5 | export const show = createSelector(modal, modal => modal.show);
6 |
7 | export const classContent = createSelector(modal, modal => modal.classContent);
8 |
9 | export const animation = createSelector(modal, modal => modal.animation);
10 |
11 | export const duration = createSelector(modal, modal => modal.duration);
12 |
13 | export const mask = createSelector(modal, modal => modal.mask);
14 |
15 | export const closeByMask = createSelector(modal, modal => modal.closeByMask);
16 |
17 | export const Component = createSelector(modal, modal => modal.component);
18 |
19 | export const title = createSelector(modal, modal => modal.title);
20 |
21 | export const closeBtn = createSelector(modal, modal => modal.closeBtn);
22 |
23 | export const textCancel = createSelector(modal, modal => modal.textCancel);
24 |
25 | export const ok = createSelector(modal, modal => modal.ok);
26 |
27 | export const textOk = createSelector(ok, ok => ok.text);
28 |
29 | export const classOk = createSelector(ok, ok => ok.classOk);
30 |
31 | export const actionOk = createSelector(ok, ok => ok.action);
32 |
33 | export const disabledOk = createSelector(ok, ok => ok.disabled);
34 |
35 | export const isFooter = createSelector(
36 | textCancel,
37 | textOk,
38 | (cancel, ok) => !!(ok || cancel)
39 | );
40 |
--------------------------------------------------------------------------------
/src/styled.js:
--------------------------------------------------------------------------------
1 | import {createGlobalStyle} from 'styled-components';
2 |
3 | export const GlobalStyle = createGlobalStyle`
4 | .animated {
5 | animation-duration: 1s;
6 | animation-fill-mode: both;
7 | will-change: transform, opacity;
8 | }
9 |
10 | .animated.infinite {
11 | animation-iteration-count: infinite;
12 | }
13 |
14 | @keyframes fadeIn {
15 | from {
16 | opacity: 0;
17 | }
18 |
19 | to {
20 | opacity: 1;
21 | }
22 | }
23 | .fadeIn {
24 | animation-name: fadeIn;
25 | }
26 |
27 | @keyframes zoomIn {
28 | from {
29 | opacity: 0;
30 | transform: scale3d(.3, .3, .3);
31 | }
32 |
33 | 50% {
34 | opacity: 1;
35 | }
36 | }
37 | .zoomIn {
38 | animation-name: zoomIn;
39 | }
40 | `;
41 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const webpack = require('webpack');
4 | const path = require('path');
5 |
6 | module.exports = {
7 | entry: {
8 | bundle: './example/index.js',
9 | },
10 |
11 | output: {
12 | filename: '[name].js',
13 | chunkFilename: '[id].chunk.js',
14 | publicPath: '/',
15 | },
16 |
17 | resolve: {
18 | extensions: ['.js', '.jsx'],
19 | },
20 |
21 | plugins: [
22 | new webpack.DefinePlugin({
23 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
24 | }),
25 | new webpack.optimize.UglifyJsPlugin({
26 | include: /\.min\.js$/,
27 | minimize: true,
28 | compress: {
29 | warnings: false,
30 | comparisons: false,
31 | },
32 | output: {
33 | comments: false,
34 | ascii_only: true,
35 | },
36 | }),
37 | new webpack.optimize.ModuleConcatenationPlugin(),
38 | new webpack.HotModuleReplacementPlugin(),
39 | ],
40 |
41 | module: {
42 | strictExportPresence: true,
43 | rules: [
44 | {
45 | test: /\.(js|jsx)$/,
46 | loader: require.resolve('babel-loader'),
47 | options: {
48 | compact: true,
49 | },
50 | },
51 | {
52 | test: /\.css$/,
53 | use: [
54 | {
55 | loader: 'style-loader',
56 | },
57 | {
58 | loader: 'css-loader',
59 | },
60 | ],
61 | },
62 | ],
63 | },
64 |
65 | devServer: {
66 | contentBase: './example/',
67 | hot: true,
68 | inline: true,
69 | compress: true,
70 | port: 2345,
71 | stats: {
72 | assets: true,
73 | children: false,
74 | chunks: false,
75 | hash: false,
76 | modules: false,
77 | publicPath: false,
78 | timings: false,
79 | version: false,
80 | warnings: true,
81 | colors: {
82 | green: '\u001b[32m',
83 | },
84 | },
85 | },
86 | };
87 |
--------------------------------------------------------------------------------