Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/custom component #147

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ The notification object has the following properties:
| onAdd | function | null | A callback function that will be called when the notification is successfully added. The first argument is the original notification e.g. `function (notification) { console.log(notification.title + 'was added'); }` |
| onRemove | function | null | A callback function that will be called when the notification is about to be removed. The first argument is the original notification e.g. `function (notification) { console.log(notification.title + 'was removed'); }` |
| uid | integer/string | null | Overrides the internal `uid`. Useful if you are managing your notifications id. Notifications with same `uid` won't be displayed. |

| systemClassName | string | "" | Additional CSS class(es) to add to `<NotificationSystem />` (see HTML template below) |
| containerClassName | string | "" | Additional CSS class(es) to add to `<NotificationContainer />` (see HTML template below) |
| itemClassName | string | "" | Additional CSS class(es) to add to `<NotificationItem />` (see HTML template below) |
| renderSystem | function | *built-in render function* | Custom render function for notification system, receives `<NotificationContainer />`s as param (this allows to use other component rather than `div.notifications-wrapper`). Overrides `systemClassName` |
| renderContainer | function | *built-in render function* | Custom render function for notification container, receives `<NotificationItem />`s as param (this allows to use other component rather than `div.notifications-{position}`). Overrides `containerClassName` |
| renderItem | function | *built-in render function* | Custom render function for notification, receives *notification object*, *dismiss callback* and *`visible` state* as params (this allows to use other component rather than `div.notification` with it's children). Overrides `itemClassName` |

### Dismissible

Expand Down Expand Up @@ -190,12 +195,12 @@ To disable all inline styles, just pass `false` to the prop `style`.
<NotificationSystem ref="notificationSystem" style={false} />
```

Here is the notification HTML:
Here is the notification HTML (if not using custom render functions):

```html
<div class="notifications-wrapper">
<div class="notifications-{position}"> <!-- '{position}' can be one of the positions available: ex: notifications-tr -->
<div class="notification notification-{level} notification-{state} {notification-not-dismissible}"> <!-- '{level}' can be: success | error | warning | info. '{state}' can be: visible | hidden. {notification-not-dismissible} is present if notification is not dismissible by user -->
<div class="notifications-wrapper {props.systemClassName}">
<div class="notifications-{position} {props.containerClassName}"> <!-- '{position}' can be one of the positions available: ex: notifications-tr -->
<div class="notification notification-{level} notification-{state} {notification-not-dismissible} {props.itemClassName}"> <!-- '{level}' can be: success | error | warning | info. '{state}' can be: visible | hidden. {notification-not-dismissible} is present if notification is not dismissible by user -->
<h4 class="notification-title">Default title</h4>
<div class="notification-message">Default message</div>
<span class="notification-dismiss">×</span>
Expand Down
26 changes: 19 additions & 7 deletions src/NotificationContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,34 @@ var NotificationContainer = createReactClass({
propTypes: {
position: PropTypes.string.isRequired,
notifications: PropTypes.array.isRequired,
getStyles: PropTypes.object
getStyles: PropTypes.object,

renderContainer: PropTypes.func,
renderItem: PropTypes.func,

containerClassName: PropTypes.string,
itemClassName: PropTypes.string
},

_style: {},

componentWillMount: function() {
// Fix position if width is overrided
// Fix position if width is overridden
this._style = this.props.getStyles.container(this.props.position);

if (this.props.getStyles.overrideWidth && (this.props.position === Constants.positions.tc || this.props.position === Constants.positions.bc)) {
this._style.marginLeft = -(this.props.getStyles.overrideWidth / 2);
}
},

_renderDefault: function(notifications) {
return (
<div className={ 'notifications-' + this.props.position + ' ' + this.props.containerClassName } style={ this._style }>
{ notifications }
</div>
)
},

render: function() {
var self = this;
var notifications;
Expand All @@ -42,15 +56,13 @@ var NotificationContainer = createReactClass({
noAnimation={ self.props.noAnimation }
allowHTML={ self.props.allowHTML }
children={ self.props.children }
renderItem={ self.props.renderItem }
itemClassName={ self.props.itemClassName }
/>
);
});

return (
<div className={ 'notifications-' + this.props.position } style={ this._style }>
{ notifications }
</div>
);
return (this.props.renderContainer || this._renderDefault)(notifications);
}
});

Expand Down
13 changes: 10 additions & 3 deletions src/NotificationItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ var NotificationItem = createReactClass({
children: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element
])
]),

renderItem: PropTypes.func,
itemClassName: PropTypes.string,
},

getDefaultProps: function() {
Expand Down Expand Up @@ -247,7 +250,7 @@ var NotificationItem = createReactClass({
return { __html: string };
},

render: function() {
_renderDefault: function() {
var notification = this.props.notification;
var className = 'notification notification-' + notification.level;
var notificationStyle = merge({}, this._styles.notification);
Expand Down Expand Up @@ -323,13 +326,17 @@ var NotificationItem = createReactClass({
}

return (
<div className={ className } onClick={ this._handleNotificationClick } onMouseEnter={ this._handleMouseEnter } onMouseLeave={ this._handleMouseLeave } style={ notificationStyle }>
<div className={ className + ' ' + this.props.itemClassName } onClick={ this._handleNotificationClick } onMouseEnter={ this._handleMouseEnter } onMouseLeave={ this._handleMouseLeave } style={ notificationStyle }>
{ title }
{ message }
{ dismiss }
{ actionButton }
</div>
);
},

render: function() {
return (this.props.renderItem || this._renderDefault)(this.props.notification, this._dismiss, this.state.visible);
}

});
Expand Down
35 changes: 27 additions & 8 deletions src/NotificationSystem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,26 @@ var NotificationSystem = createReactClass({
PropTypes.object
]),
noAnimation: PropTypes.bool,
allowHTML: PropTypes.bool
allowHTML: PropTypes.bool,

renderSystem: PropTypes.func,
renderContainer: PropTypes.func,
renderItem: PropTypes.func,

systemClassName: PropTypes.string,
containerClassName: PropTypes.string,
itemClassName: PropTypes.string
},

getDefaultProps: function() {
return {
style: {},
noAnimation: false,
allowHTML: false
allowHTML: false,

systemClassName: '',
containerClassName: '',
itemClassName: ''
};
},

Expand Down Expand Up @@ -232,6 +244,14 @@ var NotificationSystem = createReactClass({
this._isMounted = false;
},

_renderDefault: function(containers) {
return (
<div className={'notifications-wrapper ' + this.props.systemClassName} style={ this._getStyles.wrapper() }>
{ containers }
</div>
);
},

render: function() {
var self = this;
var containers = null;
Expand All @@ -257,17 +277,16 @@ var NotificationSystem = createReactClass({
onRemove={ self._didNotificationRemoved }
noAnimation={ self.props.noAnimation }
allowHTML={ self.props.allowHTML }
renderContainer={ self.props.renderContainer }
renderItem={ self.props.renderItem }
containerClassName={ self.props.containerClassName }
itemClassName={ self.props.itemClassName }
/>
);
});
}


return (
<div className="notifications-wrapper" style={ this._getStyles.wrapper() }>
{ containers }
</div>
);
return (this.props.renderSystem || this._renderDefault)(containers);
}
});

Expand Down
114 changes: 113 additions & 1 deletion test/notification-system.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ describe('Notification Component', function() {
done();
});

it('should render containers with a overrided width', done => {
it('should render containers with a overridden width', done => {
notificationObj.position = 'tc';
component.addNotification(notificationObj);
let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notifications-tc');
Expand Down Expand Up @@ -443,3 +443,115 @@ describe('Notification Component', function() {
done();
});
});

describe('Notification Component with custom render functions', function() {
let node;
let instance;
let component;
let clock;
let notificationObj;
const ref = 'notificationSystem';
let customComponents;

this.timeout(10000);

function renderSystem(children) {
return <label className="custom-system">{children}</label>;
}
function renderContainer(children) {
return <label className="custom-container">{children}</label>;
}
function renderItem(_params) {
return <label className="custom-item">item</label>;
}

beforeEach(() => {
// We need to create this wrapper so we can use refs
class ElementWrapper extends Component {
render() {
return <NotificationSystem ref={ ref } style={ style } allowHTML={ true } noAnimation={ true } renderSystem={ renderSystem } renderContainer={ renderContainer } renderItem={ renderItem } />;
}
}
node = window.document.createElement('div');
instance = TestUtils.renderIntoDocument(React.createElement(ElementWrapper), node);
component = instance.refs[ref];
notificationObj = merge({}, defaultNotification);

clock = sinon.useFakeTimers();

component.addNotification(defaultNotification);
});

afterEach(() => {
clock.restore();
});

it('should have custom wrapper rendered', done => {
customComponents = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'custom-system');
expect(customComponents.length).to.equal(1);
done();
});

it('should have custom container rendered', done => {
customComponents = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'custom-container');
expect(customComponents.length).to.equal(1);
done();
});

it('should have custom items rendered', done => {
customComponents = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'custom-item');
expect(customComponents.length).to.equal(1);
done();
});
});

describe('Notification Component with custom classNames', function() {
let node;
let instance;
let component;
let clock;
let notificationObj;
const ref = 'notificationSystem';
let customComponent;

this.timeout(10000);

beforeEach(() => {
// We need to create this wrapper so we can use refs
class ElementWrapper extends Component {
render() {
return <NotificationSystem ref={ ref } style={ style } allowHTML={ true } noAnimation={ true } systemClassName="customsystem" containerClassName="customcontainer" itemClassName="customitem" />;
}
}
node = window.document.createElement('div');
instance = TestUtils.renderIntoDocument(React.createElement(ElementWrapper), node);
component = instance.refs[ref];
notificationObj = merge({}, defaultNotification);

clock = sinon.useFakeTimers();

component.addNotification(defaultNotification);
});

afterEach(() => {
clock.restore();
});

it('should have custom wrapper rendered', done => {
customComponent = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'customsystem');
expect(customComponent.length).to.equal(1);
done();
});

it('should have custom container rendered', done => {
customComponent = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'customcontainer');
expect(customComponent.length).to.equal(1);
done();
});

it('should have custom items rendered', done => {
customComponent = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'customitem');
expect(customComponent.length).to.equal(1);
done();
});
});