Skip to content

Commit a9012f4

Browse files
authored
Use react portal (#26)
* react 16 and portals #closes 23 * add unpkg links * update readme * update readme * add missing peer dep * write tests for Notify
1 parent 415164c commit a9012f4

File tree

9 files changed

+388
-63
lines changed

9 files changed

+388
-63
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ React redux notify is a simple yet flexible component for displaying notificatio
1414
```javascript
1515
npm install react-redux-notify --save
1616
```
17+
**NOTE :Version ^4.0.0 requires react@^16.0.0, versions 3.x.x and below supports react@>=15.3.0 but has no portal support.**
1718

1819
After which you can import the component and the default styles for use:
1920
```javascript
@@ -23,8 +24,8 @@ import 'react-redux-notify/dist/ReactReduxNotify.css';
2324

2425
You can also use the standalone build by including the following css and js files (both minified and unminified exist). You will need to ensure that you have **react**, **redux**, **react-redux** dependecies also included externally.
2526
```javascript
26-
<script src="dist/ReactReduxNotify.min.js"></script>
27-
<link rel="stylesheet" href="dist/ReactReduxNotify.min.css">
27+
<script src="https://unpkg.com/react-redux-notify/dist/ReactReduxNotify.min.js"></script>
28+
<link rel="stylesheet" href="https://unpkg.com/react-redux-notify/dist/ReactReduxNotify.min.css">
2829
```
2930

3031
## Demo and Example
@@ -53,7 +54,7 @@ const mySuccessNotification = {
5354
message: 'You have been logged in!',
5455
type: NOTIFICATION_TYPE_SUCCESS,
5556
duration: 0,
56-
canDimiss: true,
57+
canDismiss: true,
5758
icon: <i className="fa fa-check" />
5859
}
5960

@@ -88,14 +89,14 @@ There are a number of options that can be used to change the look and behaviour
8889

8990
### Notify Container Component
9091
| Property | Type | Default | Default Options | Description |
91-
| -------- | ---- | ------- | --------------- | ----------- |
92-
| styles | `object` | CSSModules created mapping (see below) | | The default styles created and mapped through CSSModules for the component.
92+
| -------- | ---- | ------- | --------------- | ----------- |
9393
| customStyles | `object` | | | A custom styles object that gets merged into the default styles and allows for the overriding or creation of individual styles using your own classes.
9494
| notificationComponent | `func` | (see Notification Component below) | | A custom notification you want to use as the default Notification component to render.
9595
| transitionDurations | `object` | `{ enter : 160, leave: 400 }` | | React CSS Transition Group timeout values for enter and leave events. If you change the transition classes then you can use these to change the timeout values for your animation.
9696
| position | `string` | 'TopRight' | `'TopRight', 'BottomRight', 'BottomLeft', 'TopLeft'` | Default options for where the Notify container will be positioned to render you components. Again this can be extended/customised through your own classes and this prop.
9797
| forceClose | `boolean` | 'false' | | If set to true will remove all notifications regardless of the notification's `canDismiss` value.
9898
| localization | `object` | `{ closeAllBtnText: 'Close All', acceptBtnText: 'Accept', denyBtnText: 'Deny' }` | | Text that shows for the mentioned buttons.
99+
| node | `domNode` | `document.createElement('div')` appended to document.body | | The portal node into which the notification component will get rendered into.
99100

100101
#### Notify Component Styles
101102
This is the default style mapping created. You can choose to override these with your own classes using the `customStyles` prop explained above. You can view what these CSS classes do by default in the `src/components/Notify/` folder for `react-redux-notify` in the `node_modules` directory.
@@ -188,10 +189,10 @@ npm start
188189
```
189190

190191
## To Do
191-
* Complete tests for Notify Component (awaiting react 15.4.0 see [react issue](https://github.com/facebook/react/issues/7386)).
192+
* Have an online demo page.
192193
* Test browser compatibility.
193194
* Allow mounting reducer at custom key.
194-
* Have an online demo page.
195+
* ~~Complete tests for Notify Component (awaiting react 15.4.0 see [react issue](https://github.com/facebook/react/issues/7386)).~~
195196
* ~~Support for use of inline styles. (Ive decided to not go ahead with this. If you think this is bad decision please feel free to open an issue for discussion, create a PR or use a custom notification component. )~~
196197

197198
## License

examples/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<div id="root"></div>
3636
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
3737
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
38+
<script src="https://unpkg.com/prop-types/prop-types.min.js"></script>
3839
<script src="../node_modules/redux/dist/redux.js"></script>
3940
<script src="../node_modules/react-redux/dist/react-redux.js"></script>
4041
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-sham.min.js"></script>

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-redux-notify",
3-
"version": "3.0.0",
3+
"version": "4.0.0",
44
"description": "A simple notifications component built for use with React and Redux.",
55
"main": "./lib/index.js",
66
"files": [
@@ -92,8 +92,9 @@
9292
"react-transition-group": "^1.2.1"
9393
},
9494
"peerDependencies": {
95-
"prop-types": ">= 15.3.0 < 17.0.0",
96-
"react": ">= 15.3.0 < 17.0.0",
95+
"prop-types": ">= 16.0.0 < 17.0.0",
96+
"react": ">= 16.0.0 < 17.0.0",
97+
"react-dom": ">= 16.0.0 < 17.0.0",
9798
"react-redux": ">= 4.0.0 < 6.0.0",
9899
"redux": ">= 3.0.0 < 4.0.0"
99100
},

src/components/Notification/index.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class Notification extends React.PureComponent {
4747
}
4848
}
4949

50-
getStyle(name) {
50+
_getStyle(name) {
5151
return this.props.customStyles[name] || styles[name];
5252
}
5353

@@ -78,13 +78,13 @@ export class Notification extends React.PureComponent {
7878
return (
7979
<div className={containerTypeClass}>
8080
{icon ? <span className={styles.icon}>{icon}</span> : false}
81-
<div className={this.getStyle('content')}>
82-
<div className={this.getStyle('item__message')}>{message}</div>
81+
<div className={this._getStyle('content')}>
82+
<div className={this._getStyle('item__message')}>{message}</div>
8383
{!canDismiss && (acceptBtn || denyBtn) ? (
84-
<div className={this.getStyle('item__btnBar')}>
84+
<div className={this._getStyle('item__btnBar')}>
8585
{acceptBtn ? (
8686
<div
87-
className={this.getStyle('actionBtn')}
87+
className={this._getStyle('actionBtn')}
8888
onClick={(e) => {
8989
acceptBtn.handler(e, this.props);
9090
}}
@@ -101,7 +101,7 @@ export class Notification extends React.PureComponent {
101101
)}
102102
{denyBtn ? (
103103
<div
104-
className={this.getStyle('actionBtn')}
104+
className={this._getStyle('actionBtn')}
105105
onClick={(e) => {
106106
denyBtn.handler(e, this.props);
107107
}}
@@ -123,15 +123,15 @@ export class Notification extends React.PureComponent {
123123
</div>
124124
{canDismiss ? (
125125
<div
126-
className={this.getStyle('close')}
126+
className={this._getStyle('close')}
127127
onClick={() => handleDismiss(id)}
128128
/>
129129
) : (
130130
false
131131
)}
132132
{isFirst && canDismiss ? (
133133
<div
134-
className={this.getStyle('close-all')}
134+
className={this._getStyle('close-all')}
135135
onClick={() => handleDismissAll()}
136136
>
137137
{localization.closeAllBtnText}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import React from 'react';
2+
import { mount } from 'enzyme';
3+
import { Notify } from 'components/Notify';
4+
import {
5+
NOTIFICATION_TYPE_SUCCESS,
6+
NOTIFICATION_TYPE_INFO,
7+
NOTIFICATION_TYPE_ERROR,
8+
} from 'modules/Notifications';
9+
10+
describe('Notify', () => {
11+
const handleRemove = jest.fn();
12+
const handleRemoveAll = jest.fn();
13+
const notification1 = {
14+
id: 1,
15+
message: 'Notification 1!',
16+
type: NOTIFICATION_TYPE_SUCCESS,
17+
duration: 0,
18+
canDismiss: true,
19+
icon: <i className="fa fa-check" />,
20+
};
21+
const notification2 = {
22+
id: 2,
23+
message: 'Notification 2!',
24+
type: NOTIFICATION_TYPE_INFO,
25+
duration: 0,
26+
canDismiss: true,
27+
icon: <i className="fa fa-check" />,
28+
};
29+
const notification3 = {
30+
id: 3,
31+
message: 'Notification 3!',
32+
type: NOTIFICATION_TYPE_ERROR,
33+
duration: 0,
34+
canDismiss: true,
35+
icon: <i className="fa fa-check" />,
36+
};
37+
const props = {
38+
notifications: [],
39+
remove: handleRemove,
40+
removeAll: handleRemoveAll,
41+
};
42+
43+
it('renders with required props', () => {
44+
const component = mount(<Notify {...props} />);
45+
expect(component).toMatchSnapshot();
46+
});
47+
48+
it('renders notifications', () => {
49+
const tProps = {
50+
...props,
51+
notifications: [notification1, notification2],
52+
};
53+
const component = mount(<Notify {...tProps} />);
54+
// TODO: Fix this, not sure whats going on here, but children's length
55+
// doesn't seem to be correct, even though the output in the
56+
// snapshot is correct.
57+
// expect(component.find(".wrapper").children()).toHaveLength(2);
58+
expect(component).toMatchSnapshot();
59+
});
60+
61+
it('renders custom notifications', () => {
62+
const MyCustomNotificationComponent = ({
63+
message,
64+
canDismiss,
65+
id,
66+
handleDismiss,
67+
}) => {
68+
let styles = {
69+
margin: '5px 0',
70+
padding: '2px 5px',
71+
border: '1px solid #333',
72+
float: 'right',
73+
clear: 'right',
74+
width: '330px',
75+
boxSizing: 'border-box',
76+
};
77+
if (canDismiss) {
78+
styles = Object.assign({}, styles, { cursor: 'pointer' });
79+
}
80+
return (
81+
<div
82+
onClick={() => {
83+
if (canDismiss) {
84+
handleDismiss(id);
85+
}
86+
}}
87+
style={styles}
88+
>
89+
{message}
90+
</div>
91+
);
92+
};
93+
const tProps = {
94+
...props,
95+
notificationComponent: MyCustomNotificationComponent,
96+
notifications: [notification1],
97+
};
98+
const component = mount(<Notify {...tProps} />);
99+
expect(component).toMatchSnapshot();
100+
expect(component.contains(MyCustomNotificationComponent)).toEqual(true);
101+
});
102+
103+
it('renders with the correct className when customStyles is used', () => {
104+
const tProps = {
105+
...props,
106+
customStyles: {
107+
containerCustomPosition: 'CustomPosition',
108+
},
109+
position: 'CustomPosition',
110+
};
111+
const component = mount(<Notify {...tProps} />);
112+
expect(component.find('.CustomPosition')).toHaveLength(1);
113+
expect(component).toMatchSnapshot();
114+
});
115+
116+
it('passes the correct props to each notification', () => {
117+
const tProps = {
118+
...props,
119+
notifications: [notification1, notification2, notification3],
120+
};
121+
const component = mount(<Notify {...tProps} />);
122+
expect(component.find({ ...notification1, isFirst: true })).toHaveLength(1);
123+
expect(component.find({ ...notification2, isFirst: false })).toHaveLength(
124+
1
125+
);
126+
expect(component.find({ ...notification3, isFirst: false })).toHaveLength(
127+
1
128+
);
129+
});
130+
131+
it('calls removeAll with the correct arguments', () => {
132+
const tProps = {
133+
...props,
134+
notifications: [notification1, notification2, notification3],
135+
};
136+
const component = mount(<Notify {...tProps} />);
137+
component.find('.close-all').simulate('click');
138+
expect(handleRemoveAll).toHaveBeenCalled();
139+
component.setProps({ forceClose: true });
140+
component.find('.close-all').simulate('click');
141+
expect(handleRemoveAll).lastCalledWith(true);
142+
});
143+
144+
it('calls remove with the correct arguments', () => {
145+
const tProps = { ...props, notifications: [notification1] };
146+
const component = mount(<Notify {...tProps} />);
147+
component
148+
.find('.close')
149+
.first()
150+
.simulate('click');
151+
expect(handleRemove).toHaveBeenCalled();
152+
});
153+
154+
it('unmounts without error', () => {
155+
const component = mount(<Notify {...props} />);
156+
const instance = component.instance();
157+
expect(instance.defaultNode).not.toEqual(null);
158+
component.unmount();
159+
expect(instance.defaultNode).toEqual(null);
160+
expect(component).toMatchSnapshot();
161+
});
162+
});

0 commit comments

Comments
 (0)