-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from osstotalsoft/feature/Toast
rewrite toast
- Loading branch information
Showing
18 changed files
with
1,632 additions
and
519 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ | |
"cSpell.words": [ | ||
"autodocs", | ||
"storybuild", | ||
"Toastify", | ||
"uuidv" | ||
], | ||
"licenser.license": "Custom", | ||
|
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,112 @@ | ||
import { fireEvent, renderHook, screen, waitFor } from '@testing-library/react' | ||
import React from 'react' | ||
import useToast from './useToast' | ||
import { render } from 'testingUtils' | ||
import Button from 'components/buttons/Button' | ||
import usePromiseToast from './usePromiseToast' | ||
|
||
describe('Toast', () => { | ||
it.each([ | ||
{ | ||
variant: 'success', | ||
transitionType: 'Slide', | ||
position: 'top-center', | ||
expectedVariant: 'Toastify__toast--success', | ||
expectedTransition: 'Toastify__slide-enter--top-center ', | ||
expectedPosition: 'Toastify__toast-container--top-center' | ||
}, | ||
{ | ||
variant: 'info', | ||
transitionType: 'Zoom', | ||
position: 'top-right', | ||
expectedVariant: 'Toastify__toast--info', | ||
expectedTransition: 'Toastify__zoom-enter ', | ||
expectedPosition: 'Toastify__toast-container--top-right' | ||
}, | ||
{ | ||
variant: 'warning', | ||
transitionType: 'Bounce', | ||
position: 'bottom-right', | ||
expectedVariant: 'Toastify__toast--warning', | ||
expectedTransition: 'Toastify__bounce-enter--bottom-right', | ||
expectedPosition: 'Toastify__toast-container--bottom-right' | ||
}, | ||
{ | ||
variant: 'error', | ||
transitionType: 'Flip', | ||
position: 'bottom-center', | ||
expectedVariant: 'Toastify__toast--error', | ||
expectedTransition: 'Toastify__flip-enter', | ||
expectedPosition: 'Toastify__toast-container--bottom-center' | ||
}, | ||
{ | ||
variant: null, | ||
transitionType: 'Slide', | ||
position: 'top-center', | ||
expectedVariant: 'Toastify__toast--default', | ||
expectedTransition: 'Toastify__slide-enter--top-center ', | ||
expectedPosition: 'Toastify__toast-container--top-center' | ||
} | ||
])( | ||
'Should render a toast of type: $variant with the correct specifications', | ||
async ({ variant, transitionType, position, expectedVariant, expectedTransition, expectedPosition }) => { | ||
const { result } = renderHook(() => useToast()) | ||
|
||
render( | ||
<Button | ||
onClick={() => result.current('This is a message!', variant as any, transitionType as any, position as any)} | ||
/> | ||
) | ||
fireEvent.click(screen.getByRole('button')) | ||
|
||
await waitFor(() => expect(screen.getByRole('alert').parentNode).toHaveClass(expectedVariant, expectedTransition)) | ||
await waitFor(() => expect(screen.getByRole('alert').parentNode.parentNode).toHaveClass(expectedPosition)) | ||
} | ||
) | ||
|
||
it('Should close the toast when click-ing the x button', async () => { | ||
const { result } = renderHook(() => useToast()) | ||
|
||
render(<Button onClick={() => result.current('This is a success message!', 'success')} />) | ||
|
||
fireEvent.click(screen.getByRole('button')) | ||
await waitFor(() => expect(screen.getByRole('alert')).toBeInTheDocument()) | ||
|
||
fireEvent.click(screen.getByLabelText('close')) | ||
await waitFor(() => expect(screen.queryByRole('alert')).not.toBeInTheDocument()) | ||
}) | ||
|
||
it('Error toast should not close when click-ing on its container', async () => { | ||
const { result } = renderHook(() => useToast()) | ||
|
||
render(<Button onClick={() => result.current('This is an error message!', 'error')} />) | ||
|
||
fireEvent.click(screen.getByRole('button')) | ||
await waitFor(() => expect(screen.getByRole('alert')).toBeInTheDocument()) | ||
|
||
fireEvent.click(screen.getByRole('alert')) | ||
await waitFor(() => expect(screen.queryByRole('alert')).toBeInTheDocument()) | ||
}) | ||
}) | ||
|
||
describe('Promise toast', () => { | ||
it('Promise toast should behave as expected', async () => { | ||
const resolveAfter3Sec = () => new Promise((_resolve, reject) => setTimeout(reject, 3000)) | ||
const { result } = renderHook(() => usePromiseToast()) | ||
|
||
render( | ||
<Button | ||
onClick={() => | ||
result.current(resolveAfter3Sec(), 'Promise is pending', 'Promise resolved 👌', 'Promise rejected 🤯') | ||
} | ||
/> | ||
) | ||
fireEvent.click(screen.getByRole('button')) | ||
|
||
await waitFor(() => expect(screen.getByRole('alert')).toBeInTheDocument()) | ||
await waitFor(() => expect(screen.getByRole('alert').parentNode).toHaveClass('Toastify__toast--default')) | ||
await waitFor(() => expect(screen.getByRole('alert').parentNode).toHaveClass('Toastify__toast--error'), { | ||
timeout: 4000 | ||
}) | ||
}) | ||
}) |
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,69 @@ | ||
import React from 'react' | ||
import PropTypes from 'prop-types' | ||
import { ToastContainer as ReactToastify, toast } from 'react-toastify' | ||
import Container, { classes } from './ToastStyles' | ||
import 'react-toastify/dist/ReactToastify.css' | ||
import { ToastContainerProps } from './types' | ||
|
||
/** | ||
* Toast provide brief notifications. | ||
*/ | ||
|
||
const ToastContainer: React.FC<ToastContainerProps> = ({ | ||
position = toast.POSITION.TOP_CENTER, | ||
autoClose = 3000, | ||
newestOnTop = true, | ||
transitionType = 'Slide', | ||
limit = 5, | ||
...rest | ||
}) => { | ||
return ( | ||
<Container> | ||
<ReactToastify | ||
className={classes.toastWrapper} | ||
position={position} | ||
autoClose={autoClose} | ||
newestOnTop={newestOnTop} | ||
transition={transitionType as any} | ||
theme="colored" | ||
limit={limit} | ||
{...rest} | ||
/> | ||
</Container> | ||
) | ||
} | ||
|
||
ToastContainer.propTypes = { | ||
/** | ||
* Set the delay in ms to close the toast automatically. | ||
* Use `false` to prevent the toast from closing. | ||
* @default 3000 | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
autoClose: PropTypes.oneOf([PropTypes.number, false]), | ||
/** | ||
* Limit the number of toast displayed at the same time | ||
* @default 5 | ||
*/ | ||
limit: PropTypes.number, | ||
/** | ||
* Set the position to use. | ||
* @default 'top-center' | ||
*/ | ||
position: PropTypes.oneOf(['top-right', 'top-center', 'top-left', 'bottom-right', 'bottom-center', 'bottom-left']), | ||
/** | ||
* The appearance effect. | ||
* @default Slide | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
transitionType: PropTypes.oneOf(['Slide', 'Bounce', 'Zoom', 'Flip']), | ||
/** | ||
* Whether or not to display the newest toast on top. | ||
* @default true | ||
*/ | ||
newestOnTop: PropTypes.bool | ||
} | ||
|
||
export default ToastContainer |
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,57 @@ | ||
import { styled } from '@mui/material/styles' | ||
|
||
const PREFIX = 'StyledToast' | ||
|
||
export const classes = { | ||
default: `${PREFIX}-default`, | ||
success: `${PREFIX}-success`, | ||
info: `${PREFIX}-info`, | ||
error: `${PREFIX}-error`, | ||
warning: `${PREFIX}-warning`, | ||
toastWrapper: `${PREFIX}-toastWrapper` | ||
} | ||
|
||
const Container = styled('div')(({ theme }) => ({ | ||
[`& .${classes.default}`]: { borderRadius: '6px', padding: '6px 20px' }, | ||
[`& .${classes.success}`]: { | ||
'--toastify-color-success': theme.palette.success.main, | ||
'--toastify-text-color-success': theme.palette.success.contrastText, | ||
'--toastify-icon-color-success': theme.palette.success.main, | ||
'--toastify-color-progress-success': theme.palette.success.main | ||
}, | ||
[`& .${classes.info}`]: { | ||
'--toastify-color-info': theme.palette.info.main, | ||
'--toastify-text-color-info': theme.palette.info.contrastText, | ||
'--toastify-icon-color-info': theme.palette.info.main, | ||
'--toastify-color-progress-info': theme.palette.info.main | ||
}, | ||
[`& .${classes.error}`]: { | ||
'--toastify-color-error': theme.palette.error.main, | ||
'--toastify-text-color-error': theme.palette.error.contrastText, | ||
'--toastify-icon-color-error': theme.palette.error.main, | ||
'--toastify-color-progress-error': theme.palette.error.main | ||
}, | ||
[`& .${classes.warning}`]: { | ||
'--toastify-color-warning': theme.palette.warning.main, | ||
'--toastify-text-color-warning': theme.palette.warning.contrastText, | ||
'--toastify-icon-color-warning': theme.palette.warning.main, | ||
'--toastify-color-progress-warning': theme.palette.warning.main | ||
}, | ||
[`& .${classes.toastWrapper}`]: { | ||
borderRadius: '6px', | ||
width: '350px', | ||
overflowWrap: 'anywhere' | ||
}, | ||
['.Toastify__close-button']: { | ||
background: 'transparent', | ||
outline: 'none', | ||
border: 'none', | ||
padding: 0, | ||
cursor: 'pointer', | ||
opacity: 1, | ||
transition: '0.3s ease', | ||
alignSelf: 'auto' | ||
} | ||
})) | ||
|
||
export default Container |
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,3 @@ | ||
export { default as ToastContainer } from './ToastContainer' | ||
export { default as useToast } from './useToast' | ||
export { default as usePromiseToast } from './usePromiseToast' |
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,11 @@ | ||
// Copyright (c) TotalSoft. | ||
// This source code is licensed under the MIT license. | ||
import { ToastContainerProps as ReactToastifyProps } from 'react-toastify' | ||
|
||
export interface ToastContainerProps extends Omit<ReactToastifyProps, 'transition'> { | ||
/** | ||
* The appearance effect. | ||
* @default Slide | ||
*/ | ||
transitionType?: 'Slide' | 'Bounce' | 'Zoom' | 'Flip' | ||
} |
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,27 @@ | ||
import { useCallback } from 'react' | ||
import { toast } from 'react-toastify' | ||
import { classes } from './ToastStyles' | ||
|
||
const usePromiseToast = () => { | ||
return useCallback( | ||
/** | ||
* | ||
* @param {Promise} promise The promise function | ||
* @param {(String|Object)} pending The message to be shown while promise in pending or the entire object with all the configurations | ||
* @param {(String|Object)} success The message to be shown when promise completed successfully or the entire object with all the configurations | ||
* @param {(String|Object)} error The message to be shown when promise was rejected or the entire object with all the configurations | ||
*/ | ||
(promise: Promise<unknown>, pending: string | object, success: string | object, error: string | object) => { | ||
toast.promise( | ||
promise, | ||
{ pending, success, error }, | ||
{ | ||
className: `${classes.success} ${classes.default} ${classes.info} ${classes.error} ${classes.error} ${classes.default} ` | ||
} | ||
) | ||
}, | ||
[] | ||
) | ||
} | ||
|
||
export default usePromiseToast |
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,58 @@ | ||
import { useCallback } from 'react' | ||
import { toast, Slide, Bounce, Flip, Zoom } from 'react-toastify' | ||
import { classes } from './ToastStyles' | ||
import cx from 'classnames' | ||
import { cond, equals, always, T } from 'ramda' | ||
|
||
const useToast = () => { | ||
return useCallback( | ||
/** | ||
* | ||
* @param {String} message The text to be displayed | ||
* @param {('success'|'info'|'warning'|'error')} variant The type of the toast | ||
* @param {('Slide' | 'Bounce' | 'Zoom' | 'Flip')} transitionType The appearance effect | ||
* @param {('top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left')} position Where to be displayed on the page | ||
* @param {(Number| false)} autoClose Delay in ms to close the toast | ||
*/ | ||
( | ||
message: string, | ||
variant?: 'success' | 'info' | 'warning' | 'error', | ||
transitionType?: 'Slide' | 'Bounce' | 'Zoom' | 'Flip', | ||
position?: 'top-right' | 'top-center' | 'top-left' | 'bottom-right' | 'bottom-center' | 'bottom-left', | ||
autoClose: any = variant !== 'error' | ||
) => { | ||
const toastClasses = cx({ | ||
[classes[variant]]: variant, | ||
[classes['default']]: true | ||
}) | ||
const getTransitionType = cond([ | ||
[equals('Slide'), always(Slide)], | ||
[equals('Bounce'), always(Bounce)], | ||
[equals('Flip'), always(Flip)], | ||
[equals('Zoom'), always(Zoom)], | ||
[T, always(Slide)] | ||
]) | ||
const options = { autoClose, transition: getTransitionType(transitionType), position, className: toastClasses } | ||
switch (variant) { | ||
case 'error': | ||
toast.error(message, { ...options, autoClose: false, closeOnClick: false, draggable: false }) | ||
break | ||
case 'info': | ||
toast.info(message, options) | ||
break | ||
case 'success': | ||
toast.success(message, options) | ||
break | ||
case 'warning': | ||
toast.warn(message, options) | ||
break | ||
default: | ||
toast(message, options) | ||
break | ||
} | ||
}, | ||
[] | ||
) | ||
} | ||
|
||
export default useToast |
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
Oops, something went wrong.