-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add toaster component WIP * Update jest and testing-library. Tests for toaster * Update toaster readme * remove dangling TODO from toaster * Add better responsiveness for Toaster * Restrict Toaster types. Bypass click events for ToasterContainer * Update changelog. Add notice that we are using an external lib * Remove unused typing file * Spread rest on Toaster component to auto-pause timer * Update toaster behaviour to match specs * Supress warnings for logs. Fix toaster example setup * less jarring height transition * fixing text alignment * Set minHeight for container at 60px * Test out Percy with animations Co-authored-by: maartenafink <[email protected]> Co-authored-by: Guillaume Lambert <[email protected]>
- Loading branch information
1 parent
97f26a2
commit 25fff74
Showing
18 changed files
with
2,328 additions
and
592 deletions.
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,109 @@ | ||
# 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> | ||
</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.