Skip to content

Commit

Permalink
feat(react-alerts): create react alerts
Browse files Browse the repository at this point in the history
- React alerts can be dismissable (adds a close button)
- Close button has optional callback
- React alerts can have icons

[Finishes #85646924]

Signed-off-by: Nicole Sullivan <nsullivan@pivotal.io>
  • Loading branch information
Geoff Pleiss committed Jan 14, 2015
1 parent f50c235 commit 265e99d
Show file tree
Hide file tree
Showing 4 changed files with 364 additions and 1 deletion.
107 changes: 107 additions & 0 deletions src/pivotal-ui/components/alerts.scss
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,110 @@ other content inside a container on the page (of a width 500px for this example)
}
}

/*doc
---
title: React Alerts
name: react_alerts
categories:
- Beta
---
React alerts
```html_example_table
<div id="react-alert-example"></div>
```
```jsx_example
React.render(
<div>
<UI.SuccessAlert>success</UI.SuccessAlert>
<UI.InfoAlert>info</UI.InfoAlert>
<UI.WarningAlert>warning</UI.WarningAlert>
<UI.ErrorAlert>error</UI.ErrorAlert>
</div>,
document.getElementById('react-alert-example')
);
```
*/

/*doc
---
title: Dismissable React Alerts
name: react_alerts_dismissable
parent: react_alerts
---
Add the `dismissable` property to add a close button to the alert.
```html_example_table
<div id="react-alert-dismissable-example"></div>
```
```jsx_example
React.render(
<UI.SuccessAlert dismissable>success</UI.SuccessAlert>,
document.getElementById('react-alert-dismissable-example')
);
```
If you want a callback to be called when the close button is
clicked, set the `dismissable` property to that callback.
```html_example_table
<div id="react-alert-dismissable-callback-example"></div>
```
```jsx_example
var callback = function() {
alert('Dismissed!');
};
React.render(
<UI.InfoAlert dismissable={callback}>with callback</UI.InfoAlert>,
document.getElementById('react-alert-dismissable-callback-example')
);
```
*/

/*doc
---
title: React Alerts with Icons
name: react_alerts_icon
parent: react_alerts
---
If you want an icon to be displayed, set the `withIcon` property.
```html_example_table
<div id="react-alert-icon-example"></div>
```
```jsx_example
React.render(
<div>
<UI.SuccessAlert withIcon>success</UI.SuccessAlert>
<UI.InfoAlert withIcon>info</UI.InfoAlert>
<UI.WarningAlert withIcon>warning</UI.WarningAlert>
<UI.ErrorAlert withIcon>error</UI.ErrorAlert>
</div>,
document.getElementById('react-alert-icon-example')
);
```
Here's a dismissable alert with an icon
```html_example_table
<div id="react-alert-icon-dismissable-example"></div>
```
```jsx_example
React.render(
<UI.WarningAlert dismissable withIcon>warning</UI.WarningAlert>,
document.getElementById('react-alert-icon-dismissable-example')
);
```
*/
87 changes: 87 additions & 0 deletions src/pivotal-ui/javascripts/alerts.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use strict';

var React = require('react');
var BsAlert = require('react-bootstrap/Alert');
var Media = require('./media.jsx').Media;

var Alert = React.createClass({
getInitialState: function() {
return {
alertVisible: true
};
},

render: function() {
if (this.state.alertVisible) {
var onDismiss = this.props.dismissable ? this.handleAlertDismiss : null;

var children;

if (this.props.withIcon) {
var icon = <i className={"fa " + this.props.alertIcon}></i>;
children = (
<Media leftImage={icon}>
{this.props.children}
</Media>
);
} else {
children = this.props.children;
}
return (
<BsAlert onDismiss={onDismiss} {...this.props }>
{children}
</BsAlert>
);
}

return (
<span />
);
},

handleAlertDismiss: function() {
if (typeof this.props.dismissable === "function") {
this.props.dismissable();
}
this.setState({alertVisible: false});
}
});

var SuccessAlert = React.createClass({
render: function() {
return (
<Alert bsStyle="success" alertIcon="fa-check-circle" {...this.props} />
);
}
});

var InfoAlert = React.createClass({
render: function() {
return (
<Alert bsStyle="info" alertIcon="fa-info-circle" {...this.props } />
);
}
});

var WarningAlert = React.createClass({
render: function() {
return (
<Alert bsStyle="warning" alertIcon="fa-exclamation-triangle" {...this.props } />
);
}
});

var ErrorAlert = React.createClass({
render: function() {
return (
<Alert bsStyle="danger" alertIcon="fa-exclamation-triangle" {...this.props } />
);
}
});

module.exports = {
SuccessAlert: SuccessAlert,
InfoAlert: InfoAlert,
WarningAlert: WarningAlert,
ErrorAlert: ErrorAlert
};
7 changes: 6 additions & 1 deletion src/pivotal-ui/javascripts/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,10 @@ module.exports = {
Label: require('./labels.jsx').Label,
CollapseBase: require('./collapse.jsx').CollapseBase,
Collapse: require('./collapse.jsx').Collapse,
CollapseAlt: require('./collapse.jsx').CollapseAlt
CollapseAlt: require('./collapse.jsx').CollapseAlt,

SuccessAlert: require('./alerts.jsx').SuccessAlert,
InfoAlert: require('./alerts.jsx').InfoAlert,
WarningAlert: require('./alerts.jsx').WarningAlert,
ErrorAlert: require('./alerts.jsx').ErrorAlert,
};
164 changes: 164 additions & 0 deletions test/spec/javascripts/alerts_spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
'use strict';

var $ = require('jquery');
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;

var SuccessAlert = require('../../../src/pivotal-ui/javascripts/alerts.jsx').SuccessAlert;
var InfoAlert = require('../../../src/pivotal-ui/javascripts/alerts.jsx').InfoAlert;
var WarningAlert = require('../../../src/pivotal-ui/javascripts/alerts.jsx').WarningAlert;
var ErrorAlert = require('../../../src/pivotal-ui/javascripts/alerts.jsx').ErrorAlert;

describe('Alert', function() {
beforeEach(function() {
this.node = $('<div id="container"></div>').appendTo('body').get(0);
});

afterEach(function() {
React.unmountComponentAtNode(this.node);
document.body.removeChild(this.node);
});

describe('when dismissable is set to true', function() {
beforeEach(function() {
React.render(
<SuccessAlert dismissable>alert body</SuccessAlert>,
this.node
);
});

it('has a close button', function() {
expect($('#container button.close')).toExist();
});

it('disappears when close button is clicked', function() {
TestUtils.Simulate.click($('#container button.close').get(0));
expect($('#container .alert')).not.toExist();
});
});

describe('when dismissable is set to a callback', function() {
beforeEach(function() {
this.callback = jasmine.createSpy('dismissable callback');
React.render(
<SuccessAlert dismissable={this.callback}>alert body</SuccessAlert>,
this.node
);
});

it('has a close button', function() {
expect($('#container button.close')).toExist();
});

describe('when close button is clicked', function() {
beforeEach(function() {
TestUtils.Simulate.click($('#container button.close').get(0));
});

it('disappears', function() {
expect($('#container .alert')).not.toExist();
});

it('calls the callback passed in', function() {
expect(this.callback).toHaveBeenCalled();
});
});
});

describe('when dismissable is not present', function() {
beforeEach(function() {
React.render(
<SuccessAlert>alert body</SuccessAlert>,
this.node
);
});

it('does not have a close button', function() {
expect($('#container button.close')).not.toExist();
});
});
});

describe('SuccessAlert', function() {
describe('when withIcon is set to true', function() {
beforeEach(function() {
this.node = $('<div id="container"></div>').appendTo('body').get(0);
React.render(
<SuccessAlert withIcon>alert body</SuccessAlert>,
this.node
);
});

afterEach(function() {
React.unmountComponentAtNode(this.node);
document.body.removeChild(this.node);
});

it('renders an icon in the alert', function() {
expect($('#container i')).toHaveClass('fa-check-circle');
});
});
});

describe('InfoAlert', function() {
describe('when withIcon is set to true', function() {
beforeEach(function() {
this.node = $('<div id="container"></div>').appendTo('body').get(0);
React.render(
<InfoAlert withIcon>alert body</InfoAlert>,
this.node
);
});

afterEach(function() {
React.unmountComponentAtNode(this.node);
document.body.removeChild(this.node);
});

it('renders an icon in the alert', function() {
expect($('#container i')).toHaveClass('fa-info-circle');
});
});
});

describe('WarningAlert', function() {
describe('when withIcon is set to true', function() {
beforeEach(function() {
this.node = $('<div id="container"></div>').appendTo('body').get(0);
React.render(
<WarningAlert withIcon>alert body</WarningAlert>,
this.node
);
});

afterEach(function() {
React.unmountComponentAtNode(this.node);
document.body.removeChild(this.node);
});

it('renders an icon in the alert', function() {
expect($('#container i')).toHaveClass('fa-exclamation-triangle');
});
});
});

describe('ErrorAlert', function() {
describe('when withIcon is set to true', function() {
beforeEach(function() {
this.node = $('<div id="container"></div>').appendTo('body').get(0);
React.render(
<ErrorAlert withIcon>alert body</ErrorAlert>,
this.node
);
});

afterEach(function() {
React.unmountComponentAtNode(this.node);
document.body.removeChild(this.node);
});

it('renders an icon in the alert', function() {
expect($('#container i')).toHaveClass('fa-exclamation-triangle');
});
});
});

0 comments on commit 265e99d

Please sign in to comment.