├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── README.md
├── build
├── webpack.config.js
└── webpack
│ ├── base.js
│ ├── development.js
│ └── production.js
├── config
└── index.js
├── development
├── client.js
└── index.less
├── package.json
└── src
├── Item.js
├── Notificaton.js
├── Tag.js
├── content.js
├── footer.js
├── header.js
├── index.js
├── less
├── index.less
├── notification.less
└── notifications.less
└── utils.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "syntax-class-properties",
4 | "transform-class-properties",
5 | "transform-decorators-legacy",
6 | "transform-es2015-arrow-functions",
7 | "transform-es2015-block-scoping",
8 | ["transform-es2015-classes", {"loose": true}],
9 | "transform-proto-to-assign"
10 | ],
11 | "presets": ["stage-0", "react", "es2015"]
12 | }
13 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
2 | node_modules
3 | demo
4 | .DS_Store
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | },
7 | "ecmaFeatures": {
8 | "jsx": true
9 | },
10 | "plugins": [
11 | "react"
12 | ],
13 | "rules": {
14 | "comma-dangle": [2, "never"],
15 | "no-cond-assign": 2,
16 | "no-constant-condition": 2,
17 | "no-control-regex": 2,
18 | "no-debugger": 2,
19 | "no-dupe-keys": 2,
20 | "no-empty": 2,
21 | "no-empty-character-class": 2,
22 | "no-ex-assign": 2,
23 | "no-extra-boolean-cast": 2,
24 | "no-extra-parens": 0,
25 | "no-extra-semi": 2,
26 | "no-func-assign": 2,
27 | "no-inner-declarations": 2,
28 | "no-invalid-regexp": 2,
29 | "no-irregular-whitespace": 2,
30 | "no-negated-in-lhs": 2,
31 | "no-obj-calls": 2,
32 | "no-regex-spaces": 2,
33 | "no-reserved-keys": 0,
34 | "no-sparse-arrays": 2,
35 | "no-unreachable": 2,
36 | "strict": [2, "global"],
37 | "no-catch-shadow": 2,
38 | "no-delete-var": 2,
39 | "no-label-var": 2,
40 | "no-shadow": 2,
41 | "no-shadow-restricted-names": 2,
42 | "no-undef": 2,
43 | "no-undef-init": 2,
44 | "no-undefined": 2,
45 | "no-unused-vars": 2,
46 | "no-use-before-define": 2,
47 | "indent": [2, 4, {
48 | "SwitchCase": 1
49 | }],
50 | "brace-style": 2,
51 | "camelcase": 0,
52 | "comma-spacing": 2,
53 | "comma-style": 2,
54 | "consistent-this": 0,
55 | "eol-last": 2,
56 | "func-names": 0,
57 | "func-style": 0,
58 | "key-spacing": [2, {
59 | "beforeColon": false,
60 | "afterColon": true
61 | }],
62 | "max-nested-callbacks": 0,
63 | "new-cap": 2,
64 | "new-parens": 2,
65 | "no-array-constructor": 2,
66 | "no-inline-comments": 0,
67 | "no-lonely-if": 2,
68 | "no-mixed-spaces-and-tabs": 2,
69 | "no-nested-ternary": 2,
70 | "no-new-object": 2,
71 | "semi-spacing": [2, {
72 | "before": false,
73 | "after": true
74 | }],
75 | "no-spaced-func": 2,
76 | "no-ternary": 0,
77 | "no-trailing-spaces": 2,
78 | "no-multiple-empty-lines": 2,
79 | "no-underscore-dangle": 0,
80 | "one-var": 0,
81 | "operator-assignment": [2, "always"],
82 | "padded-blocks": 0,
83 | "quotes": [2, "single"],
84 | "quote-props": [2, "as-needed"],
85 | "semi": [2, "always"],
86 | "sort-vars": [2, {"ignoreCase": true}],
87 | "space-before-blocks": 2,
88 | "object-curly-spacing": [2, "never"],
89 | "array-bracket-spacing": [2, "never"],
90 | "space-in-parens": 2,
91 | "space-infix-ops": 2,
92 | "keyword-spacing": 2,
93 | "space-unary-ops": 2,
94 | "spaced-comment": 2,
95 | "wrap-regex": 0,
96 | "use-isnan": 2,
97 | "valid-jsdoc": 0,
98 | "valid-typeof": 2,
99 | "react/display-name": 1,
100 | "react/jsx-no-undef": 1,
101 | "react/jsx-uses-react": 1,
102 | "react/jsx-uses-vars": 1
103 | }
104 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | 0.*
4 | 1.*
5 | lib
6 | fonts/
7 | dist
8 | typings/
9 | tsconfig.json
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*.swp
2 | ._*
3 | .DS_Store
4 | .git
5 | .hg
6 | .npmrc
7 | .lock-wscript
8 | .svn
9 | .wafpickle-*
10 | config.gypi
11 | CVS
12 | npm-debug.log
13 | src
14 | CHANGELOG.md
15 | build/
16 | config/
17 | node_modules
18 | .babelrc
19 | .eslintrc
20 | .tsconfig.json
21 | .gitignore
22 | .eslintigore
23 | dist/
24 | typings/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/diegoddox/react-notification-center/d9ba5ef33c7e5bc4378a50e59e4810f9543e18bd/CHANGELOG.md
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NOT MAINTAINED.
2 | ## NOTE:
3 | This is not well documented but it will give you an idea on how to start
4 | ## `react-notification-center` [demo](http://diegoddox.github.io/react-notification-center/)
5 |
6 | ### Implementation Guide
7 |
8 | #### 1: Installation
9 | `npm install --save react-notification-center`
10 |
11 | #### 2: Import the less file to your project
12 | `import 'react-notification-center/src/less/index.less'`
13 |
14 | #### 3 Add the notification component
15 | ```
16 | import ReactNotificationCenter from 'react-notification-center';
17 |
18 | export default class App extends Component {
19 | constructor(props) {
20 | super(props);
21 |
22 | this.state = {
23 | notifications = [{
24 | id: 1,
25 | title: 'some title', // not required
26 | message: 'The notification text',
27 | new: false, // if the user has read the notification
28 | tags: [{ // not required
29 | type: 'success',
30 | text: 'text'
31 | }],
32 | date: '09/12/2016' // not required
33 | }];
34 | };
35 | }
36 |
37 | render() {
38 | return (
39 |
40 |
41 | console.log('You are on the bottom babay :D')}
46 | onScroll={() => console.log('You are scrolling on the list')}
47 | onItemClick={item => console.log('## item clicked', item)}
48 | onNotificationOpen={items => console.log('## all notifications', items)}
49 | onNotificationClose={items => console.log('## all notifications', items)}
50 | onScroll={e => console.log('You are scrolling', e)}
51 |
52 |
53 | );
54 | }
55 | }
56 | ```
57 | In case you wanna control the notification-icon position you can do it by accessing the `react-notification-center` `css` `class`.
58 |
59 | #### Notification tag types
60 | `success` `info` `warning` and `danger`
61 |
62 | #### You hate the notification data structure I've created :D
63 | ok ok don't panic, you don't have the same data structure and you don't wanna map your current data here is what your can do. Just use the `mapToItem` `props`
64 |
65 | ```
66 | this.mapDataToItems = {
67 | id: '_id',
68 | title: 'name',
69 | message: 'text',
70 | new: 'hasRead',
71 | date: 'createAt'
72 | }
73 |
74 |
77 | ```
78 |
79 | Sorry but you cannot map `tags` at the moment :(
80 |
81 | #### You still don't get it `o.O`
82 |
83 | Clone the repo and run a local demo
84 | ```
85 | git clone https://github.com/diegoddox/react-notification-center.git
86 | cd react-notification-center
87 | npm install
88 | npm start
89 | ```
90 |
91 | open your browser att `http://localhost:4001` and take a look at the file `developement/App.js`
92 |
93 | ### TODO:
94 | improve documentation.
95 |
--------------------------------------------------------------------------------
/build/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./webpack/' + require('../config').env);
4 |
--------------------------------------------------------------------------------
/build/webpack/base.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path = require('path');
3 | var HtmlWebpackPlugin = require('html-webpack-plugin');
4 | var config = require('../../config');
5 |
6 | module.exports = {
7 | target: 'web',
8 | entry: {
9 | app: path.join(config.path_base, config.dir_client + '/client.js')
10 | },
11 | output: {
12 | path: path.join(config.path_base + '/dist'),
13 | filename: 'bundle.js',
14 | publicPath: ''
15 | },
16 | module: {
17 | preLoaders: [
18 | {
19 | test: /\.jsx?$/,
20 | loaders: ['eslint'],
21 | exclude: /node_modules/
22 | }
23 | ],
24 | loaders: [
25 | {
26 | test: /\.js?$/,
27 | exclude: /node_modules/,
28 | loaders: ['react-hot', 'babel']
29 | }, {
30 | test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
31 | exclude: /node_modules/,
32 | loader: 'url-loader'
33 | }, {
34 | test: /\.less$/,
35 | exclude: /node_modules/,
36 | loader: 'style!css!less'
37 | }, {
38 | test: /\.css$/,
39 | loader: 'style-loader!css-loader'
40 | }, {
41 | test: /\.jpg$/,
42 | exclude: /node_modules/,
43 | loader: 'file-loader'
44 | }
45 | ]
46 | },
47 | plugins: [
48 | new webpack.DefinePlugin({
49 | 'process.env': {
50 | NODE_ENV: '"' + process.env.NODE_ENV + '"'
51 | }
52 | }),
53 | new HtmlWebpackPlugin({
54 | templateContent: ''
55 | + ''
56 | + ''
57 | + ' '
58 | + ' '
59 | + ' React Notification Center'
60 | + ' '
61 | + ' '
62 | + ' '
63 | + ' '
64 | + ' '
65 | + '',
66 | inject: 'body'
67 | })
68 | ]
69 | };
70 |
--------------------------------------------------------------------------------
/build/webpack/development.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var config = require('../../config');
5 | var baseConfig = require('./base');
6 |
7 | baseConfig.entry.app = [
8 | 'webpack-dev-server/client?http://localhost:' + config.server_port,
9 | 'webpack/hot/only-dev-server',
10 | config.path_base + '/' + config.dir_client + '/client.js'
11 | ];
12 |
13 | baseConfig.devtool = 'inline-source-map';
14 |
15 | baseConfig.devServer = {
16 | headers: {
17 | 'Access-Control-Allow-Origin': '*',
18 | 'Access-Control-Allow-Credentials': true,
19 | 'Access-Control-Max-Age': 1
20 | },
21 | contentBase: path.join(config.path_base, '/src'),
22 | noInfo: false,
23 | port: config.server_port,
24 | hot: true,
25 | stats: {
26 | colors: true
27 | },
28 | historyApiFallback: true
29 | };
30 |
31 | module.exports = baseConfig;
32 |
--------------------------------------------------------------------------------
/build/webpack/production.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var webpack = require('webpack');
4 | var webpackBase = require('./base');
5 |
6 | webpackBase.plugins.push(
7 | new webpack.optimize.UglifyJsPlugin({
8 | compress: {
9 | warnings: false
10 | }
11 | })
12 | );
13 |
14 | module.exports = webpackBase;
15 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | module.exports = {
6 | env: process.env.NODE_ENV,
7 | path_base: path.resolve(__dirname, '../'),
8 | dir_client: 'development',
9 | server_port: process.env.NODE_PORT || 3000
10 | };
11 |
--------------------------------------------------------------------------------
/development/client.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {render} from 'react-dom';
3 |
4 | import './index.less';
5 | import './../src/less/index.less';
6 | import loremIpsum from 'lorem-ipsum';
7 | import moment from 'moment';
8 | import uiid from 'uuid';
9 | import ReactNotificationCenter from './../src/';
10 |
11 | class App extends Component {
12 | constructor(props) {
13 | super(props);
14 |
15 | this.notificationOptions = {
16 | id: '__id',
17 | message: 'text',
18 | new: 'active',
19 | date: 'startDate'
20 | };
21 |
22 | this.notifications = [
23 | {
24 | __id: uiid.v1(),
25 | title: loremIpsum({count: 1}),
26 | text: loremIpsum({count: 3}),
27 | active: true,
28 | tags: [{
29 | type: 'success',
30 | text: loremIpsum({count: 1, units: 'words'})
31 | }],
32 | startDate: moment().format('LLL')
33 | }, {
34 | __id: uiid.v1(),
35 | title: loremIpsum({count: 1}),
36 | text: loremIpsum({count: 6}),
37 | active: true,
38 | tags: [{
39 | type: 'info',
40 | text: loremIpsum({count: 1, units: 'words'})
41 | }, {
42 | type: 'warning',
43 | text: loremIpsum({count: 1, units: 'words'})
44 | }, {
45 | type: 'danger',
46 | text: loremIpsum({count: 1, units: 'words'})
47 | }],
48 | startDate: moment().format('LLL')
49 | }, {
50 | __id: uiid.v1(),
51 | title: loremIpsum({count: 1}),
52 | text: loremIpsum({count: 6}),
53 | active: true,
54 | tags: [{
55 | type: 'warning',
56 | text: loremIpsum({count: 1, units: 'words'})
57 | }],
58 | startDate: moment().format('LLL')
59 | }, {
60 | __id: uiid.v1(),
61 | title: loremIpsum({count: 1}),
62 | text: loremIpsum({count: 6}),
63 | active: true,
64 | tags: [{
65 | type: 'danger',
66 | text: loremIpsum({count: 1, units: 'words'})
67 | }],
68 | startDate: moment().format('LLL')
69 | }
70 | ];
71 | this.state = {
72 | notifications: this.notifications
73 | };
74 | }
75 |
76 | addnotification(tagType) {
77 | this.setState({
78 | notifications: [
79 | {
80 | __id: uiid.v1(),
81 | title: loremIpsum({count: 1}),
82 | text: loremIpsum({count: 6}),
83 | active: true,
84 | tags: [{
85 | type: tagType ? tagType : 'info',
86 | text: loremIpsum({count: 1, units: 'words'})
87 | }],
88 | startDate: moment().format('LLL')
89 | },
90 | ...this.state.notifications
91 | ]
92 | });
93 | }
94 |
95 | render() {
96 | return (
97 |
98 |
99 |
100 |
101 |
7
102 |

103 |
104 |
105 | Just include the notification-icon where you want config and you're ready to go!
106 |
107 |
108 |
109 |
110 | console.log('Notification has open')}
113 | onNotificationClose={() => console.log('Notification has close')}
114 | onItemClick={() => console.log('The item has been clicked')}
115 | onScroll={() => console.log('You are scrolling')}
116 | onScrollBottom={() => console.log('you are on the bottom')}
117 | mapToItem={this.notificationOptions}/>
118 |
119 |
125 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | );
143 | }
144 | }
145 | App.displayName = 'ReactNotificationDevApp';
146 |
147 | render(, document.getElementById('app'));
148 |
--------------------------------------------------------------------------------
/development/index.less:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
4 | }
5 |
6 | html, body {
7 | height: 100%;
8 | }
9 | #app {
10 | height: 100%;
11 | }
12 |
13 | .wrapper {
14 | width: 100%;
15 | height: 100%;
16 | background-color: #f7f7f7;
17 | }
18 |
19 | .fake-app {
20 | width: 500px;
21 | height: 400px;
22 | top: 12%;
23 | left: 50px;
24 | position: absolute;
25 |
26 | .demo-icon-info {
27 | width: 100%;
28 | height: 60px;
29 | position: absolute;
30 | top: -60px;
31 | left: 0px;
32 |
33 | .app-icon {
34 | width: 90px;
35 | height: 100%;
36 | float: left;
37 | margin-left: 30px;
38 | margin-right: 10px;
39 | position: relative;
40 |
41 | .no-length {
42 | width: 30px;
43 | text-align: center;
44 | position: absolute;
45 | top: 10px;
46 | left: 54px;
47 | color: white;
48 | font-size: 1.2em;
49 | }
50 | img {
51 | width: 100%;
52 | }
53 | }
54 |
55 | .description {
56 | width: 320px;
57 | height: 100%;
58 | color: #666;
59 | font-size: 14px;
60 | float: left;
61 | padding: 10px;
62 | }
63 | }
64 |
65 | .menu {
66 | width: 100%;
67 | min-height: 48px;
68 | border: 1px solid #dbdbdb;
69 | position: relative;
70 | border-top-left-radius: 4px;
71 | border-top-right-radius: 4px;
72 |
73 | .app-notification {
74 | width: 35px;
75 | height: 35px;
76 | position: absolute;
77 | top: 5px;
78 | left: 10px;
79 | border: 1px solid #dbdbdb;
80 | border-radius: 3px;
81 |
82 | .react-notification-center {
83 | position: absolute;
84 | top: 5px;
85 | left: 5px;
86 | }
87 | }
88 |
89 | .link-first, .link {
90 | display: block;
91 | height: 25px;
92 | background-color: #ccc;
93 | float: left;
94 | margin-top: 10px;
95 | margin-left: 10px;
96 | border-radius: 4px;
97 | border: none;
98 | padding: 5px 20px;
99 | color: #999;
100 | outline: none;
101 |
102 | &:hover {
103 | color: white;
104 | cursor: pointer;
105 | }
106 |
107 | &.first {
108 | margin-left: 60px;
109 | }
110 | }
111 | }
112 |
113 | .content {
114 | width: 100%;
115 | height: 300px;
116 | padding: 20px;
117 | border-left: 1px solid #dbdbdb;
118 | overflow: hidden;
119 |
120 | .avatar {
121 | width: 80px;
122 | height: 80px;
123 | border-radius: 50%;
124 | border: 1px solid #dbdbdb;
125 | float: left;
126 | background-color: #fcfcfc;
127 | }
128 |
129 | .box {
130 | width: 100px;
131 | height: 80px;
132 | border: 1px solid #dbdbdb;
133 | border-radius: 2px;
134 | float: left;
135 | margin-left: 20px;
136 |
137 | &.second {
138 | width: 200px;
139 | }
140 | }
141 |
142 | .line {
143 | width: 380px;
144 | height: 20px;
145 | float: left;
146 | margin-top: 20px;
147 | background-color: #f0f0f0;
148 | border: 1px solid #dbdbdb;
149 | clear: both;
150 | border-radius: 2px;
151 |
152 | &.small {
153 | width: 200px;
154 | }
155 |
156 | &.medium {
157 | width: 300px;
158 | }
159 |
160 | &.large {
161 | width: 400px;
162 | }
163 | }
164 | }
165 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-notification-center",
3 | "version": "1.2.2",
4 | "description": "react-notification-center keep all your notification in a single place",
5 | "main": "lib/index.js",
6 | "jsnext:main": "./src/notification/index.js",
7 | "scripts": {
8 | "start": "better-npm-run dev-server",
9 | "build:w": "better-npm-run build:w",
10 | "build": "better-npm-run build && npm run less && npm run less:m",
11 | "less": "node_modules/.bin/lessc src/less/index.less --autoprefix=\"last 2 versions\" lib/css/react-redux-notification.css",
12 | "less:m": "node_modules/.bin/lessc src/less/index.less --autoprefix=\"last 2 versions\" --clean-css lib/css/react-redux-notification.min.css",
13 | "clean": "better-npm-run clean",
14 | "lint": "better-npm-run lint",
15 | "build_app": "concurrent --kill-others & npm run clean & npm run lint & npm run build & npm run less & npm run less:m",
16 | "buildc": "better-npm-run build_client"
17 | },
18 | "eslintConfig": {
19 | "root": true
20 | },
21 | "betterScripts": {
22 | "dev-server": {
23 | "command": "./node_modules/.bin/webpack-dev-server --hot --inline --config build/webpack.config.js",
24 | "env": {
25 | "NODE_ENV": "development",
26 | "NODE_PORT": 4001
27 | }
28 | },
29 | "build_client": {
30 | "command": "./node_modules/.bin/webpack --progress --config build/webpack.config.js",
31 | "env": {
32 | "NODE_ENV": "production"
33 | }
34 | },
35 | "build": {
36 | "command": "node_modules/.bin/babel src/ --out-dir lib"
37 | },
38 | "build:w": {
39 | "command": "node_modules/.bin/babel -w src/ --out-dir lib"
40 | },
41 | "lint": {
42 | "command": "node_modules/.bin/eslint . --ext .js --ext .jsx || true"
43 | },
44 | "clean": {
45 | "command": "node_modules/.bin/rimraf dist lib"
46 | }
47 | },
48 | "author": "Diego Oliveira",
49 | "license": "MIT",
50 | "repository": {
51 | "type": "git",
52 | "url": "https://github.com/diegoddox/react-notification-center"
53 | },
54 | "bugs": {
55 | "url": "https://github.com/diegoddox/react-notification-center/issues"
56 | },
57 | "files": [
58 | "src/",
59 | "lib/",
60 | "CHANGELOG.md",
61 | "README.md"
62 | ],
63 | "keywords": [
64 | "React.js",
65 | "React",
66 | "react",
67 | "notification",
68 | "react-notification",
69 | "react-component",
70 | "notification",
71 | "react-notification",
72 | "react notifications",
73 | "react notification",
74 | "changelog"
75 | ],
76 | "devDependencies": {
77 | "babel-cli": "^6.6.5",
78 | "babel-core": "^6.2.1",
79 | "babel-eslint": "^6.0.2",
80 | "babel-loader": "^6.2.0",
81 | "babel-plugin-syntax-class-properties": "^6.1.18",
82 | "babel-plugin-transform-class-properties": "^6.2.2",
83 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
84 | "babel-plugin-transform-es2015-arrow-functions": "^6.1.18",
85 | "babel-plugin-transform-es2015-block-scoping": "^6.1.18",
86 | "babel-plugin-transform-proto-to-assign": "^6.6.5",
87 | "babel-preset-es2015": "^6.1.18",
88 | "babel-preset-react": "^6.1.18",
89 | "babel-preset-stage-0": "^6.1.18",
90 | "better-npm-run": "0.0.8",
91 | "chance": "^1.0.1",
92 | "concurrently": "^2.0.0",
93 | "css-loader": "^0.23.1",
94 | "eslint": "^2.7.0",
95 | "eslint-loader": "^1.1.1",
96 | "eslint-plugin-react": "^4.2.3",
97 | "html-webpack-plugin": "^2.15.0",
98 | "jshint": "^2.9.1-rc1",
99 | "jshint-loader": "^0.8.3",
100 | "less": "^2.5.3",
101 | "less-loader": "^2.2.1",
102 | "less-plugin-autoprefix": "^1.5.1",
103 | "less-plugin-clean-css": "^1.5.1",
104 | "lorem-ipsum": "^1.0.3",
105 | "moment": "^2.12.0",
106 | "react": "^0.14.7",
107 | "react-dom": "^0.14.7",
108 | "react-hot-loader": "^1.3.0",
109 | "rimraf": "^2.4.4",
110 | "style-loader": "^0.13.0",
111 | "url-loader": "^0.5.7",
112 | "webpack": "^1.12.4",
113 | "webpack-dev-server": "^1.14.0"
114 | },
115 | "dependencies": {
116 | "classnames": "^2.2.3",
117 | "element-class": "^0.2.2"
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Item.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react';
2 | import cn from 'classnames';
3 | import Tag from './Tag';
4 | import {cutString} from './utils';
5 |
6 | export default class NotificationItem extends Component {
7 | static displayName = 'NotificationItemComponent';
8 |
9 | static propTypes = {
10 | onClick: PropTypes.func,
11 | tags: PropTypes.array
12 | };
13 |
14 | constructor(props) {
15 | super(props);
16 | }
17 |
18 | handleOnClick() {
19 | if (this.props.onClick) {
20 | this.props.onClick(this.props);
21 | }
22 | }
23 | render() {
24 | return (
25 |
26 |
27 | {this.props.tags && this.props.tags.map((item, i) => {item.text})}
28 | {this.props[this.props.options.title] && {this.props[this.props.options.title]} }
29 | {this.props[this.props.options.message] && cutString(this.props[this.props.options.message], 50)}
30 | {this.props[this.props.options.date] && {this.props[this.props.options.date]}}
31 |
32 |
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Notificaton.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import Header from './header';
3 | import Content from './content';
4 | import Footer from './footer';
5 |
6 | export default class Notification extends Component {
7 | static displayName = 'Notification';
8 |
9 | constructor(props) {
10 | super(props);
11 | }
12 |
13 | render() {
14 | return (
15 |
16 |
{this.state.current && this.state.current[this.mapOptions().title]}
17 |
18 |
19 | {this.props.notification}
20 |
21 |
22 |
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Tag.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cn from 'classnames';
3 |
4 | const Tag = props => (
5 |
6 | {props.children}
7 |
8 | );
9 |
10 | Tag.displayName = 'NotificationTagComponent';
11 | Tag.propTypes = {
12 | type: React.PropTypes.string.isRequired,
13 | children: React.PropTypes.node
14 | };
15 | export default Tag;
16 |
--------------------------------------------------------------------------------
/src/content.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 |
3 | export default class NotificationContent extends Component {
4 | constructor(props) {
5 | super(props);
6 | this.onContentScroll = this.onContentScroll.bind(this);
7 | }
8 |
9 | componentDidMount() {
10 | this.refs.rrContent.addEventListener('scroll', this.onContentScroll);
11 | }
12 |
13 | componentWillUnmount() {
14 | this.refs.rrContent.removeEventListener('scroll', this.onContentScroll);
15 | }
16 |
17 | onContentScroll(e) {
18 | if (this.props.onScroll) {
19 | this.props.onScroll(e);
20 | }
21 |
22 | if ((e.target.scrollHeight - e.target.scrollTop) == e.target.clientHeight) {
23 | if (this.props.onScrollBottom) {
24 | this.props.onScrollBottom();
25 | }
26 | }
27 | }
28 |
29 | render() {
30 | return (
31 |
32 | {this.props.children}
33 |
34 | );
35 | }
36 | }
37 |
38 | NotificationContent.displayName = 'NotificationContent';
39 | NotificationContent.proptypes = {
40 | children: React.PropTypes.node.isRequired
41 | };
42 |
--------------------------------------------------------------------------------
/src/footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotificationFooter = props => {props.children}
;
4 |
5 | NotificationFooter.displayName = 'NotificationHeader';
6 | NotificationFooter.proptypes = {
7 | children: React.PropTypes.node.isRequired
8 | };
9 | export default NotificationFooter;
10 |
--------------------------------------------------------------------------------
/src/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotificationHeader = props => (
4 |
5 |
{props.children}
6 |
7 | );
8 |
9 | NotificationHeader.displayName = 'NotificationHeader';
10 | NotificationHeader.proptypes = {
11 | children: React.PropTypes.node.isRequired
12 | };
13 | export default NotificationHeader;
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from 'react';
2 | import cn from 'classnames';
3 | import elementClass from 'element-class';
4 | import NotificationItem from './Item';
5 | import Header from './header';
6 | import Content from './content';
7 | import Footer from './footer';
8 | import {cutString} from './utils';
9 |
10 | export default class ReduxnotificationCenter extends Component {
11 | static displayName = 'ReduxNotofication';
12 |
13 | static propTypes = {
14 | notifications: PropTypes.array,
15 | mapToItem: PropTypes.object,
16 | onItemClick: PropTypes.func,
17 | customItemComponent: PropTypes.func,
18 | onNotificationOpen: PropTypes.func,
19 | onNotificationClose: PropTypes.func,
20 | onScrollBottom: PropTypes.func,
21 | position: PropTypes.string,
22 | wordsInItem: PropTypes.number,
23 | noNotificationText: PropTypes.string
24 | };
25 |
26 | static defaultProps = {
27 | notificationTitle: 'React notification center',
28 | position: 'left',
29 | wordsInItem: 50,
30 | noNotificationText: 'No data available, enjoy your day',
31 | mapToItem: {},
32 | notifications: []
33 | };
34 |
35 | constructor(props) {
36 | super(props);
37 | this.state = {
38 | notifications: this.props.notifications,
39 | showNotification: false,
40 | showNotificationMessage: false,
41 | current: null
42 | };
43 |
44 | this.isChildOf = this.isChildOf.bind(this);
45 | this.mapOptions = this.mapOptions.bind(this);
46 | this.getUnreadLength = this.getUnreadLength.bind(this);
47 | this.toggleNotification = this.toggleNotification.bind(this);
48 | this.timeout = null;
49 | }
50 |
51 | componentDidMount() {
52 | if (document) {
53 | document.addEventListener('click', this.toggleNotification);
54 | }
55 | }
56 |
57 | componentWillReceiveProps(nextProps) {
58 | if (nextProps.notifications.length !== this.state.notifications.length) {
59 | elementClass(this.refs.notificationIcon).add('pulse');
60 |
61 | this.timeout = setTimeout(() => {
62 | elementClass(this.refs.notificationIcon).remove('pulse');
63 | }, 1200);
64 | this.setState({notifications: nextProps.notifications});
65 | }
66 | }
67 |
68 | componentWillUnmount() {
69 | if (this.timeout) {
70 | clearTimeout(this.timeout);
71 | }
72 | if (document) {
73 | document.removeEventListener('click', this.toggleNotification);
74 | }
75 | }
76 |
77 | getUnreadLength() {
78 | return this.state.notifications.filter(item => item[this.mapOptions().new]).length;
79 | }
80 |
81 | toggleNotification(e) {
82 | if (e.target == this.refs.notificationIcon && !this.state.showNotification) {
83 | this.setState({
84 | showNotification: true
85 | });
86 | if (this.props.onNotificationOpen) {
87 | this.props.onNotificationOpen(this.state.notifications);
88 | }
89 | } else if (this.state.showNotification && !this.isChildOf(e.target, this.refs.notificationHolder)) {
90 | this.setState({
91 | showNotification: false,
92 | showNotificationMessage: false,
93 | current: null
94 | });
95 | if (this.props.onNotificationClose) {
96 | this.props.onNotificationClose(this.state.notifications);
97 | }
98 | }
99 | }
100 |
101 | isChildOf(child, parent) {
102 | if (child.parentNode === parent) {
103 | return true;
104 | } else if (child.parentNode === null) {
105 | return false;
106 | } else {
107 | return this.isChildOf(child.parentNode, parent);
108 | }
109 | }
110 |
111 | mapOptions() {
112 | return {
113 | id: this.props.mapToItem.id || 'id',
114 | title: this.props.mapToItem.title || 'title',
115 | message: this.props.mapToItem.message || 'message',
116 | date: this.props.mapToItem.date || 'date',
117 | new: this.props.mapToItem.new || 'new'
118 | };
119 | }
120 |
121 | onItemClick(item) {
122 | this.setState({
123 | notifications: this.state.notifications.map(notification => {
124 | if (!notification[this.mapOptions().id]) {
125 | console.error('React Notification ERROR: You need an id');
126 | return notification;
127 | }
128 |
129 | if (notification[this.mapOptions().id] == item[this.mapOptions().id]) {
130 | notification[this.mapOptions().new] = false;
131 | }
132 | return notification;
133 | }),
134 | showNotificationMessage: true,
135 | current: item
136 | });
137 | if (this.props.onItemClick) {
138 | this.props.onItemClick(item, this.state.notifications);
139 | }
140 | }
141 |
142 | back() {
143 | this.setState({
144 | showNotificationMessage: false
145 | });
146 | }
147 |
148 | render() {
149 | return (
150 |
151 |
152 | {this.getUnreadLength() > 0 && this.getUnreadLength()}
153 |
154 | {this.state.showNotification &&
155 |
156 |
157 |
158 |
{cutString(this.props.notificationTitle, 30)}
159 |
160 | {this.state.notifications.length == 0 &&
161 | {this.props.noNotificationText}
162 | }
163 |
164 | {this.state.notifications.map((item, i) => {
165 | return (
166 |
170 | );
171 | })}
172 |
173 |
174 |
175 |
176 |
177 |
178 |
{this.state.current && this.state.current[this.mapOptions().title]}
179 |
180 |
181 | {this.state.current && this.state.current[this.mapOptions().message]}
182 |
183 |
184 |
189 |
190 |
191 |
}
192 |
193 | );
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/less/index.less:
--------------------------------------------------------------------------------
1 | @import './notification.less';
2 | @import './notifications.less';
3 |
4 | .react-notification-center {
5 | width: 24px;
6 | height: 24px;
7 | position: relative;
8 | z-index: 99999;
9 | font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
10 |
11 | *, *:before, *:after {
12 | box-sizing: border-box;
13 | }
14 |
15 | .r-notifications-icon {
16 | width: 10px;
17 | height: 10px;
18 | border: 1px solid white;
19 | border-radius: 50px;
20 | text-align: center;
21 | line-height: 20px;
22 | color: white;
23 | position: absolute;
24 | z-index: 0;
25 | font-size: 10px;
26 | cursor: pointer;
27 | background-color: #ccc;
28 | top: 6px;
29 | left: 6px;
30 | overflow: hidden;
31 | transition: all .2s ease-in-out;
32 |
33 | &.active {
34 | background-color: #ff5c5c;
35 | width: 22px;
36 | height: 22px;
37 | top: 1px;
38 | left: 1px;
39 | }
40 |
41 | &:hover {
42 | transform: scale(1.1);
43 | }
44 |
45 | &.pulse {
46 | animation-name: pulse_animation;
47 | animation-duration: 300ms;
48 | animation-iteration-count: 2;
49 | animation-timing-function: linear;
50 |
51 | -webkit-animation-name: webkit_pulse_animation;
52 | -webkit-animation-duration: 300ms;
53 | -webkit-animation-iteration-count: 2;
54 | -webkit-animation-timing-function: linear;
55 | }
56 | }
57 |
58 | .rn-header {
59 | width: 100%;
60 | height: 50px;
61 | background-color: #fcfcfc;
62 | border-bottom: 1px solid #f0f0f0;
63 | border-top-right-radius: 3px;
64 | border-top-left-radius: 3px;
65 | overflow: hidden;
66 | h4 {
67 | width: 100%;
68 | padding: 0px 20px;
69 | margin: 0;
70 | text-align: center;
71 | line-height: 50px;
72 | color: #333;
73 | }
74 | }
75 |
76 | .rn-content {
77 | width: 100%;
78 | height: 360px;
79 | background-color: white;
80 | overflow: hidden;
81 | overflow-y: auto;
82 | font-size: 14px;
83 |
84 | .no-rn {
85 | width: 100%;
86 | height: 100%;
87 | text-align: center;
88 | color: #999;
89 | line-height: 250px;
90 | overflow: hidden;
91 | }
92 | }
93 |
94 | .rn-footer {
95 | width: 100%;
96 | height: 40px;
97 | background-color: #fcfcfc;
98 | border-top: 1px solid #f0f0f0;
99 | border-bottom-right-radius: 3px;
100 | border-bottom-left-radius: 3px;
101 | overflow: hidden;
102 | }
103 |
104 | .rr-wrapper {
105 | box-shadow: 3px 3px 25px #dbdbdb;
106 | border-radius: 3px;
107 | width: 350px;
108 | height: 450px;
109 | position: absolute;
110 | z-index: 1;
111 |
112 | &.left {
113 | top: 40px;
114 | left: -25px;
115 | }
116 |
117 | &:before {
118 | content: '';
119 | width: 0;
120 | height: 0;
121 | border-right: 15px solid transparent;
122 | border-left: 15px solid transparent;
123 | border-bottom: 15px solid #fcfcfc;
124 | position: absolute;
125 | top: -15px;
126 | left: 21px;
127 | }
128 |
129 | .notification-holder {
130 | border-radius: 3px;
131 | width: 100%;
132 | height: 100%;
133 | position: absolute;
134 | overflow: hidden;
135 | border-radius: 3px;
136 | }
137 | }
138 |
139 | &.light-theme {
140 | .notification-box {
141 | background-color: white;
142 | }
143 |
144 | .notification-list {
145 | .header {
146 | border-bottom: 1px solid #f0f0f0;
147 | h4 {
148 | color: #666;
149 | }
150 | }
151 |
152 | .contents {
153 | li.item {
154 | border-bottom: 1px solid #f9f9f9;
155 | .short-desc {
156 | color: #444;
157 | }
158 | }
159 | }
160 |
161 | .footer {
162 | border-top: 1px solid #f0f0f0;
163 | }
164 | }
165 | }
166 | }
167 |
168 | @-webkit-keyframes webkit_pulse_animation {
169 | 0% { -webkit-transform: scale(1); }
170 | 50% { -webkit-transform: scale(1.2); }
171 | 100% { -webkit-transform: scale(1); }
172 | }
173 |
174 | @keyframes pulse_animation {
175 | 0% { transform: scale(1); }
176 | 50% { transform: scale(1.2); }
177 | 100% { transform: scale(1); }
178 | }
--------------------------------------------------------------------------------
/src/less/notification.less:
--------------------------------------------------------------------------------
1 | .r-notification {
2 | width: 100%;
3 | height: 450px;
4 | position: absolute;
5 | top: 0;
6 | left: 0px;
7 | z-index: 2;
8 | transform: translate(-400px, 0);
9 | transition: all .3s ease-in-out;
10 |
11 | &.active {
12 | transform: translate(0, 0);
13 | }
14 |
15 | .desc {
16 | padding: 20px;
17 | }
18 |
19 | button {
20 | display: block;
21 | width: 50px;
22 | height: 30px;
23 | margin: auto;
24 | margin-top: 4px;
25 | background-color: transparent;
26 | border: none;
27 | outline: none;
28 |
29 | &:hover {
30 | cursor: pointer;
31 | }
32 |
33 | .back {
34 | display: block;
35 | width: 40px;
36 | height: 4px;
37 | background-color: #ccc;
38 | position: relative;
39 | border-radius: 4px;
40 |
41 | &:before {
42 | content: '';
43 | width: 0;
44 | height: 0;
45 | border-top: 7px solid transparent;
46 | border-right: 7px solid #ccc;
47 | border-bottom: 7px solid transparent;
48 | position: absolute;
49 | top: -5px;
50 | left: -5px;
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/less/notifications.less:
--------------------------------------------------------------------------------
1 | .r-notifications {
2 | width: 100%;
3 | height: 450px;
4 | z-index: 1;
5 | overflow: hidden;
6 | position: relative;
7 | z-index: 0;
8 |
9 | ul.rn-ul {
10 | padding: 0;
11 | }
12 |
13 | li.rn-item {
14 | width: 100%;
15 | padding: 10px 0;
16 | list-style: none;
17 | cursor: pointer;
18 | position: relative;
19 | border-bottom: 1px solid #f5f5f5;
20 |
21 | &:before {
22 | display: none;
23 | content: '';
24 | width: 5px;
25 | height: 5px;
26 | position: absolute;
27 | top: 50%;
28 | left: 8px;
29 | background-color: #666;
30 | border-radius: 50%;
31 | }
32 |
33 | &:hover {
34 | background-color: #fcfcfc;
35 | box-shadow: inset 0px 0px 2px #f2f2f2;
36 | }
37 |
38 | &.new {
39 | &:before {
40 | display: block;
41 | }
42 | }
43 |
44 | &:last-child {
45 | border: none;
46 | }
47 |
48 | .short-desc {
49 | width: 80%;
50 | margin: 5px auto;
51 |
52 | .notification-tag {
53 | color: white;
54 | border-radius: 16px;
55 | padding: 3px 7px;
56 | font-size: 11px;
57 | text-transform: uppercase;
58 | margin-right: 4px;
59 | line-height: 20px;
60 |
61 | &.info {
62 | background-color: #58abc3;
63 | }
64 |
65 | &.success {
66 | background-color: #60bb71;
67 | }
68 |
69 | &.warning {
70 | background-color: #f7a336;
71 | }
72 |
73 | &.danger {
74 | background-color: #db6a64;
75 | }
76 | }
77 |
78 | &:hover {
79 | .title {
80 | text-decoration: underline;
81 | }
82 | }
83 |
84 | .date {
85 | display: block;
86 | font-size: 10px;
87 | color: #999;
88 | clear: both;
89 | margin-top: 5px;
90 | }
91 |
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | export function createReducer(initialState, fnMap) {
2 | return (state = initialState, {type, payload}) => {
3 | const handle = fnMap[type];
4 | return handle ? handle(state, payload) : state;
5 | };
6 | }
7 |
8 | export function cutString(st, limit) {
9 | let cut = st.indexOf(' ', limit);
10 | if (cut == -1) {
11 | return st;
12 | }
13 |
14 | return st.substring(0, cut) + '...';
15 | }
16 |
--------------------------------------------------------------------------------