Skip to content

Commit

Permalink
Merge pull request #112 from reactioncommerce/toasts
Browse files Browse the repository at this point in the history
feat(toast): Toast component
  • Loading branch information
mikemurray authored Oct 4, 2019
2 parents 9660845 + 1e0a623 commit f89aa31
Show file tree
Hide file tree
Showing 12 changed files with 671 additions and 5 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@
"dependencies": {
"@babel/polyfill": "~7.2.5",
"@material-ui/core": "~4.3.2",
"mdi-material-ui": "~5.8.0",
"@reactioncommerce/components-context": "~1.2.0",
"commitlint": "^8.0.0",
"mdi-material-ui": "~5.8.0",
"notistack": "^0.9.2",
"prop-types": "~15.6.2",
"react": "~16.9.0",
"react-dom": "~16.9.0"
Expand Down
54 changes: 54 additions & 0 deletions package/src/components/Toast/Toast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from "react";
import PropTypes from "prop-types";
import { Snackbar } from "@material-ui/core";
import { ToastWrapper } from "./helpers";

/**
* @name Toast
* @param {Object} props Component props
* @returns {React.Component} returns a React component
*/
const Toast = React.forwardRef(function Toast(props, ref) {
const { message, variant, title, onClose, ...otherProps } = props;

return (
<Snackbar
ref={ref}
{...otherProps}
>
<ToastWrapper
props={otherProps}
variant={variant}
title={title}
message={message}
onClose={onClose}
/>
</Snackbar>
);
});


Toast.propTypes = {
/**
* Message: Node, <span>Message goes here</span>
*/
message: PropTypes.node,
/**
* onClose: Callback fired when the component requests to be closed and when the X icon button is clicked
*/
onClose: PropTypes.func.isRequired,
/**
* Title: Optional string, displayed in bold
*/
title: PropTypes.string,
/**
* Variant: Info, Success, Warning, Error
*/
variant: PropTypes.oneOf(["error", "info", "success", "warning"]).isRequired
};

Toast.defaultProps = {
variant: "info"
};

export default Toast;
204 changes: 204 additions & 0 deletions package/src/components/Toast/Toast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
### Overview

Toasts are used to give action-based feedback messages and convey critical or informational account-related messages. Use Toasts when a user needs more detailed information for an action.

The Toast component inherits from the Material-UI [Snackbar component](https://material-ui.com/components/snackbars/). Refer to the Material-UI [Snackbar docs](https://material-ui.com/api/snackbar/) for more information.

### Usage

Toasts are most often used when the user has taken an action. Messages appear in context and communicate when that action is successful, unsuccessful, or that it otherwise needs attention and further context.

Language should be polite, clear and concise. Optionally, a title can be added to a Toast to give clarity, or when there are 2 or more lines of information to display.

Toasts should guide the user into taking corrective action if necessary.

Users should be able to dismiss Toasts when appropriate. Information and success alerts can close automatically after 10 seconds. Error alerts should be persistent, and close only when action is resolved.

#### Types

##### Default: Information

- Used when there is information or tips that users can benefit from
- Can close automatically after 10 seconds

```jsx
import Button from "../Button";
import IconButton from "@material-ui/core/IconButton";

function OpenToast(props) {
const [open, setOpen] = React.useState(false);

const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}

setOpen(false);
};

return (
<div>
<Button variant="contained" color="primary" onClick={() => setOpen(true)}>Open information toast</Button>
<Button variant="outlined" color="primary" onClick={() => setOpen(false)}>Close information toast</Button>
<Toast
open={open}
onClose={handleClose}
message={props.message}
variant={props.variant}
title={props.title}
/>
</div>
);
}

<OpenToast message={<span>Information toast</span>} />
```
##### Success

- Used when an action has been completed successfully
- Can close automatically after 10 seconds

```jsx
import Button from "../Button";
import IconButton from "@material-ui/core/IconButton";

function OpenToast(props) {
const [open, setOpen] = React.useState(false);

const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}

setOpen(false);
};

return (
<div>
<Button variant="contained" color="primary" onClick={() => setOpen(true)}>Open success toast</Button>
<Button variant="outlined" color="primary" onClick={() => setOpen(false)}>Close success toast</Button>
<Toast
open={open}
onClose={handleClose}
message={props.message}
variant={props.variant}
title={props.title}
/>
</div>
);
}

<OpenToast message={<span>Success toast</span>} variant="success" />
```

##### Warning

- Used when an action or item needs attention
- Should not close automatically, unless the action has been resolved

```jsx
import Button from "../Button";
import IconButton from "@material-ui/core/IconButton";

function OpenToast(props) {
const [open, setOpen] = React.useState(false);

const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}

setOpen(false);
};

return (
<div>
<Button variant="contained" color="primary" onClick={() => setOpen(true)}>Open warning toast</Button>
<Button variant="outlined" color="primary" onClick={() => setOpen(false)}>Close warning toast</Button>
<Toast
open={open}
onClose={handleClose}
message={props.message}
variant={props.variant}
title={props.title}
/>
</div>
);
}

<OpenToast message={<span>Warning toast</span>} variant="warning" />
```
##### Error

- Used when the system has failed to complete an action, or the user has made an error
- Should not close automatically, unless the action has been resolved

```jsx
import Button from "../Button";
import IconButton from "@material-ui/core/IconButton";

function OpenToast(props) {
const [open, setOpen] = React.useState(false);

const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}

setOpen(false);
};

return (
<div>
<Button variant="contained" color="error" onClick={() => setOpen(true)}>Open error toast</Button>
<Button variant="outlined" color="error" onClick={() => setOpen(false)}>Close error toast</Button>
<Toast
open={open}
onClose={handleClose}
message={props.message}
variant={props.variant}
title={props.title}
/>
</div>
);
}

<OpenToast message={<span>Error toast</span>} variant="error" />
```

#### Toasts with Titles

- All toasts can also have a `Title`

```jsx
import Button from "../Button";
import IconButton from "@material-ui/core/IconButton";

function OpenToast(props) {
const [open, setOpen] = React.useState(false);

const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}

setOpen(false);
};

return (
<div>
<Button variant="contained" color="primary" onClick={() => setOpen(true)}>Open title toast</Button>
<Button variant="outlined" color="primary" onClick={() => setOpen(false)}>Close title toast</Button>
<Toast
open={open}
onClose={handleClose}
message={props.message}
variant={props.variant}
title={props.title}
/>
</div>
);
}

<OpenToast message={<span>Information toast with title</span>} title="Title" />
```
28 changes: 28 additions & 0 deletions package/src/components/Toast/Toast.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import { render } from "../../tests/index.js";
import Toast from "./Toast";

test("basic snapshot - only default props", () => {
const { asFragment } = render(<Toast message="Test message" open/>);
expect(asFragment()).toMatchSnapshot();
});

test("basic snapshot - information variant", () => {
const { asFragment } = render(<Toast message="Test message" variant="info" open/>);
expect(asFragment()).toMatchSnapshot();
});

test("basic snapshot - success variant", () => {
const { asFragment } = render(<Toast message="Test message" variant="success" open/>);
expect(asFragment()).toMatchSnapshot();
});

test("basic snapshot - error variant", () => {
const { asFragment } = render(<Toast message="Test message" variant="error" open/>);
expect(asFragment()).toMatchSnapshot();
});

test("basic snapshot - warning variant", () => {
const { asFragment } = render(<Toast message="Test message" variant="warning" open/>);
expect(asFragment()).toMatchSnapshot();
});
Loading

0 comments on commit f89aa31

Please sign in to comment.