react-notification
Advanced tools
Comparing version 6.5.2 to 6.6.0
@@ -49,3 +49,3 @@ 'use strict'; | ||
value: function componentWillReceiveProps(nextProps) { | ||
if (this.props.dismissAfter === false) return; | ||
if (nextProps.dismissAfter === false) return; | ||
@@ -57,4 +57,6 @@ // See http://eslint.org/docs/rules/no-prototype-builtins | ||
if (nextProps.onDismiss && nextProps.isActive && !this.props.isActive) { | ||
this.dismissTimeout = setTimeout(nextProps.onDismiss, nextProps.dismissAfter); | ||
if (nextProps.onDismiss) { | ||
if (nextProps.isActive && !this.props.isActive || nextProps.dismissAfter && this.props.dismissAfter === false) { | ||
this.dismissTimeout = setTimeout(nextProps.onDismiss, nextProps.dismissAfter); | ||
} | ||
} | ||
@@ -61,0 +63,0 @@ } |
@@ -36,4 +36,14 @@ 'use strict'; | ||
var dismissNow = isLast || !props.dismissInOrder; | ||
// Handle styles | ||
var barStyle = props.barStyleFactory(index, notification.barStyle); | ||
var activeBarStyle = props.activeBarStyleFactory(index, notification.activeBarStyle); | ||
// Allow onClick from notification stack or individual notifications | ||
var onClick = notification.onClick || props.onClick; | ||
var onDismiss = props.onDismiss; | ||
var dismissAfter = notification.dismissAfter; | ||
if (dismissAfter !== false) { | ||
@@ -43,4 +53,2 @@ if (dismissAfter == null) dismissAfter = props.dismissAfter; | ||
} | ||
var barStyle = props.barStyleFactory(index, notification.barStyle); | ||
var activeBarStyle = props.activeBarStyleFactory(index, notification.activeBarStyle); | ||
@@ -52,3 +60,4 @@ return _react2.default.createElement(_stackedNotification2.default, _extends({}, notification, { | ||
dismissAfter: dismissAfter, | ||
onDismiss: props.onDismiss.bind(undefined, notification), | ||
onDismiss: onDismiss.bind(undefined, notification), | ||
onClick: onClick.bind(undefined, notification), | ||
activeBarStyle: activeBarStyle, | ||
@@ -66,5 +75,7 @@ barStyle: barStyle | ||
barStyleFactory: _react.PropTypes.func, | ||
dismissInOrder: _react.PropTypes.bool.isRequired, | ||
dismissInOrder: _react.PropTypes.bool, | ||
notifications: _react.PropTypes.array.isRequired, | ||
onDismiss: _react.PropTypes.func.isRequired | ||
onDismiss: _react.PropTypes.func.isRequired, | ||
onClick: _react.PropTypes.func, | ||
action: _react.PropTypes.string | ||
}; | ||
@@ -76,3 +87,4 @@ | ||
dismissInOrder: true, | ||
dismissAfter: 1000 | ||
dismissAfter: 1000, | ||
onClick: function onClick() {} | ||
}; | ||
@@ -79,0 +91,0 @@ |
@@ -54,6 +54,9 @@ 'use strict'; | ||
if (this.props.dismissAfter) { | ||
this.dismissTimeout = setTimeout(this.setState.bind(this, { | ||
isActive: false | ||
}), this.props.dismissAfter); | ||
this.dismiss(this.props.dismissAfter); | ||
} | ||
}, { | ||
key: 'componentWillReceiveProps', | ||
value: function componentWillReceiveProps(nextProps) { | ||
if (nextProps.dismissAfter !== this.props.dismissAfter) { | ||
this.dismiss(nextProps.dismissAfter); | ||
} | ||
@@ -67,3 +70,12 @@ } | ||
} | ||
}, { | ||
key: 'dismiss', | ||
value: function dismiss(dismissAfter) { | ||
if (dismissAfter === false) return; | ||
this.dismissTimeout = setTimeout(this.setState.bind(this, { | ||
isActive: false | ||
}), dismissAfter); | ||
} | ||
/* | ||
@@ -70,0 +82,0 @@ * @function handleClick |
{ | ||
"name": "react-notification", | ||
"version": "6.5.2", | ||
"description": "Snackbar style notification component for React.", | ||
"version": "6.6.0", | ||
"description": "Snackbar style notification component for React", | ||
"main": "dist/index.js", | ||
"scripts": { | ||
"prepublish": "npm run build", | ||
"precommit": "echo 'Running pre-commit hooks...' && npm test", | ||
"prepublish": "npm run build", | ||
"prebuild": "npm test", | ||
"build": "`npm bin`/babel src -d dist", | ||
"pretest": "npm run lint", | ||
"test": "NODE_ENV=test `npm bin`/mocha --compilers js:babel-core/register --reporter spec --recursive --timeout 5000 test/setup.js test/**/*.js", | ||
"test": "./bin/test.sh", | ||
"lint": "`npm bin`/eslint src/**/*.js test/**/*.js", | ||
@@ -31,2 +31,3 @@ "start": "node examples/server.js" | ||
"notification", | ||
"notification stack", | ||
"snackbar" | ||
@@ -33,0 +34,0 @@ ], |
183
README.md
@@ -1,181 +0,22 @@ | ||
# react-notification | ||
# React Notification | ||
[![npm version](https://badge.fury.io/js/react-notification.svg)](http://badge.fury.io/js/react-notification) [![Build Status](https://travis-ci.org/pburtchaell/react-notification.svg)](https://travis-ci.org/pburtchaell/react-notification) [![npm downloads](https://img.shields.io/npm/dm/react-notification.svg?style=flat)](http://badge.fury.io/js/react-notification) | ||
[![npm version](https://badge.fury.io/js/react-notification.svg)](http://badge.fury.io/js/react-notification) [![Build Status](https://travis-ci.org/pburtchaell/react-notification.svg)](https://travis-ci.org/pburtchaell/react-notification) [![npm downloads](https://img.shields.io/npm/dm/react-notification.svg?style=flat)](http://badge.fury.io/js/react-notification) | ||
## Overview | ||
![](https://raw.githubusercontent.com/pburtchaell/react-notification/master/docs/example-b.gif) | ||
![](https://raw.githubusercontent.com/pburtchaell/react-notification/master/examples/example.gif) | ||
![](https://raw.githubusercontent.com/pburtchaell/react-notification/master/examples/stack.gif) | ||
React Notification is a component designed to provide snackbar notification messages and notification stacks. The default visual style and interaction follows [Material Design guidelines for snackbards](http://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-usage), but it can be fully customized. | ||
This is a component designed to provide "[snackbar](http://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-usage)" notification messages and notification stacks (similar to how notifications stack on OS X). I would suggest reading the usage guidelines for [snackbars](http://www.google.com/design/spec/components/snackbars-toasts.html#). | ||
## Docs and Help | ||
## Getting Started | ||
- [Guides](/docs/guides/) | ||
- [Introduction](/docs/introduction.md) | ||
- [Examples](/examples) | ||
- [Releases](https://github.com/pburtchaell/react-notification/releases) | ||
- [Upgrading versions](/UPGRADING.md) | ||
Install the component via npm: `npm install react-notification`. | ||
## Sponsor | ||
If you are using the React 0.13.x or lower, you can install the previously compatible version of this component with `npm i react-notification@2.3.0 -S`. The current version only works with React 0.14.x. | ||
To help cover the cost of my time spent maintaining open source software (OSS) projects, **I'm looking for a small sponsor.** If your company is interested, [send me an email](mailto:patrick@pburtchaell.com) and we can chat! If you like my code and you're interested in buying me a drink, I have a [Gratipay](https://gratipay.com/~pburtchaell/). Thanks! I greatly appreciate your support and I'm grateful to be a part of the OSS and GitHub community. | ||
Please read the [contributing guide](/CONTRUBUTING.md) if you are interested in contributing. If you are coming from version 1.0.0, there is an [upgrade guide](/UPGRADING.md) to help you make the switch. If you have questions, please feel free to create an issue on GitHub. | ||
Note the component uses `Object.assign`. If you are compiling with Babel, you should include the Babel Polyfill in your build to ensure the method works. | ||
## Usage | ||
Single notification: | ||
```js | ||
import { Notification } from 'react-notification'; | ||
<Notification | ||
isActive={boolean} | ||
message={string} | ||
action={string} | ||
onClick={myClickHander} | ||
/> | ||
``` | ||
Notification stack: | ||
```js | ||
import { NotificationStack } from 'react-notification'; | ||
import { OrderedSet } from 'immutable'; | ||
constructor () { | ||
super(); | ||
this.state = { | ||
notifications: OrderedSet() | ||
}; | ||
} | ||
addNotification () { | ||
const newCount = count + 1; | ||
return this.setState({ | ||
notifications: this.state.notifications.add({ | ||
message: `Notification ipsum...`, | ||
key: 'some UID', | ||
action: 'Dismiss', | ||
onClick: (deactivate) => { | ||
deactivate(); | ||
setTimeout(() => this.removeNotification('some UID'), 400); | ||
}, | ||
}) | ||
}); | ||
} | ||
removeNotification (count) { | ||
this.setState({ | ||
notifications: this.state.notifications.filter(n => n.key !== count) | ||
}) | ||
} | ||
render () { | ||
return ( | ||
<NotificationStack | ||
notifications={this.state.notifications.toArray()} | ||
onDismiss={notification => this.setState({ | ||
notifications: this.state.notifications.delete(notification) | ||
})} | ||
/> | ||
); | ||
} | ||
``` | ||
See the [examples](examples/notification-tree) for more context on how to use a notification stack. | ||
### Props | ||
For Notification component: | ||
| Name | Type | Description | Required | Default | | ||
|-----------------|-------------------------|-------------------------------------------------------------|-----------|----------------------------| | ||
| isActive | boolean | If true, the notification is visible | true | `false` | | ||
| message | string or React element | The message or component for the notification | true | | | ||
| title | string | The title for the notification | | | | ||
| action | string | The name of the action, e.g., "close" or "undo" | | | | ||
| style | boolean | Setting this prop to `false` will disable all inline styles | | | | ||
| barStyle | object | Custom snackbar styles | | | | ||
| activeBarStyle | object | Custom snackbar styles when the bar is active | | | | ||
| actionStyle | object | Custom action styles | | | | ||
| className | string | Custom class to apply to the top-level component | | | | ||
| activeClassName | string | Custom class to apply to the top-level component when active| | `'notification-bar-active'`| | ||
| dismissAfter | number or false | Timeout for onDismiss event | | `2000` | | ||
The `style` prop useful if you are not using React inline styles and would like to use CSS instead. See [styles](#styles) for more. | ||
For NotificationStack component: | ||
| Name | Type | Description | Required | Default | | ||
|-----------------------|-------|----------------------------------------------------------|---------- |----------| | ||
| notifications | array | Array of notifications to render | true | | | ||
| dismissInOrder | bool | If false, notification dismiss timers start immediately | false | true | | ||
| barStyleFactory | func | create the style of the notification | false | fn | | ||
| activeBarStyleFactory | func | create the style of the active notification | false | fn | | ||
**Update** `v5.0.3`: Now notifications used in a stack _can_ have all properties included in the regular notification component. | ||
## Events | ||
For Notification component: | ||
| Event | Description | | ||
|-----------|------------------------------------------------------------| | ||
| onClick | Callback function to run when the action is clicked | | ||
| onDismiss | Callback function to run when dismissAfter timer runs out | | ||
onClick is called with parameter *deactivate*, which is a function and can be called to set the notification to inactive. Used to activate notification exit animation on click. | ||
For NotificationStack component: | ||
| Event | Description | Arguments | | ||
|-----------|------------------------------------------------------------------------------|-----------------------------------------------------------| | ||
| onDismiss | Callback function to run when dismissAfter timer runs out for a notification | The object for the notification currently being dismissed | | ||
## Styles | ||
This component does use basic inline CSS to style the position and visibility of the notification. You have two options for adding additional styles: | ||
1. Remove all inline styles and use only CSS. | ||
2. Add additional inline styles via the style prop. | ||
The DOM tree of the component for reference: | ||
```html | ||
<div class="notification-bar"> | ||
<div class="notification-bar-wrapper" onClick={this.props.onClick}> | ||
<span class="notification-bar-message">{this.props.message}</span> | ||
<span class="notification-bar-action">{this.props.action}</span> | ||
</div> | ||
</div> | ||
``` | ||
To use additional inline styles, return two objects. The `bar` object applies styles to the entire notification "snackbar" and the `action` object applies styles to the action message. Under the hood, this uses `Object.assign` to handle properly combining styles. | ||
I would highly suggest using this method since the styles included in the component by default handle the visibility of the notification. If you remove these styles, the component won't actually show or hide itself. | ||
### barStyleFactory and activeBarStyleFactory NotificationStack props | ||
These two function have the following signature: | ||
```js | ||
(index: Number, style: Object|Void) => Object | ||
``` | ||
Where `index` is the index of the notification in the notifications array and | ||
`style` is the style property of the individual notification. | ||
This function is used to dynamically set the style of each notification in the | ||
stack. The default function adds the `bottom` style property to correctly | ||
position of the notification in a stack. | ||
```js | ||
function defaultStyleFactory(index, style) { | ||
return Object.assign( | ||
{}, | ||
style, | ||
{ bottom: `${2 + index * 4}rem` } | ||
); | ||
} | ||
``` | ||
--- | ||
Copyright (c) 2015 Patrick Burtchaell. [Licensed with The MIT License (MIT)](https://raw.githubusercontent.com/pburtchaell/react-notification/master/LICENSE). [Gratipay](https://gratipay.com/~pburtchaell/). |
@@ -53,4 +53,4 @@ import React from 'react'; | ||
done(); | ||
} catch (e) { | ||
done(e); | ||
} catch (error) { | ||
throw error; | ||
} | ||
@@ -61,25 +61,2 @@ // Add time due to each StackedNotification transition time ( > 300 ) | ||
it('onDismiss fires after `dismissAfter` value + transition time', (done) => { | ||
const handleDismiss = spy(); | ||
const wrapper = mount( | ||
<NotificationStack | ||
notifications={notifications} | ||
onDismiss={handleDismiss} | ||
/> | ||
); | ||
wrapper.update(); | ||
setTimeout(() => { | ||
try { | ||
expect(handleDismiss.calledOnce).to.equal(true); | ||
done(); | ||
} catch (e) { | ||
done(e); | ||
} | ||
// Add time due to each StackedNotification transition time ( > 300 ) | ||
}, mockNotification.dismissAfter + 340); | ||
}); | ||
it('should not dismiss when `dismissAfter` is `false`', (done) => { | ||
@@ -101,4 +78,4 @@ const handleDismiss = spy(); | ||
done(); | ||
} catch (e) { | ||
done(e); | ||
} catch (error) { | ||
throw error; | ||
} | ||
@@ -109,36 +86,37 @@ // Add time due to each StackedNotification transition time ( > 300 ) | ||
it('onDismiss fires on each Notification in the stack', (done) => { | ||
it('should dismiss when `dismissAfter` is updated to a number after it was `false`', (done) => { | ||
const handleDismiss = spy(); | ||
let dismissAfter = 0; | ||
const wrapper = mount( | ||
<NotificationStack | ||
notifications={notifications} | ||
dismissInOrder={false} | ||
notifications={[{ ...mockNotification, dismissAfter: false }]} | ||
onDismiss={handleDismiss} | ||
/> | ||
); | ||
wrapper.update(); | ||
setTimeout(() => { | ||
try { | ||
expect(handleDismiss.callCount).to.equal(notifications.length); | ||
done(); | ||
} catch (e) { | ||
done(e); | ||
} | ||
// Add transition time + 1000ms per each Notification | ||
}, mockNotification.dismissAfter + 1340); | ||
}); | ||
it('onDismiss does not fire until `dismissAfter` value times out', () => { | ||
const handleDismiss = spy(); | ||
const isDismissed = (called, callback) => { | ||
setTimeout(() => { | ||
try { | ||
expect(handleDismiss.called).to.equal(called); | ||
callback(); | ||
} catch (err) { | ||
callback(err); | ||
} | ||
// Add time due to each StackedNotification transition time ( > 300 ) | ||
}, dismissAfter + 440); | ||
}; | ||
// eslint-disable-next-line | ||
const wrapper = shallow( | ||
<NotificationStack | ||
notifications={[mockNotification]} | ||
onDismiss={handleDismiss} | ||
/> | ||
); | ||
expect(handleDismiss.calledOnce).to.equal(false); | ||
isDismissed(false, (err) => { | ||
if (err) return done(err); | ||
dismissAfter = 110; | ||
wrapper.setProps({ | ||
notifications: [{ ...mockNotification, dismissAfter }] | ||
}, () => { | ||
isDismissed(true, done); | ||
}); | ||
}); | ||
}); | ||
@@ -152,2 +130,3 @@ | ||
); | ||
const stack = mount( | ||
@@ -160,3 +139,5 @@ <NotificationStack | ||
); | ||
const notification = stack.find(Notification); | ||
expect(notification.prop('barStyle').bottom).to.equal('0px'); | ||
@@ -171,2 +152,3 @@ }); | ||
); | ||
const stack = mount( | ||
@@ -179,3 +161,5 @@ <NotificationStack | ||
); | ||
const notification = stack.find(Notification); | ||
expect(notification.prop('barStyle').background).to.equal('rgb(2, 2, 2)'); | ||
@@ -190,2 +174,3 @@ }); | ||
); | ||
const stack = mount( | ||
@@ -198,3 +183,5 @@ <NotificationStack | ||
); | ||
const notification = stack.find(Notification); | ||
expect(notification.prop('activeBarStyle').bottom).to.equal('2px'); | ||
@@ -209,2 +196,3 @@ }); | ||
); | ||
const stack = mount( | ||
@@ -217,5 +205,146 @@ <NotificationStack | ||
); | ||
const notification = stack.find(Notification); | ||
expect(notification.prop('activeBarStyle').left).to.equal('4rem'); | ||
}); | ||
/** | ||
* Test: Global handling of onClick: | ||
* | ||
* If a child notification in the stack does not have an onClick property, | ||
* then fire onClick at the parent (<NotificationStack />) level. | ||
* | ||
* Because the mockNotification we used for other tests includes an | ||
* onClick property, we need use a different mock notification. | ||
*/ | ||
it('onClick fires globally when notification action is clicked', () => { | ||
const handleClickGlobal = spy(); | ||
// Render a notification stack with one notification child | ||
const wrapper = mount( | ||
<NotificationStack | ||
notifications={[{ | ||
key: '0', | ||
message: 'Foo', | ||
action: 'Dismiss', | ||
dismissAfter: 100, | ||
title: 'Title' | ||
// No onClick property on the notification child | ||
// onClick: () => {} | ||
}]} | ||
// Instead, it is handled globally | ||
onClick={handleClickGlobal} | ||
onDismiss={() => {}} | ||
/> | ||
); | ||
// Simulate a click on the notification in the stack | ||
const notification = wrapper.find('.notification-bar-action'); | ||
notification.simulate('click'); | ||
// Expect handleClick to be caled once | ||
expect(handleClickGlobal).to.have.property('callCount', 1); | ||
}); | ||
/** | ||
* Test: Local handling of onClick: | ||
* | ||
* If a child notification in the stack has an onClick property, | ||
* then fire onClick on the child. | ||
*/ | ||
it('onClick fires locally when notification action is clicked', () => { | ||
const handleClickLocal = spy(); | ||
const handleClickGlobal = spy(); | ||
// Render a notification stack with one notification child | ||
const wrapper = mount( | ||
<NotificationStack | ||
notifications={[{ | ||
key: mockNotification.key, | ||
message: mockNotification.message, | ||
action: mockNotification.action, | ||
dismissAfter: mockNotification.dismissAfter, | ||
title: mockNotification.title, | ||
onClick: handleClickLocal | ||
}]} | ||
// Instead, it is handled globally | ||
onClick={handleClickGlobal} | ||
onDismiss={() => {}} | ||
/> | ||
); | ||
// Simulate a click on the notification in the stack | ||
const notification = wrapper.find('.notification-bar-action'); | ||
notification.simulate('click'); | ||
// Expect local to be called once and global to be called 0 | ||
expect(handleClickLocal).to.have.property('callCount', 1); | ||
expect(handleClickGlobal).to.have.property('callCount', 0); | ||
}); | ||
it('onDismiss fires for notification', (done) => { | ||
const handleDismiss = spy(); | ||
const wrapper = mount( | ||
<NotificationStack | ||
notifications={notifications} | ||
onDismiss={handleDismiss} | ||
/> | ||
); | ||
wrapper.update(); | ||
setTimeout(() => { | ||
try { | ||
expect(handleDismiss.callCount).to.equal(notifications.length); | ||
done(); | ||
} catch (error) { | ||
throw error; | ||
} | ||
// Add transition time + 1000ms per each Notification | ||
}, mockNotification.dismissAfter + 1340); | ||
}); | ||
it('onDismiss does not fire until `dismissAfter` value times out', () => { | ||
const handleDismiss = spy(); | ||
// eslint-disable-next-line | ||
const wrapper = shallow( | ||
<NotificationStack | ||
notifications={[mockNotification]} | ||
onDismiss={handleDismiss} | ||
/> | ||
); | ||
expect(handleDismiss.calledOnce).to.equal(false); | ||
}); | ||
it('onDismiss fires after `dismissAfter` value', (done) => { | ||
const handleDismiss = spy(); | ||
const wrapper = mount( | ||
<NotificationStack | ||
notifications={notifications} | ||
onDismiss={handleDismiss} | ||
/> | ||
); | ||
wrapper.update(); | ||
setTimeout(() => { | ||
try { | ||
expect(handleDismiss.calledOnce).to.equal(true); | ||
done(); | ||
} catch (error) { | ||
throw error; | ||
} | ||
// Add time due to each StackedNotification transition time ( > 300 ) | ||
}, mockNotification.dismissAfter + 340); | ||
}); | ||
}); |
@@ -21,3 +21,1 @@ # 1.0.0 to 2.0.0 | ||
``` | ||
If you have questions, please feel free to create an issue on GitHub or message me (@pburtchaell) on the [Reactiflux Slack community](http://www.reactiflux.com/). |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
530229
35
1076
23