diff --git a/README.md b/README.md index 464f1b6..f4397fe 100644 --- a/README.md +++ b/README.md @@ -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 `` (see HTML template below) | +| containerClassName | string | "" | Additional CSS class(es) to add to `` (see HTML template below) | +| itemClassName | string | "" | Additional CSS class(es) to add to `` (see HTML template below) | +| renderSystem | function | *built-in render function* | Custom render function for notification system, receives ``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 ``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 @@ -190,12 +195,12 @@ To disable all inline styles, just pass `false` to the prop `style`. ``` -Here is the notification HTML: +Here is the notification HTML (if not using custom render functions): ```html -
-
-
+
+
+

Default title

Default message
× diff --git a/src/NotificationContainer.jsx b/src/NotificationContainer.jsx index 0b6e044..4b41653 100644 --- a/src/NotificationContainer.jsx +++ b/src/NotificationContainer.jsx @@ -9,13 +9,19 @@ 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)) { @@ -23,6 +29,14 @@ var NotificationContainer = createReactClass({ } }, + _renderDefault: function(notifications) { + return ( +
+ { notifications } +
+ ) + }, + render: function() { var self = this; var notifications; @@ -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 ( -
- { notifications } -
- ); + return (this.props.renderContainer || this._renderDefault)(notifications); } }); diff --git a/src/NotificationItem.jsx b/src/NotificationItem.jsx index d578382..9d53022 100644 --- a/src/NotificationItem.jsx +++ b/src/NotificationItem.jsx @@ -36,7 +36,10 @@ var NotificationItem = createReactClass({ children: PropTypes.oneOfType([ PropTypes.string, PropTypes.element - ]) + ]), + + renderItem: PropTypes.func, + itemClassName: PropTypes.string, }, getDefaultProps: function() { @@ -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); @@ -323,13 +326,17 @@ var NotificationItem = createReactClass({ } return ( -
+
{ title } { message } { dismiss } { actionButton }
); + }, + + render: function() { + return (this.props.renderItem || this._renderDefault)(this.props.notification, this._dismiss, this.state.visible); } }); diff --git a/src/NotificationSystem.jsx b/src/NotificationSystem.jsx index b661a7d..35cde98 100644 --- a/src/NotificationSystem.jsx +++ b/src/NotificationSystem.jsx @@ -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: '' }; }, @@ -232,6 +244,14 @@ var NotificationSystem = createReactClass({ this._isMounted = false; }, + _renderDefault: function(containers) { + return ( +
+ { containers } +
+ ); + }, + render: function() { var self = this; var containers = null; @@ -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 ( -
- { containers } -
- ); + return (this.props.renderSystem || this._renderDefault)(containers); } }); diff --git a/test/notification-system.test.js b/test/notification-system.test.js index 44b17a7..712d390 100644 --- a/test/notification-system.test.js +++ b/test/notification-system.test.js @@ -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'); @@ -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 ; + } + function renderContainer(children) { + return ; + } + function renderItem(_params) { + return ; + } + + beforeEach(() => { + // We need to create this wrapper so we can use refs + class ElementWrapper extends Component { + render() { + return ; + } + } + 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 ; + } + } + 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(); + }); +});