├── .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 |
    23 | 26 |
    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 | --------------------------------------------------------------------------------