-
Notifications
You must be signed in to change notification settings - Fork 13
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
Toaster component #86
Merged
Merged
Changes from 12 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
4d05e68
Add toaster component WIP
xdrdak 3d8f861
Update jest and testing-library. Tests for toaster
xdrdak 9361bc3
Update toaster readme
xdrdak 538cca0
remove dangling TODO from toaster
xdrdak c1a3d9f
Add better responsiveness for Toaster
xdrdak fbad43a
Restrict Toaster types. Bypass click events for ToasterContainer
xdrdak 2aab158
Update changelog. Add notice that we are using an external lib
xdrdak d625e1a
Remove unused typing file
xdrdak e4d91df
Spread rest on Toaster component to auto-pause timer
xdrdak df8f8e5
Update toaster behaviour to match specs
xdrdak cbe92de
Merge branch 'next' into toaster-component
xdrdak b5ae102
Merge branch 'next' into toaster-component
xdrdak 4f6a04a
Supress warnings for logs. Fix toaster example setup
xdrdak 2275625
less jarring height transition
xdrdak 7b40551
fixing text alignment
maartenafink 372543d
Set minHeight for container at 60px
xdrdak 079aa31
Test out Percy with animations
glambert d9bb1db
Merge branch 'next' into toaster-component
glambert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# Toaster | ||
|
||
Toaster inform users on the outcome of an action. They appear temporarily, towards the bottom of the screen. They shouldn’t interrupt the user experience, and they don’t require user input to disappear. | ||
|
||
This component uses the [react-toast-notifications](https://github.com/jossmac/react-toast-notifications) library. | ||
|
||
## Usage | ||
|
||
First, wrap your application with the `<ToasterProvider>` component. | ||
|
||
```jsx | ||
// App.js | ||
import React from 'react'; | ||
import { FlameTheme, FlameGlobalStyles } from '@lightspeed/flame/Core'; | ||
import { ToasterProvider } from '@lightspeed/flame/Toaster'; | ||
|
||
const App = () => ( | ||
<FlameTheme> | ||
<FlameGlobalStyles /> | ||
<ToasterProvider> | ||
<div>{/* The rest of your app */}</div> | ||
<div>{/* The rest of your app */}</div> | ||
</ToasterProvider> | ||
</FlameTheme> | ||
); | ||
``` | ||
|
||
Once that is done, you may use the provided hooks to generate a toast notification. | ||
|
||
```jsx | ||
// MyComponent.js | ||
import * as React from 'react'; | ||
import { useToast } from '@lightspeed/flame/Toaster'; | ||
|
||
const MyComponent = () => { | ||
const { addToast } = useToasts(); | ||
return ( | ||
<button | ||
type="button" | ||
onClick={() => | ||
addToast('This is a toast', { | ||
appearance: 'success', // set to 'error' for a red error toast | ||
autoDismiss: false, // set to true to have a timer that automatically closes it | ||
}) | ||
} | ||
> | ||
Create toast | ||
</button> | ||
); | ||
}; | ||
``` | ||
|
||
## Components | ||
|
||
### `<ToasterProvider />` | ||
|
||
A pre-configured `ToastProvider` from the [react-toast-notifications](https://github.com/jossmac/react-toast-notifications) library. | ||
|
||
Please consult its documentation for a full list of all the props available. | ||
|
||
### `useToast()` | ||
|
||
An augmented hook of the original `useToast` found in [react-toast-notifications](https://github.com/jossmac/react-toast-notifications). | ||
|
||
The `useToast` hook has the following signature: | ||
|
||
```jsx | ||
const { | ||
addToast, | ||
addActionableToast, | ||
removeToast, | ||
removeAllToasts, | ||
updateToast, | ||
toastStack, | ||
} = useToasts(); | ||
``` | ||
|
||
The `addToast` method has three arguments: | ||
|
||
1. The first is the content of the toast, which can be any renderable `Node`. | ||
1. The second is the `Options` object, which can take any shape you like. `Options.appearance` is required when using the `DefaultToast`. When departing from the default shape, you must provide an alternative, compliant `Toast` component. | ||
1. The third is an optional callback, which is passed the added toast `ID`. | ||
|
||
The `addActionableToast` method has three arguments: | ||
|
||
1. The first is the `ActionableContent` object, which requires 3 properties to be filled. `ActionableContent.content` is the content of the toast, which can be any renderable `Node`. `ActionableContent.actionTitle` is the string that'll be shown for the action button. `ActionableContent.actionCallback` is the function that will be executed when the action button is clicked. | ||
1. The second is the `Options` object, which can take any shape you like. `Options.appearance` is required when using the `DefaultToast`. When departing from the default shape, you must provide an alternative, compliant `Toast` component. | ||
1. The third is an optional callback, which is passed the added toast `ID`. | ||
|
||
The `removeToast` method has two arguments: | ||
|
||
1. The first is the `ID` of the toast to remove. | ||
1. The second is an optional callback. | ||
|
||
The `removeAllToasts` method has no arguments. | ||
|
||
The `updateToast` method has three arguments: | ||
|
||
1. The first is the `ID` of the toast to update. | ||
1. The second is the `Options` object, which differs slightly from the add method because it accepts a `content` property. | ||
1. The third is an optional callback, which is passed the updated toast `ID`. | ||
|
||
The `toastStack` is an array of objects representing the current toasts, e.g. | ||
|
||
```jsx | ||
[ | ||
{ content: 'Something went wrong', id: 'generated-string', appearance: 'error' }, | ||
{ content: 'Item saved', id: 'generated-string', appearance: 'success' }, | ||
]; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import * as React from 'react'; | ||
import { customRender, screen, fireEvent, waitFor } from 'test-utils'; | ||
import { ToasterProvider, useToasts } from './index'; | ||
|
||
const TestAddToast: React.FC<{ appearance: 'success' | 'error'; autoDismiss: boolean }> = ({ | ||
appearance, | ||
autoDismiss, | ||
}) => { | ||
const { addToast } = useToasts(); | ||
return ( | ||
<button | ||
type="button" | ||
onClick={() => { | ||
addToast('this is a message', { | ||
appearance, | ||
autoDismiss, | ||
}); | ||
}} | ||
> | ||
create toast | ||
</button> | ||
); | ||
}; | ||
|
||
const TestAddActionableToast: React.FC<{ actionCallback: () => void }> = ({ actionCallback }) => { | ||
const { addActionableToast } = useToasts(); | ||
return ( | ||
<button | ||
type="button" | ||
onClick={() => { | ||
addActionableToast({ | ||
content: 'actionable toast', | ||
actionTitle: 'action-title', | ||
actionCallback, | ||
}); | ||
}} | ||
> | ||
create toast | ||
</button> | ||
); | ||
}; | ||
|
||
describe('Toaster', () => { | ||
describe('without auto-dismss set to false', () => { | ||
it('should render out a toaster', async () => { | ||
customRender( | ||
<ToasterProvider> | ||
<TestAddToast appearance="success" autoDismiss={false} /> | ||
</ToasterProvider>, | ||
); | ||
|
||
const toaster = await screen.queryByRole('alert'); | ||
expect(toaster).not.toBeInTheDocument(); | ||
|
||
fireEvent.click(screen.getByRole('button')); | ||
expect(screen.getByRole('alert')).toHaveTextContent(/this is a message/); | ||
fireEvent.click(screen.getByLabelText('Dismiss toast')); | ||
|
||
await waitFor(() => { | ||
expect(screen.queryByRole('alert')).not.toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('should render out a success toaster with a custom action', async () => { | ||
const spy = jest.fn(); | ||
customRender( | ||
<ToasterProvider> | ||
<TestAddActionableToast actionCallback={spy} /> | ||
</ToasterProvider>, | ||
); | ||
|
||
const toaster = await screen.queryByRole('alert'); | ||
expect(toaster).not.toBeInTheDocument(); | ||
|
||
fireEvent.click(screen.getByText('create toast')); | ||
expect(screen.getByRole('alert')).toHaveTextContent(/actionable toast/); | ||
|
||
fireEvent.click(screen.getByLabelText('action-title')); | ||
expect(spy).toHaveBeenCalledTimes(1); | ||
|
||
fireEvent.click(screen.getByLabelText('Dismiss toast')); | ||
|
||
await waitFor(() => { | ||
expect(screen.queryByRole('alert')).not.toBeInTheDocument(); | ||
}); | ||
}); | ||
}); | ||
|
||
// We have fake timers running here, isolate these test-cases in their own | ||
// describe block | ||
describe('with auto-dismiss set to true', () => { | ||
it('should render out a success toaster that disappears after a while', async () => { | ||
jest.useFakeTimers(); | ||
|
||
customRender( | ||
<ToasterProvider> | ||
<TestAddToast appearance="success" autoDismiss={true} /> | ||
</ToasterProvider>, | ||
); | ||
|
||
const toaster = await screen.queryByRole('alert'); | ||
expect(toaster).not.toBeInTheDocument(); | ||
|
||
fireEvent.click(screen.getByRole('button')); | ||
expect(screen.getByRole('alert')).toHaveTextContent(/this is a message/); | ||
|
||
jest.runAllTimers(); | ||
|
||
await waitFor(() => { | ||
expect(screen.queryByRole('alert')).not.toBeInTheDocument(); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need the same example twice here?