From ce86bcdc24fcd174ccbe068d923c8cdcefc47ad4 Mon Sep 17 00:00:00 2001 From: Cheton Wu <447801+cheton@users.noreply.github.com> Date: Mon, 1 Jan 2024 10:38:05 +0800 Subject: [PATCH] feat: update `ToastManager` to enable passing transition props and specifying toast data (#814) --- packages/react-docs/pages/_app.page.js | 25 +++- .../faq-control-spacing-between-toasts.js | 126 ++++++++++++++++++ .../components/toast-manager/index.page.mdx | 79 +++++++++-- .../toast-manager/useToastManager.js | 14 +- .../toast-manager/useToastManager.page.mdx | 13 +- .../pages/components/toast/appearances.js | 17 +-- .../pages/components/toast/icons.js | 17 +-- .../pages/components/toast/layout.js | 17 +-- .../getting-started/security/index.page.mdx | 6 +- .../getting-started/usage/index.page.mdx | 25 +++- .../pages/patterns/notification/app-toast.js | 38 +++--- packages/react/src/toast/ToastManager.js | 17 ++- 12 files changed, 286 insertions(+), 108 deletions(-) create mode 100644 packages/react-docs/pages/components/toast-manager/faq-control-spacing-between-toasts.js diff --git a/packages/react-docs/pages/_app.page.js b/packages/react-docs/pages/_app.page.js index 15642e17db..ee93c7d1fc 100644 --- a/packages/react-docs/pages/_app.page.js +++ b/packages/react-docs/pages/_app.page.js @@ -98,7 +98,30 @@ const App = (props) => { useCSSBaseline > - + &:first-of-type': { + mt: '4x', // the space to the top edge of the screen + }, + '[data-toast-placement^="top"] > &:not(:first-of-type)': { + mt: '2x', // the space between toasts + }, + '[data-toast-placement^="bottom"] > &:last-of-type': { + mb: '4x', // the space to the bottom edge of the screen + }, + '[data-toast-placement^="bottom"] > &:not(:last-of-type)': { + mb: '2x', // the space between toasts + }, + '[data-toast-placement$="left"] > &': { + ml: '4x', // the space to the left edge of the screen + }, + '[data-toast-placement$="right"] > &': { + mr: '4x', // the space to the right edge of the screen + }, + }, + }} + > diff --git a/packages/react-docs/pages/components/toast-manager/faq-control-spacing-between-toasts.js b/packages/react-docs/pages/components/toast-manager/faq-control-spacing-between-toasts.js new file mode 100644 index 0000000000..7c9ec556c4 --- /dev/null +++ b/packages/react-docs/pages/components/toast-manager/faq-control-spacing-between-toasts.js @@ -0,0 +1,126 @@ +import { Box, Button, Divider, Flex, TextLabel, Toast, ToastManager, useToastManager } from '@tonic-ui/react'; +import React, { useRef, useState } from 'react'; + +const FormGroup = (props) => ( + +); + +const ToastApp = () => { + const counterRef = useRef(0); + const toast = useToastManager(); + const placement = 'bottom-right'; + const isTop = placement.includes('top'); + + return ( + + ); +}; + +const App = () => { + const [edgeSpacing, setEdgeSpacing] = useState(16); + const [toastSpacing, setToastSpacing] = useState(16); + + return ( + <> + + + + The space to the edge of the screen + + + + { + const value = parseInt(event.target.value); + setEdgeSpacing(value); + }} + value={edgeSpacing} + /> + {edgeSpacing}px + + + + + + The space between toasts + + + + { + const value = parseInt(event.target.value); + setToastSpacing(value); + }} + value={toastSpacing} + /> + {toastSpacing}px + + + + &:first-of-type': { + mt: edgeSpacing, // the space to the top edge of the screen + }, + '[data-toast-placement^="top"] > &:not(:first-of-type)': { + mt: toastSpacing, // the space between toasts + }, + '[data-toast-placement^="bottom"] > &:last-of-type': { + mb: edgeSpacing, // the space to the bottom edge of the screen + }, + '[data-toast-placement^="bottom"] > &:not(:last-of-type)': { + mb: toastSpacing, // the space between toasts + }, + '[data-toast-placement$="left"] > &': { + ml: edgeSpacing, // the space to the left edge of the screen + }, + '[data-toast-placement$="right"] > &': { + mr: edgeSpacing, // the space to the right edge of the screen + }, + }, + }} + > + + + + ); +}; + +export default App; diff --git a/packages/react-docs/pages/components/toast-manager/index.page.mdx b/packages/react-docs/pages/components/toast-manager/index.page.mdx index c32aeabc15..59b06ba2b7 100644 --- a/packages/react-docs/pages/components/toast-manager/index.page.mdx +++ b/packages/react-docs/pages/components/toast-manager/index.page.mdx @@ -12,7 +12,31 @@ import { TonicProvider, ToastManager } from '@tonic-ui/react'; function App() { return ( - + &:first-of-type': { + mt: '4x', // the space to the top edge of the screen + }, + '[data-toast-placement^="top"] > &:not(:first-of-type)': { + mt: '2x', // the space between toasts + }, + '[data-toast-placement^="bottom"] > &:last-of-type': { + mb: '4x', // the space to the bottom edge of the screen + }, + '[data-toast-placement^="bottom"] > &:not(:last-of-type)': { + mb: '2x', // the space between toasts + }, + '[data-toast-placement$="left"] > &': { + ml: '4x', // the space to the left edge of the screen + }, + '[data-toast-placement$="right"] > &': { + mr: '4x', // the space to the right edge of the screen + }, + }, + }} + placement="bottom-right" + > {/* Your app goes here */} @@ -29,20 +53,8 @@ function MyComponent() { const toast = useToastManager(); const handleClickOpenToast = () => { const render = ({ onClose, placement }) => { - const styleProps = { - 'top-left': { pt: '2x', px: '4x' }, - 'top': { pt: '2x', px: '4x' }, - 'top-right': { pt: '2x', px: '4x' }, - 'bottom-left': { pb: '2x', px: '4x' }, - 'bottom': { pb: '2x', px: '4x' }, - 'bottom-right': { pb: '2x', px: '4x' }, - }[placement]; - return ( - + This is a toast notification @@ -82,12 +94,51 @@ toast.remove(id); See the [useToastManager](./toast-manager/useToastManager) Hook for detailed usage. +## Commonly Asked Questions + +### Control the spacing between toasts + +To control the spacing between toasts, make use of the `TransitionProps` prop in the `ToastManager`. Pass the `sx` prop to the transition component with your preferred spacing. + +```jsx disabled + &:first-of-type': { + mt: edgeSpacing, // the space to the top edge of the screen + }, + '[data-toast-placement^="top"] > &:not(:first-of-type)': { + mt: toastSpacing, // the space between toasts + }, + '[data-toast-placement^="bottom"] > &:last-of-type': { + mb: edgeSpacing, // the space to the bottom edge of the screen + }, + '[data-toast-placement^="bottom"] > &:not(:last-of-type)': { + mb: toastSpacing, // the space between toasts + }, + '[data-toast-placement$="left"] > &': { + ml: edgeSpacing, // the space to the left edge of the screen + }, + '[data-toast-placement$="right"] > &': { + mr: edgeSpacing, // the space to the right edge of the screen + }, + }, + }} +> + + +``` + +{render('./faq-control-spacing-between-toasts')} + ## Props ### ToastManager | Name | Type | Default | Description | | :--- | :--- | :------ | :---------- | +| TransitionComponent | ElementType | ToastTransition | The component used for the transition. | +| TransitionProps | object | | Props applied to the [Transition](http://reactcommunity.org/react-transition-group/transition#Transition-props) element. | | children | ReactNode \| `(context) => ReactNode` | | A function child can be used intead of a React element. This function is called with the context object. | | containerRef | RefObject | | A `ref` to the component where the toasts will be rendered. | | placement | string | 'bottom-right' | The default placement to place toasts. One of: 'top', 'top-right', 'top-left', 'bottom', 'bottom-left', 'bottom-right' | diff --git a/packages/react-docs/pages/components/toast-manager/useToastManager.js b/packages/react-docs/pages/components/toast-manager/useToastManager.js index 8e28ea3041..79a581be6f 100644 --- a/packages/react-docs/pages/components/toast-manager/useToastManager.js +++ b/packages/react-docs/pages/components/toast-manager/useToastManager.js @@ -11,20 +11,8 @@ const App = () => { const toast = useToastManager(); const handleClickOpenToast = useCallback(() => { const render = ({ onClose, placement }) => { - const styleProps = { - 'top-left': { pt: '2x', px: '4x' }, - 'top': { pt: '2x', px: '4x' }, - 'top-right': { pt: '2x', px: '4x' }, - 'bottom-left': { pb: '2x', px: '4x' }, - 'bottom': { pb: '2x', px: '4x' }, - 'bottom-right': { pb: '2x', px: '4x' }, - }[placement]; - return ( - + This is a toast notification diff --git a/packages/react-docs/pages/components/toast-manager/useToastManager.page.mdx b/packages/react-docs/pages/components/toast-manager/useToastManager.page.mdx index b7af809e7e..7c040940c3 100644 --- a/packages/react-docs/pages/components/toast-manager/useToastManager.page.mdx +++ b/packages/react-docs/pages/components/toast-manager/useToastManager.page.mdx @@ -31,6 +31,7 @@ Create a toast at the specified placement and return the toast id.
`message` *(Function|string)*: The toast message to render.
`[options={}]` *(Object)*: The options object.
+
`[options.data]` *(any)*: The user-defined data supplied to the toast.
`[options.duration=null]` *(number)*: The duration (in milliseconds) that the toast should remain on the screen. If set to null, toast will never dismiss.
`[options.id]` *(string)*: A unique ID of the toast.
`[options.placement]` *(string)*: The placement of the toast.
@@ -155,10 +156,10 @@ toast.setState({ 'top': [ { id: '2', - message: 'New toast message', - placement: 'top', duration: 3000, + message: 'New toast message', onClose: () => toast.close('2'), + placement: 'top', } ], 'top-left': [], @@ -178,10 +179,10 @@ toast.setState(prevState => ({ ...prevState['top'], { id: '2', - message: 'New toast message', - placement: 'top', duration: null, + message: 'New toast message', onClose: () => toast.close('2', 'top'), + placement: 'top', }, ], })); @@ -196,10 +197,10 @@ The toast state is a placement object, each placement contains an array of objec 'top': [ { id: '1', // A unique identifier that represents the toast message - message: ({ id, onClose, placement }) => , // The toast message to render - placement: 'top', // The placement of the toast duration: null, // The duration (in milliseconds) that the toast should remain on the screen. If set to null, toast will never dismiss. + message: ({ id, onClose, placement }) => , // The toast message to render onClose: () => toast.close(id, placement), // The function to close the toast + placement: 'top', // The placement of the toast }, ], 'top-left': [], diff --git a/packages/react-docs/pages/components/toast/appearances.js b/packages/react-docs/pages/components/toast/appearances.js index 0e2c83643f..80283b63bf 100644 --- a/packages/react-docs/pages/components/toast/appearances.js +++ b/packages/react-docs/pages/components/toast/appearances.js @@ -70,21 +70,10 @@ const App = () => { const toast = useToastManager(); const handleClickBy = (ToastNotification) => () => { toast(({ onClose, placement }) => { - const styleProps = { - 'top-left': { pt: '2x', px: '4x' }, - 'top': { pt: '2x', px: '4x' }, - 'top-right': { pt: '2x', px: '4x' }, - 'bottom-left': { pb: '2x', px: '4x' }, - 'bottom': { pb: '2x', px: '4x' }, - 'bottom-right': { pb: '2x', px: '4x' }, - }[placement]; - return ( - - - - - + + + ); }, { placement: 'bottom-right', diff --git a/packages/react-docs/pages/components/toast/icons.js b/packages/react-docs/pages/components/toast/icons.js index 8c1c978bfd..b20d520c81 100644 --- a/packages/react-docs/pages/components/toast/icons.js +++ b/packages/react-docs/pages/components/toast/icons.js @@ -74,21 +74,10 @@ const App = () => { const toast = useToastManager(); const handleClickBy = (ToastNotification) => () => { toast(({ onClose, placement }) => { - const styleProps = { - 'top-left': { pt: '2x', px: '4x' }, - 'top': { pt: '2x', px: '4x' }, - 'top-right': { pt: '2x', px: '4x' }, - 'bottom-left': { pb: '2x', px: '4x' }, - 'bottom': { pb: '2x', px: '4x' }, - 'bottom-right': { pb: '2x', px: '4x' }, - }[placement]; - return ( - - - - - + + + ); }, { placement: 'bottom-right', diff --git a/packages/react-docs/pages/components/toast/layout.js b/packages/react-docs/pages/components/toast/layout.js index 9ede64349e..aa9cd42b10 100644 --- a/packages/react-docs/pages/components/toast/layout.js +++ b/packages/react-docs/pages/components/toast/layout.js @@ -143,21 +143,10 @@ const App = () => { const toast = useToastManager(); const handleClickBy = (ToastNotification) => () => { toast(({ onClose, placement }) => { - const styleProps = { - 'top-left': { pt: '2x', px: '4x' }, - 'top': { pt: '2x', px: '4x' }, - 'top-right': { pt: '2x', px: '4x' }, - 'bottom-left': { pb: '2x', px: '4x' }, - 'bottom': { pb: '2x', px: '4x' }, - 'bottom-right': { pb: '2x', px: '4x' }, - }[placement]; - return ( - - - - - + + + ); }, { placement: 'bottom-right', diff --git a/packages/react-docs/pages/getting-started/security/index.page.mdx b/packages/react-docs/pages/getting-started/security/index.page.mdx index 12d9aa1728..e7f1c10e71 100644 --- a/packages/react-docs/pages/getting-started/security/index.page.mdx +++ b/packages/react-docs/pages/getting-started/security/index.page.mdx @@ -32,11 +32,7 @@ Wrap the `TonicProvider` component with the `EmotionCacheProvider` and provide t colorMode={colorMode} useCSSBaseline > - - - - - + ``` diff --git a/packages/react-docs/pages/getting-started/usage/index.page.mdx b/packages/react-docs/pages/getting-started/usage/index.page.mdx index 8560a4634c..00d3b896e1 100644 --- a/packages/react-docs/pages/getting-started/usage/index.page.mdx +++ b/packages/react-docs/pages/getting-started/usage/index.page.mdx @@ -60,7 +60,30 @@ function App(props) { useCSSBaseline={true} // If `true`, apply CSS reset and base styles > - + &:first-of-type': { + mt: '4x', // the space to the top edge of the screen + }, + '[data-toast-placement^="top"] > &:not(:first-of-type)': { + mt: '2x', // the space between toasts + }, + '[data-toast-placement^="bottom"] > &:last-of-type': { + mb: '4x', // the space to the bottom edge of the screen + }, + '[data-toast-placement^="bottom"] > &:not(:last-of-type)': { + mb: '2x', // the space between toasts + }, + '[data-toast-placement$="left"] > &': { + ml: '4x', // the space to the left edge of the screen + }, + '[data-toast-placement$="right"] > &': { + mr: '4x', // the space to the right edge of the screen + }, + }, + }} + > diff --git a/packages/react-docs/pages/patterns/notification/app-toast.js b/packages/react-docs/pages/patterns/notification/app-toast.js index 91034e2f76..57142f6b82 100644 --- a/packages/react-docs/pages/patterns/notification/app-toast.js +++ b/packages/react-docs/pages/patterns/notification/app-toast.js @@ -56,31 +56,29 @@ const App = () => { }; toast.notify(({ onClose, placement }) => { - const styleProps = { - 'top-left': { pt: '2x', px: '4x' }, - 'top': { pt: '2x', px: '4x' }, - 'top-right': { pt: '2x', px: '4x' }, - 'bottom-left': { pb: '2x', px: '4x' }, - 'bottom': { pb: '2x', px: '4x' }, - 'bottom-right': { pb: '2x', px: '4x' }, - }[placement]; - return ( - - - - {content} - - - + + + {content} + + ); }, options); + const isTop = placement.includes('top'); + // Limit the maximum number of toasts - toast.setState(prevState => ({ - ...prevState, - [placement]: prevState[placement].slice(-MAX_TOASTS), - })); + if (isTop) { + toast.setState(prevState => ({ + ...prevState, + [placement]: prevState[placement].slice(0, MAX_TOASTS), + })); + } else { + toast.setState(prevState => ({ + ...prevState, + [placement]: prevState[placement].slice(-MAX_TOASTS), + })); + } }; const handleClickCloseToasts = () => { diff --git a/packages/react/src/toast/ToastManager.js b/packages/react/src/toast/ToastManager.js index 5aa6456ae3..6eb7149850 100644 --- a/packages/react/src/toast/ToastManager.js +++ b/packages/react/src/toast/ToastManager.js @@ -42,7 +42,8 @@ const getToastPlacementByState = (state, id) => { const ToastManager = ({ container: DEPRECATED_container, // deprecated (remove in next major version) - + TransitionComponent = ToastTransition, + TransitionProps, children, containerRef, placement: placementProp = defaultPlacement, @@ -72,6 +73,7 @@ const ToastManager = ({ */ const createToast = useCallback((message, options) => { const id = options?.id ?? uniqueId(); + const data = options?.data; const duration = options?.duration; const placement = ensureString(options?.placement ?? placementProp); const onClose = () => close(id, placement); @@ -80,6 +82,9 @@ const ToastManager = ({ // A unique identifier that represents the toast message id, + // The user-defined data supplied to the toast + data, + // The toast message to render message, @@ -155,8 +160,7 @@ const ToastManager = ({ * Create a toast at the specified placement and return the id */ const notify = useCallback((message, options) => { - const { id, duration, placement } = options; - const toast = createToast(message, { id, duration, placement }); + const toast = createToast(message, options); if (!placements.includes(toast.placement)) { console.error(`[ToastManager] Error: Invalid toast placement "${toast.placement}". Please provide a valid placement from the following options: ${placements.join(', ')}.`); @@ -256,10 +260,10 @@ const ToastManager = ({ > {toasts.map((toast) => ( - - + ))}