├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── .nvmrc
├── LICENSE
├── README.md
├── examples
├── components
│ ├── App
│ │ ├── App.js
│ │ └── App.scss
│ ├── Button
│ │ ├── Button.js
│ │ └── Button.scss
│ ├── Logo
│ │ └── Logo.js
│ ├── Modal
│ │ ├── Modal.js
│ │ └── Modal.scss
│ └── ModalLogin
│ │ ├── ModalLogin.js
│ │ └── ModalLogin.scss
├── containers
│ ├── SignalContainer.js
│ └── SignalOverlayContainer.js
├── index.html
├── index.js
├── index.scss
├── reducers.js
└── webpack.config.js
├── gulpfile.babel.js
├── logo.png
├── package-lock.json
├── package.json
├── src
├── __tests__
│ ├── index.spec.js
│ └── reducer.spec.js
├── actions.js
├── constants
│ ├── ActionTypes.js
│ ├── ModalStates.js
│ ├── SignalEvents.js
│ └── SignalTypes.js
├── createContainer.js
├── eventHandler.js
├── index.js
├── isModal.js
├── reducer.js
├── selectors.js
├── utils.js
└── withSignal.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015-no-commonjs",
4 | "react",
5 | "stage-2"
6 | ],
7 | "env": {
8 | "development": {
9 | "plugins": [
10 | "transform-es2015-modules-commonjs"
11 | ]
12 | },
13 | "test": {
14 | "plugins": [
15 | "transform-es2015-modules-commonjs",
16 | "transform-react-jsx-source",
17 | "istanbul"
18 | ]
19 | },
20 | "production": {
21 | "plugins": [
22 | "transform-es2015-modules-commonjs"
23 | ]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | insert_final_newline = true
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | trim_trailing_whitespace = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
13 | [*.js]
14 | indent_style = space
15 | indent_size = 2
16 | trim_trailing_whitespace = false
17 | insert_final_newline = true
18 | max_line_length = f
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | webpack.config*.js
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "standard",
5 | "standard-react",
6 | "plugin:jest/recommended"
7 | ],
8 | "env": {
9 | "browser": true,
10 | "mocha": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 | *.iml
4 | .nyc_output
5 | coverage
6 | flow-coverage
7 | node_modules
8 | dist
9 | lib
10 | es
11 | npm-debug.log
12 | .DS_Store
13 | stats.json
14 | dist-examples
15 | .publish
16 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | examples
2 | scripts
3 | docs
4 | .babelrc
5 | .eslint*
6 | .idea
7 | .editorconfig
8 | .npmignore
9 | .nyc_output
10 | .travis.yml
11 | webpack.*
12 | coverage
13 | package-lock.json
14 | yarn.lock
15 | .publish
16 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 9.8.0
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Mike Vercoelen
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 | [](http://mikevercoelen.github.io/redux-signal/)
2 |
3 | # Redux Signal
4 |
5 | Flexible, scalable library for creating modals with React and Redux.
6 |
7 | * `signals`: small, quick notification modals (confirmations, alerts etc.)
8 | * `modals`: fully customizable modals for your forms etc.
9 |
10 | ## Demo
11 |
12 | [mikevercoelen.github.io/redux-signal](http://mikevercoelen.github.io/redux-signal/)
13 |
14 | ## Example
15 |
16 | ```js
17 | import React from 'react'
18 | import { withSignal, SignalTypes } from 'redux-signal'
19 |
20 | // As an example, this app has a ProfileView. The user can upload an avatar,
21 | // and if the avatar file is too large, we want to display a notification popup
22 |
23 | class ProfileView extends React.Component {
24 | onAvatarFileUpload = () => {
25 | if (avatarIsToLarge) { // use your imagination...
26 | this.props.createSignal({
27 | type: SignalTypes.OK,
28 | title: 'Warning',
29 | message: 'The file was too large'
30 | })
31 | }
32 | }
33 |
34 | render () {
35 | // ...
36 | }
37 | }
38 |
39 | export default withSignal(ProfileView)
40 | ```
41 |
42 | More example code [available here](https://github.com/mikevercoelen/redux-signal/tree/master/examples)
43 |
44 | ## Table of Contents
45 |
46 | - [Installation](#installation)
47 | - [Introduction](#introduction)
48 | - [The problem](#the-problem)
49 | - [How does it work](#how-does-it-work?)
50 | - [Setup](#setup)
51 | - [Reducer](#reducer-setup)
52 | - [SignalContainer](#signalcontainer-setup)
53 | - [Signals](#signals)
54 | - [Signal types](#signal-types)
55 | - [Handling events](#handling-events)
56 | - [Modals](#modals)
57 | - [Overlay](#overlay)
58 | - [API](#api)
59 | - [createContainer](#createcontainer)
60 | - [withSignal](#withsignal)
61 | - [createSignal](#createsignal)
62 | - [getHasVisibleModal](#gethasvisiblemodal)
63 |
64 |
65 | ## Installation
66 |
67 | `npm install redux-signal --save`
68 |
69 | ## Introduction
70 |
71 | ### The problem
72 | Setting up a flexible solution for modals with Redux is hard. Our apps mostly need 2 types of modals: simple modals (such as confirms / warnings etc.) and custom modals.
73 |
74 | We might need multiple modals open at once, so we need some stacking order and we only want to display one overlay component. We also want our Redux state to be clean and serializable.
75 |
76 | So meet Redux Signal, hi.
77 |
78 | In Redux Signal there are 2 types of modals: ***signals*** and ***modals***. Signals are simple notifications: like warnings when things go wrong, confirmations when a user tries to removing an item, or user feedback when an item has been removed etc. Modals are fully customizable, modals. Like login popups etc.
79 |
80 | ***NOTE***:
81 |
82 | Redux Signal is not a library for the styling of your modals, that is your responsibility, however we made [`react-modal-construction-kit`](https://github.com/mikevercoelen/react-modal-construction-kit) for making your life even easier.
83 |
84 | Check out the [examples](https://github.com/mikevercoelen/redux-signal/tree/master/examples) for more info.
85 |
86 | ### How does it work?
87 | Redux Signal uses an `event / feedback queue` mechanism for signals so we handle events in a clean way, without cluttering our app state with functions etc. See [Handling events](#handling-events) for more info.
88 |
89 | ## Setup
90 |
91 | ### Reducer setup
92 |
93 | The first thing you need to do is to include the signal reducer in your rootReducer. Please make sure it's mounted at your rootReducer as `signal`, we are working on making this flexible in the future.
94 |
95 | `reducers/index.js`
96 |
97 | ```js
98 | import { combineReducers } from 'redux'
99 | import { reducer as signalReducer } from 'redux-signal'
100 |
101 | export const rootReducer = combineReducers({
102 | signal: signalReducer
103 | })
104 | ```
105 |
106 | ### SignalContainer setup
107 |
108 | The second thing you need to do, is to create a `SignalContainer`. Again: Redux-signal is not responsible for your Modal look and feel, you need your own Modal component ([`react-modal-construction-kit`](https://github.com/mikevercoelen/react-modal-construction-kit))
109 |
110 | The `SignalContainer` is ***the link between signal and your modal component***
111 |
112 | So let's create a `SignalContainer` component:
113 |
114 | `containers/SignalContainer.js`
115 |
116 | ```js
117 | import React from 'react'
118 | import PropTypes from 'prop-types'
119 |
120 | // These are your application specific components we use in this demo example
121 | import Button from '../components/Button/Button'
122 | import Modal from '../components/Modal/Modal'
123 |
124 | import {
125 | createContainer,
126 | SignalEvents,
127 | SignalTypes
128 | } from 'redux-signal'
129 |
130 | const SignalContainer = ({
131 | event,
132 | destroy,
133 | close,
134 | modal
135 | }) => {
136 | // modal contains all the properties you submit when calling `createSignal`, so you have all the freedom
137 | // to do whatever you want (title, message, isRequired) only isFirst and isVisible are required.
138 |
139 | return (
140 | event(modal, eventType))}>
145 | {modal.message}
146 |
147 | )
148 | }
149 |
150 | SignalContainer.propTypes = {
151 | event: PropTypes.func,
152 | destroy: PropTypes.func,
153 | close: PropTypes.func,
154 | modal: PropTypes.object
155 | }
156 |
157 | function getModalLabel (modal, labelType, otherwise) {
158 | return (modal.labels && modal.labels[labelType]) || {otherwise}
159 | }
160 |
161 | function getFooter (modal, onModalEvent) {
162 | switch (modal.type) {
163 | case SignalTypes.YES_NO:
164 | return [
165 | ,
171 |
177 | ]
178 | case SignalTypes.YES_NO_CANCEL:
179 | return [
180 | ,
185 | ,
191 |
197 | ]
198 |
199 | case SignalTypes.OK_CANCEL:
200 | return [
201 | ,
206 |
212 | ]
213 | case SignalTypes.OK:
214 | return (
215 |
220 | )
221 | }
222 |
223 | return null
224 | }
225 |
226 | export default createContainer(SignalContainer)
227 | ```
228 |
229 | Once you've created the `SignalContainer` (which, again, is the link between your Modal and `redux-signal`) you have to use it somewhere in your application.
230 | The most logical place would be your main layout, use it like so:
231 |
232 | ```js
233 |
234 | ```
235 |
236 | Now you've setup everything you need for `redux-signal` and can start using `createSignal` to show `signals`.
237 |
238 | ## Signals
239 |
240 | ### Showing a signal
241 |
242 | 1. Wrap the component where you want to show a signal with [`withSignal`](#withsignal), which injects the component with a few props from which one is called: `createSignal`.
243 | 2. Use [`createSignal`](#createsignal) to show a signal.
244 |
245 | Example:
246 |
247 | `components/Demo.js`
248 |
249 | ```js
250 | import React from 'react'
251 |
252 | import {
253 | withSignal,
254 | withSignalPropTypes,
255 | SignalTypes
256 | } from 'redux-signal'
257 |
258 | const Demo = ({ createSignal }) => {
259 | return (
260 |
261 |
271 |
272 | )
273 | }
274 |
275 | Demo.propTypes = {
276 | ...withSignalPropTypes
277 | }
278 |
279 | export default withSignal(Demo)
280 | ```
281 |
282 | ### Signal types
283 |
284 | There are 4 `SignalTypes`:
285 |
286 | * `OK`
287 | * `OK_CANCEL`
288 | * `YES_NO`
289 | * `YES_NO_CANCEL`
290 |
291 | It's your responsibility for handling the signal type in the `SignalContainer`, in our example [SignalContainer](#signalcontainer-setup) you can see the `type` is being used to show different buttons.
292 |
293 | More info see [createSignal API](#createsignal)
294 |
295 | ### Handling events
296 |
297 | Lets say you want to have a confirmation popup when a user wants to remove an item. You want to handle the events when clicked on the `yes` button or `no` button. You can do so by using `eventHandler`
298 |
299 | ```js
300 | import React from 'react'
301 |
302 | import {
303 | withSignal,
304 | withSignalPropTypes,
305 | SignalTypes,
306 | eventHandler
307 | } from 'redux-signal'
308 |
309 | const KillTheWorldEvents = eventHandler()
310 |
311 | const Demo = ({ createSignal }) => {
312 | return (
313 |
314 |
325 | window.alert("You have killed the world.")}
327 | onNo={() => window.alert("Thank god, you are a good kid."} />
328 |
329 | )
330 | }
331 |
332 | Demo.propTypes = {
333 | ...withSignalPropTypes
334 | }
335 |
336 | export default withSignal(Demo)
337 | ```
338 |
339 | ## Modals
340 |
341 | TODO: for now check out the [modal examples code](https://github.com/mikevercoelen/redux-signal/blob/master/examples/components/ModalLogin/ModalLogin.js).
342 |
343 | ## Overlay
344 |
345 | TODO: for now check out the [SignalOverlayContainer in the examples code](https://github.com/mikevercoelen/redux-signal/blob/master/examples/containers/SignalOverlayContainer.js).
346 |
347 | ## API
348 |
349 | ### createContainer
350 |
351 | The following props will be available once a component has been wrapped with `createContainer`:
352 |
353 | | Property | Type | Description |
354 | |:---|:---|:---|
355 | | `event` | function | Dispatch a signal event |
356 | | `close` | function | Closes the signal, not to be confused with `destroy`, which removes the DOM element. Close should be used to close, destroy on transition end |
357 | | `destroy` | function | Destroys the signal, should be used on transition end when using a transitioned modal, see examples for more info on it's use |
358 | | `modal` | object | All the props passed to `createSignal` |
359 |
360 | ### withSignal
361 |
362 | The following props will be available once a component has been wrapped with `withSignal`:
363 |
364 | | Property | Type | Description |
365 | |:---|:---|:---|
366 | | `createSignal` | function({ type, ...props }) | See [createSignal](#createsignal) |
367 | | `signalEvent` | function | Dispatch a signal event |
368 | | `setModalState` | function(modalId, ModalState) | Set the state of a modal |
369 | | `showModal` | function(modalId) | Shows a modal |
370 | | `hideModal` | function(modalId) | Hides a modal |
371 |
372 | ### createSignal
373 |
374 | This method will be available in the props of a component wrapped with `withSignal`
375 |
376 | The method expects an object with the following parameters:
377 |
378 | | Property | Type | Default | Description |
379 | |:---|:---|:---|:---|
380 | | `type` | SignalType (enum) | - | The type of Signal see: [Signal types](#signal-types) |
381 | | `eventHandler` | EventHandler | - | ***(optional)*** pass in an EventHandler to handle events |
382 | | `...props` | - | - | All other props passed to `createSignal` can be accessed in your `SignalContainer`'s `modal` object prop, see [createContainer](#createcontainer) |
383 |
384 | ### getHasVisibleModal
385 |
386 | This is a selector, and returns true or false if a modal OR signal is visible, this can be used to render an overlay component
387 |
388 | | Property | Type | Default | Description |
389 | |:---|:---|:---|:---|
390 | | `state` | object | - | Your application state |
391 |
--------------------------------------------------------------------------------
/examples/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Button from '../Button/Button'
3 | import Logo from '../Logo/Logo'
4 | import ModalLogin from '../ModalLogin/ModalLogin'
5 | import styles from './App.scss'
6 |
7 | import {
8 | withSignal,
9 | withSignalPropTypes,
10 | SignalTypes,
11 | eventHandler
12 | } from '../../../src/index'
13 |
14 | const modalLoginId = '@app/modal-login'
15 | const KillTheWorldEvent = eventHandler()
16 |
17 | const App = ({ createSignal, showModal }) => {
18 | const onBtnKillClick = () => {
19 | createSignal({
20 | type: SignalTypes.YES_NO,
21 | title: 'Are you sure?',
22 | message: 'You are about to kill the world, are you sure?',
23 | labels: {
24 | yes: 'Yes, kill it!',
25 | no: 'No, there is still hope'
26 | },
27 | eventHandler: KillTheWorldEvent
28 | })
29 | }
30 |
31 | const onBtnErrorClick = () => {
32 | createSignal({
33 | type: SignalTypes.OK,
34 | title: 'Oeps',
35 | message: 'Something has gone wrong'
36 | })
37 | }
38 |
39 | const onBtnLoginClick = () => {
40 | showModal(modalLoginId)
41 | }
42 |
43 | const onYes = () => {
44 | console.log('You killed everyone, you must be proud.')
45 | }
46 |
47 | const onNo = () => {
48 | console.log('That was close, we need more people like you.')
49 | }
50 |
51 | return (
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Signals
60 |
61 |
62 | Quick popups that require no custom logic (confirms, notifications etc.)
63 |