├── .editorconfig
├── .gitattributes
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── .vscode
├── settings.json
└── tasks.json
├── LICENSE
├── README.md
├── jest.config.js
├── package.json
├── src
├── InlineNotification.tsx
├── index.ts
└── notifications.redux.ts
├── style.css
├── tests
└── placeholder.spec.ts
├── tsconfig.build.json
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 4
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | gh-pages/
3 | dist-modules/
4 | node_modules/
5 |
6 | /dist/
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JakeGinnivan/react-redux-notifications/718880a0be13eb692751ea3ac7dfe82e44a52e54/.npmignore
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package.json
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "tabWidth": 4,
5 | "trailingComma": "all"
6 | }
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - lts/*
4 | cache: yarn
5 | notifications:
6 | email: false
7 | script: yarn verify
8 | env:
9 | global:
10 | - secure: e3y8xJ0Npx1q1pArGTrkfkGBJiN66xhhP2XvKz9gKrq7nKOdou9FOv3QaR6tCHfqcrKEmTpFMmyKGCHDPNU3Eqkn5IUMtck06hxYpzlun3K3JvmWhrMSrMixnTv7LrfnAnLmO/Mw7wMtGkEJM+1etkMG+aNN83AtBC/m4NOtLaIdfg8Jvxn37M47xNfBJmPIk0/RAIWphIczjZQDE5+H7lvYIj9R219wfKvwAWqWvWaAGvJ5+cD1C2XO53YMsPcFnNtJCHP2ftQPyF4e6VifJgoV31GQ/dWIi98phjd5KyhJqQVpHiUQM8W0nl9b/ctNWASo8RJDPAfX/IOg7aAmoN5hEuSnw/EocFJWatDUINClOGkAaoVOKMFA0+rIDiy9+4EnpQ9jgmp0Ybc7NK1KDY13nAq8hTmVgFUbkLZxhRQri2ndoPH6nbkXdID9lFA2ZC0354O39dCyA3AUsr7/0ik3xbjmXvc265cWTIJQeRHS/ZeUrTVrEwG4yNTN2UdjSxnxipMy0Ylxupc7cW3/WrRaW00IPT+vW3xLerdGyoG1yQ/vx6oJRhsdLdiJBQ+3PRGwIe+KM8FheFGxu8s2EEFKGralNURopXp6rhuh/Jy7wmhBKFb4unBpjxnry9SSFN8eUGWgCSSTNP48/x0xJ3CJLGU8UPLK37VdpfuPH94=
11 | - secure: iimxfkyM0AEtr5+roXFBiwCE6bTW9duaufD6lMIwTSkJOCZo5x4Dp/uojyHvY50rPB6xxPeBiWAeLkzxn8J8OIVY7ijB+qyvWJSpSrzDYqHAGwQKxpPHkGFHMlo7Qe4ymrrROkfqmIkQgPomHpqSXsii6mlCv5sadmixOku9S0MivQTzQLiLJhnaAtPcLFizaFdlDlXcHm3JjX0uHkNTdBFtFXX9eHKlx4cKmV9CUJ2OIdy9BhK2UEJf3j+bf7uyP5MXD+K6oTwNT+ESsJ6UU3BB81ZAvrmfdt57dCp0B3DNHS+jGljWwLyE/+MUgQXBJFoEVTsnrrcylboEYxPNtaN99hTZQMHvN6rzazP3RdeSfhNl1cJi2FsLS6SF3Y3RVVA1xYfGyeFgcNfU/V7iNKRHBZr7jdW11gMdeEhWJwj7DcVE23k6giRcxwOFVGyhp/x39QY71EnWnlpvD71E0FDfEOSazlEU+318d1e4i9kYPgj+fqhd+n654yTAdV9XcYqptdQOUC+nEC57/rVP97ZlB6dMKdgKPzmATh2us33MQFo631Wlg4FRH5UzJRldCKn9s3XqBs+wHgDyM84Ia+iQUFRlGCVCqZgwEVL2mqcijrZRxZuz2Dz3AwUjXoju4O1lNVHYHLAuVM9XbO5C7NvF9unUudJEA8P1EiGj6AQ=
12 | jobs:
13 | include:
14 | - stage: release
15 | node_js: lts/*
16 | deploy:
17 | provider: script
18 | skip_cleanup: true
19 | script:
20 | - npx semantic-release
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "build",
9 | "problemMatcher": [
10 | "$tsc"
11 | ]
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jake Ginnivan
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 | # react-redux-notifications
2 |
3 | [](https://travis-ci.com/JakeGinnivan/react-redux-notifications)
4 |
5 | Say you have a form with an API call and when the form submit succeeds you want to show the user a success message, or
6 | in the event of a failure, show them a failure message.
7 |
8 | React-redux-notifications is a redux middleware powered notification system which makes this super easy in a decoupled way.
9 |
10 | ## Setup
11 |
12 | ```js
13 | // Reducer setup
14 | import { reducer as notifications } from 'react-redux-notifications'
15 |
16 | const todoApp = combineReducers({
17 | notifications,
18 | ...restOfMiddleware,
19 | })
20 |
21 | // Middleware setup
22 | import { middleware as NotificationMiddleware } from 'react-redux-notifications'
23 |
24 | let store = createStore(
25 | myAppReducers,
26 | compose(
27 | applyMiddleware(ReduxThunk, NotificationMiddleware),
28 | window.devToolsExtension ? window.devToolsExtension() : _ => _,
29 | ),
30 | )
31 |
32 | // To use a different key to middleware
33 | import { createMiddleware } from 'react-redux-notifications'
34 | import { reducer as myCustomKey } from 'react-redux-notifications'
35 |
36 | const todoApp = combineReducers({
37 | myCustomKey,
38 | ...restOfMiddleware,
39 | })
40 |
41 | const NotificationMiddleware = createMiddleware('myCustomKey')
42 |
43 | let store = createStore(
44 | myAppReducers,
45 | compose(
46 | applyMiddleware(ReduxThunk, NotificationMiddleware),
47 | window.devToolsExtension ? window.devToolsExtension() : _ => _,
48 | ),
49 | )
50 |
51 |
55 | ```
56 |
57 | ## Example usage
58 |
59 | ```js
60 |
64 |
65 |
69 | ```
70 |
71 | ## API
72 |
73 | ### InlineNotification component
74 |
75 | - `triggeredBy` - either string or array of strings defining which redux event(s) to listen for
76 | - `defaultMessage` - The fallback message to render. `notificationMessage` on the triggering event type will take precident
77 | - `hideAfter` - Hide notification after specified number of ms
78 | - `showDismiss` - Show the default dismiss button
79 | - `renderDismiss` - Override the rendering of the dismiss button (this has no effect when renderNotification is specified, as dismiss is part of the notification)
80 | - `renderNotification` - Override rendering each notification `function(notification) { return
{notification.message}
}`
81 | - `renderContainer` - Override the container render `function(notifications) { return {notifications}
}`
82 | - `notifications` are the rendered components, you need to specify renderContainer and renderNotification separately
83 | - `reduxKey` - If not using the default `notifications` key for redux, your key can be specified here
84 |
85 | #### notification
86 |
87 | The notification object which is passed to `renderNotification`
88 |
89 | ```js
90 | {
91 | key: ''
92 | message: ''
93 | trigger: ''
94 | }
95 | ```
96 |
97 | ## License
98 |
99 | _react-redux-notifications_ is available under MIT. See LICENSE for more details.
100 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node'
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-notifications",
3 | "description": "Redux state driven notification components for React",
4 | "user": "JakeGinnivan",
5 | "author": "Jake Ginnivan",
6 | "version": "0.3.2",
7 | "scripts": {
8 | "prepack": "yarn verify && yarn build",
9 | "build": "yarn build:es2015 && yarn build:esm && yarn build:cjs",
10 | "build:es2015": "tsc --module es2015 --target es2015 --lib dom,es2015 --outDir dist/es2015 -p tsconfig.build.json",
11 | "build:esm": "tsc --module es2015 --target es5 --outDir dist/esm -p tsconfig.build.json",
12 | "build:cjs": "tsc --module commonjs --target es5 --outDir dist/cjs -p tsconfig.build.json",
13 | "lint": "yarn tslint --project tsconfig.build.json",
14 | "test": "jest",
15 | "verify": "yarn tsc -p tsconfig.json && yarn test && yarn lint",
16 | "semantic-release": "semantic-release"
17 | },
18 | "main": "dist/cjs/index.js",
19 | "module": "dist/esm/index.js",
20 | "es2015": "dist/es2015/index.js",
21 | "devDependencies": {
22 | "@types/jest": "^23.3.10",
23 | "@types/prop-types": "^15.5.8",
24 | "@types/react": "^16.8.2",
25 | "@types/react-redux": "^7.0.1",
26 | "jest": "^24.1.0",
27 | "prettier": "^1.16.4",
28 | "react": "^16.8.1",
29 | "react-addons-css-transition-group": "^15.6.2",
30 | "react-addons-test-utils": "^15.6.2",
31 | "react-dom": "^16.8.1",
32 | "react-redux": "^4.4.5",
33 | "redux": "^3.5.0",
34 | "semantic-release": "^15.13.3",
35 | "ts-jest": "^23.10.5",
36 | "tslint": "^5.12.1",
37 | "tslint-config-prettier": "^1.18.0",
38 | "tslint-eslint-rules": "^5.4.0",
39 | "typescript": "^3.3.3"
40 | },
41 | "peerDependencies": {
42 | "prop-types": "^15.6.2",
43 | "react": ">= 15.0.0 < 17.0.0",
44 | "react-redux": ">= 4.0.0 < 7.0.0",
45 | "redux": ">= 3.0.0 < 5.0.0"
46 | },
47 | "dependencies": {
48 | "redux-thunk": "^2.0.1"
49 | },
50 | "repository": {
51 | "type": "git",
52 | "url": "https://github.com/JakeGinnivan/ReduxNotifications.git"
53 | },
54 | "homepage": "https://github.com/JakeGinnivan/ReduxNotifications",
55 | "bugs": {
56 | "url": "https://github.com/JakeGinnivan/ReduxNotifications/issues"
57 | },
58 | "keywords": [
59 | "react",
60 | "redux",
61 | "notifications"
62 | ],
63 | "license": "MIT",
64 | "pre-push": [
65 | "test",
66 | "test:lint"
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/src/InlineNotification.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect, HandleThunkActionCreator } from 'react-redux'
4 | import {
5 | listen,
6 | unlisten,
7 | hide,
8 | Notification,
9 | NotificationsState,
10 | } from './notifications.redux'
11 |
12 | interface MappedStateProps {
13 | subscribers: NotificationsState['subscribers']
14 | }
15 | export interface OwnProps {
16 | defaultMessage?: string
17 | hideAfter?: number
18 | triggeredBy: string | string[]
19 | renderNotification?: (
20 | notification: Notification,
21 | dismissNotification: () => void,
22 | ) => React.ReactElement
23 | renderContainer?: (
24 | notifications: Array>,
25 | ) => React.ReactElement
26 | renderDismiss?: (dismiss: () => void) => React.ReactElement | null
27 | showDismiss?: boolean
28 | reduxKey?: string
29 | }
30 | interface DispatchProps {
31 | listen: HandleThunkActionCreator
32 | unlisten: HandleThunkActionCreator
33 | hide: HandleThunkActionCreator
34 | }
35 |
36 | type Props = DispatchProps & OwnProps & MappedStateProps
37 |
38 | class InlineNotification extends React.Component {
39 | static idSeed = 1
40 | static propTypes = {
41 | triggeredBy: PropTypes.oneOfType([
42 | PropTypes.string.isRequired,
43 | PropTypes.arrayOf(PropTypes.string.isRequired),
44 | ]).isRequired,
45 | defaultMessage: PropTypes.string,
46 | hideAfter: PropTypes.number,
47 | renderNotification: PropTypes.func,
48 | renderContainer: PropTypes.func,
49 | renderDismiss: PropTypes.func,
50 | showDismiss: PropTypes.bool,
51 | }
52 |
53 | componentId = `inline-${InlineNotification.idSeed++}`
54 |
55 | componentDidMount() {
56 | this.dispatchListen(this.props)
57 | }
58 |
59 | componentWillUnmount() {
60 | this.props.unlisten(this.componentId)
61 | }
62 |
63 | componentDidUpdate(prevProps: Props) {
64 | if (
65 | this.props.triggeredBy !== prevProps.triggeredBy ||
66 | this.props.hideAfter !== prevProps.hideAfter ||
67 | this.props.defaultMessage !== prevProps.defaultMessage
68 | ) {
69 | this.componentWillUnmount()
70 | this.dispatchListen(prevProps)
71 | }
72 | }
73 |
74 | dispatchListen(props: Props) {
75 | this.props.listen({
76 | componentId: this.componentId,
77 | triggeredBy: Array.isArray(props.triggeredBy)
78 | ? props.triggeredBy
79 | : [props.triggeredBy],
80 | hideAfter: props.hideAfter || undefined,
81 | defaultMessage: props.defaultMessage || undefined,
82 | })
83 | }
84 |
85 | dismiss = (notification: Notification) => {
86 | this.props.hide(this.componentId, notification.key)
87 | }
88 |
89 | renderNotification(
90 | notification: Notification,
91 | dismiss: React.ReactElement | null,
92 | ): React.ReactElement {
93 | return (
94 |
95 | {notification.message}
96 | {dismiss}
97 |
98 | )
99 | }
100 |
101 | renderContainer(notifications: Array>) {
102 | return {notifications}
103 | }
104 |
105 | render() {
106 | const renderContainer =
107 | this.props.renderContainer || this.renderContainer
108 | const componentState = this.props.subscribers[this.componentId]
109 |
110 | return renderContainer(
111 | componentState
112 | ? componentState.notifications.map(n => {
113 | const dismiss = () => this.dismiss(n)
114 | if (this.props.renderNotification) {
115 | return this.props.renderNotification(n, dismiss)
116 | }
117 |
118 | const dismissEl = this.props.renderDismiss ? (
119 | this.props.renderDismiss(dismiss)
120 | ) : this.props.showDismiss ? (
121 |
127 | ) : null
128 | return this.renderNotification(n, dismissEl)
129 | })
130 | : [],
131 | )
132 | }
133 | }
134 |
135 | export default connect<
136 | MappedStateProps,
137 | DispatchProps,
138 | OwnProps,
139 | { [key: string]: NotificationsState }
140 | >(
141 | (state, ownProps) => {
142 | const reduxKey = ownProps.reduxKey || 'notifications'
143 | const notificationState = state[reduxKey]
144 | if (!notificationState) {
145 | throw new Error(`Missing ${reduxKey} key in redux state`)
146 | }
147 | return {
148 | subscribers: notificationState.subscribers,
149 | }
150 | },
151 | {
152 | listen,
153 | hide,
154 | unlisten,
155 | },
156 | )(InlineNotification)
157 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import InlineNotification from './InlineNotification.js'
2 | import reducer, {
3 | middleware,
4 | NotificationsState,
5 | NotificationActions,
6 | Notification,
7 | } from './notifications.redux'
8 |
9 | export {
10 | reducer,
11 | middleware,
12 | InlineNotification,
13 | NotificationsState,
14 | NotificationActions,
15 | Notification,
16 | }
17 |
--------------------------------------------------------------------------------
/src/notifications.redux.ts:
--------------------------------------------------------------------------------
1 | import redux, { AnyAction } from 'redux'
2 |
3 | export interface ComponentState {
4 | options: ListenOptions
5 | notifications: Notification[]
6 | }
7 | export interface NotificationsState {
8 | subscribers: {
9 | [componentId: string]: ComponentState
10 | }
11 | }
12 |
13 | export interface Notification {
14 | key: string
15 | message: string
16 | trigger: AnyAction
17 | }
18 |
19 | export interface ListeningTo {
20 | hideAfter?: number | null
21 | defaultMessage?: string | null
22 | showDismiss: boolean
23 | }
24 |
25 | export const LISTEN_TO = 'notifications/listenTo'
26 | export const STOP_LISTEN = 'notifications/stopListen'
27 | export const SHOW_NOTIFICATION = 'notification/showNotification'
28 | export const HIDE_NOTIFICATION = 'notification/hideNotification'
29 |
30 | export interface ListenOptions {
31 | componentId: string
32 | triggeredBy: string[]
33 | hideAfter: number | undefined
34 | defaultMessage: string | undefined
35 | }
36 |
37 | export function listen(options: ListenOptions): ListenToAction {
38 | return { options, type: LISTEN_TO }
39 | }
40 |
41 | export function unlisten(componentId: string): StopListenAction {
42 | return {
43 | type: STOP_LISTEN,
44 | componentId,
45 | }
46 | }
47 |
48 | export function hide(componentId: string, key: string): HideNotificationAction {
49 | return {
50 | type: HIDE_NOTIFICATION,
51 | componentId,
52 | notificationKey: key,
53 | }
54 | }
55 |
56 | let keySeed = 0
57 |
58 | export function createMiddleware(key: string): redux.Middleware {
59 | return store => next => action => {
60 | const result = next(action)
61 | const state: NotificationsState | undefined = (store.getState() as any)[
62 | key
63 | ]
64 |
65 | if (!state) {
66 | throw new Error(
67 | `No state key ${key}, ensure notifications middleware is registered`,
68 | )
69 | }
70 |
71 | const notificationKey = `notification_${keySeed++}`
72 | const notificationMessage =
73 | action && (action as any).notificationMessage
74 |
75 | for (const componentId in state.subscribers) {
76 | if (state.subscribers.hasOwnProperty(componentId)) {
77 | const subscriber = state.subscribers[componentId]
78 |
79 | if (
80 | subscriber.options.triggeredBy.indexOf(action.type) !== -1
81 | ) {
82 | const show: ShowNotificationAction = {
83 | type: SHOW_NOTIFICATION,
84 | componentId,
85 | notification: {
86 | key: notificationKey,
87 | message:
88 | notificationMessage ||
89 | subscriber.options.defaultMessage,
90 | trigger: action,
91 | },
92 | }
93 | store.dispatch(show)
94 |
95 | if (subscriber.options.hideAfter) {
96 | setTimeout(() => {
97 | const hideAction: HideNotificationAction = {
98 | type: HIDE_NOTIFICATION,
99 | notificationKey,
100 | componentId,
101 | }
102 | store.dispatch(hideAction)
103 | }, subscriber.options.hideAfter)
104 | }
105 | }
106 | }
107 | }
108 |
109 | return result
110 | }
111 | }
112 |
113 | export const middleware: redux.Middleware = createMiddleware('notifications')
114 |
115 | export interface ListenToAction {
116 | type: typeof LISTEN_TO
117 | options: ListenOptions
118 | }
119 | export interface StopListenAction {
120 | type: typeof STOP_LISTEN
121 | componentId: string
122 | }
123 | export interface ShowNotificationAction {
124 | type: typeof SHOW_NOTIFICATION
125 | componentId: string
126 | notification: Notification
127 | }
128 | export interface HideNotificationAction {
129 | type: typeof HIDE_NOTIFICATION
130 | componentId?: string
131 | notificationKey: string
132 | }
133 | export type NotificationActions =
134 | | ListenToAction
135 | | StopListenAction
136 | | ShowNotificationAction
137 | | HideNotificationAction
138 |
139 | const defaultState: NotificationsState = { subscribers: {} }
140 |
141 | // tslint:disable-next-line:no-shadowed-variable
142 | const reducer: redux.Reducer = function reducer(
143 | state = defaultState,
144 | action: NotificationActions | AnyAction,
145 | ): NotificationsState {
146 | const narrowedActions = action as NotificationActions
147 | switch (narrowedActions.type) {
148 | case LISTEN_TO: {
149 | return {
150 | ...state,
151 | subscribers: {
152 | ...state.subscribers,
153 | [narrowedActions.options.componentId]: {
154 | options: narrowedActions.options,
155 | notifications: [],
156 | },
157 | },
158 | }
159 | }
160 |
161 | case STOP_LISTEN: {
162 | const newListeningTo: NotificationsState = {
163 | ...state,
164 | subscribers: { ...state.subscribers },
165 | }
166 | delete newListeningTo.subscribers[narrowedActions.componentId]
167 |
168 | return newListeningTo
169 | }
170 |
171 | case SHOW_NOTIFICATION: {
172 | return {
173 | ...state,
174 | subscribers: {
175 | ...state.subscribers,
176 | [narrowedActions.componentId]: {
177 | ...state.subscribers[narrowedActions.componentId],
178 | options:
179 | state.subscribers[narrowedActions.componentId]
180 | .options,
181 | notifications: [
182 | ...state.subscribers[narrowedActions.componentId]
183 | .notifications,
184 | narrowedActions.notification,
185 | ],
186 | },
187 | },
188 | }
189 | }
190 | case HIDE_NOTIFICATION: {
191 | const newState: NotificationsState = {
192 | ...state,
193 | subscribers: {
194 | ...state.subscribers,
195 | },
196 | }
197 |
198 | if (narrowedActions.componentId) {
199 | const existing =
200 | newState.subscribers[narrowedActions.componentId]
201 | newState.subscribers[narrowedActions.componentId] = {
202 | options: existing.options,
203 | notifications: existing.notifications.filter(
204 | val => val.key !== narrowedActions.notificationKey,
205 | ),
206 | }
207 | } else {
208 | for (const subscriberId in newState.subscribers) {
209 | if (newState.subscribers.hasOwnProperty(subscriberId)) {
210 | const subscriber = newState.subscribers[subscriberId]
211 |
212 | const filtered = subscriber.notifications.filter(
213 | notification =>
214 | notification.key !==
215 | narrowedActions.notificationKey,
216 | )
217 | if (
218 | filtered.length !== subscriber.notifications.length
219 | ) {
220 | newState.subscribers[subscriberId] = {
221 | options:
222 | newState.subscribers[subscriberId].options,
223 | notifications: filtered,
224 | }
225 | }
226 | }
227 | }
228 | }
229 |
230 | return newState
231 | }
232 | }
233 |
234 | return state
235 | }
236 |
237 | export default reducer
238 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | /* TODO: insert default styles of your component here for easy access */
2 |
3 | .notification {
4 | border-radius: 0.25rem;
5 | padding: 0.9375rem;
6 | border: #000 1px solid;
7 | background: lightgreen;
8 | }
9 |
10 | .notification_dismiss {
11 | float: right;
12 | position: relative;
13 | font-size: 1.3125rem;
14 | font-weight: 700;
15 | line-height: 1;
16 | color: #000;
17 | text-shadow: 0 1px 0 #fff;
18 | opacity: 0.2;
19 | cursor: pointer;
20 | background: unset;
21 | }
22 |
23 | .notification_dismiss:hover {
24 | opacity: 0.5;
25 | }
26 |
--------------------------------------------------------------------------------
/tests/placeholder.spec.ts:
--------------------------------------------------------------------------------
1 | it('placeholder', () => {
2 | // TODO: test something now
3 | expect(true).toBe(true)
4 | })
5 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/index.ts"],
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "importHelpers": true /* Import emit helpers from 'tslib'. */,
6 | "noEmit": false /* Do not emit outputs. */,
7 | "declaration": true /* Generates corresponding '.d.ts' file. */,
8 | "sourceMap": true /* Generates corresponding '.map' file. */,
9 |
10 | /* Additional Checks */
11 | "noUnusedLocals": true /* Report errors on unused locals. */,
12 | "noUnusedParameters": true /* Report errors on unused parameters. */,
13 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
14 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["node_modules", "dist", "src/type-safety-fixtures"],
3 | "compilerOptions": {
4 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
6 | "lib": [
7 | "dom",
8 | "es5"
9 | ] /* Specify library files to be included in the compilation. */,
10 | "noEmit": true /* Do not emit outputs. */,
11 | "strict": true /* Enable all strict type-checking options. */,
12 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
13 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
14 | "jsx": "react"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:latest", "tslint-eslint-rules", "tslint-config-prettier"],
3 | "rules": {
4 | "ordered-imports": false,
5 | "member-ordering": false,
6 | "object-literal-sort-keys": false,
7 | "member-access": false,
8 | "interface-name": false
9 | }
10 | }
11 |
--------------------------------------------------------------------------------