-
Notifications
You must be signed in to change notification settings - Fork 154
docs(blade): Toast API decisions #1990
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
Changes from 6 commits
daf91a6
b5bc7ac
06cfd69
d1bda28
69e8204
f17adab
ee20396
0c441bb
d01baef
7a08645
f957687
ec2db65
74a03f1
80132a1
34b29e8
e24adc6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,251 @@ | ||||||
# Toast 🏷️ | ||||||
|
||||||
Toasts are used to provide feedback to the user after an important action has been performed. | ||||||
Toasts can also be used to provide feedback to the user when a system event occurs, such as when a file is saved. | ||||||
|
||||||
<img width="426" alt="image" src="./toast-thumbnail.png" /> | ||||||
|
||||||
## Design | ||||||
|
||||||
- [Toast - Figma Design](https://www.figma.com/file/jubmQL9Z8V7881ayUD95ps/Blade-DSL?type=design&node-id=7665-27414&mode=design&t=UNInCMmP1iFCu9je-0) | ||||||
|
||||||
## Features | ||||||
|
||||||
- Stackable | ||||||
- Auto dismissable / manual dismissable | ||||||
- Positioning | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
- `informational` or `promotional` types | ||||||
|
||||||
## Anatomy | ||||||
|
||||||
<img src="./toast-anatomy.png" width="100%" alt="Toast anatomy" /> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. show promotional type here as well There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. anatomy is the same. leading, button, close button & content. |
||||||
|
||||||
## Simple Usage | ||||||
|
||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
```jsx | ||||||
import { BladeProvider, ToastContainer, useToast } from "@razorpay/blade/components" | ||||||
|
||||||
const HomePage = () => { | ||||||
const { showToast } = useToast(); | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
return ( | ||||||
<Button | ||||||
onClick={() => { | ||||||
showToast({ | ||||||
type: 'informational', | ||||||
color: 'success', | ||||||
content: 'Payment Successful', | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
leading: <DollarIcon />, | ||||||
autoDismiss: true, | ||||||
onDismissButtonClick: () => { | ||||||
console.log('Toast dismissed'); | ||||||
}, | ||||||
action: { | ||||||
text: 'View', | ||||||
onClick: () => { | ||||||
console.log('Toast action clicked'); | ||||||
} | ||||||
} | ||||||
}); | ||||||
}} | ||||||
> | ||||||
Show Toast | ||||||
</Button> | ||||||
); | ||||||
}; | ||||||
|
||||||
const App = () => { | ||||||
return ( | ||||||
<BladeProvider> | ||||||
<ToastContainer position="bottom-left" /> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will there be multiple toast containers in an app? Can there not be a single ToastProvider that is part of BladeProvider and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There can only be 1.
That should be possible, we can keep the ToastContainer inside BladeProvider but the thing is we also may need to pass some props to ToastContainer, how should we handle that? |
||||||
<HomePage /> | ||||||
</BladeProvider> | ||||||
) | ||||||
} | ||||||
``` | ||||||
|
||||||
## Props | ||||||
|
||||||
### ToastContainer | ||||||
|
||||||
```ts | ||||||
type ToastContainer = { | ||||||
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
``` | ||||||
|
||||||
```ts | ||||||
type Toast = { | ||||||
/** | ||||||
* @default `informational` | ||||||
*/ | ||||||
type: 'informational' | 'promotional'; | ||||||
snitin315 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* @default `neutral` | ||||||
*/ | ||||||
color: 'neutral' | 'positive' | 'negative' | 'warning' | 'information' | ||||||
snitin315 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* If the type is `promotional`, the content will be `React.ReactNode` | ||||||
*/ | ||||||
content: string | React.ReactNode; | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* Can be used to render an icon | ||||||
*/ | ||||||
leading?: IconComponent; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since we don't have trailing counterpart and we know that icon will always be leading wouldn't it be better to name it just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1. Lets just call it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do we mean by asset? It can be an image or text or video? Any restrictions or is it a slot? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No restrictions, it's a slot. we should atleast keep the prop name generic to cater to future usecase. Same thing we did for BottomSheetHeader too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we can have icon and asset as a naming then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two props? Will cause impossible states. |
||||||
|
||||||
/** | ||||||
* If true, the toast will be dismissed after few seconds | ||||||
* | ||||||
* Duration for promotional toast is 6s | ||||||
* Duration for informational toast is 4s | ||||||
* | ||||||
* @default false | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the default shall be true IMO? because majority of the times you want this distracting pice to be autohidden? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 we should autodismiss by default There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, makes sense. Discussed with RK, for informational the autoDismiss we can keep true, and for promotional we can keep it false. |
||||||
*/ | ||||||
autoDismiss?: boolean; | ||||||
|
||||||
/** | ||||||
* Called when the toast is dismissed or duration runs out | ||||||
*/ | ||||||
onDismissButtonClick?: () => void; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
to avoid confusion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we need to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah maybe we need this separated out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps single onDismiss with type = 'auto' | 'onclick'? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
|
||||||
/** | ||||||
* Primary action of toast | ||||||
*/ | ||||||
action?: { | ||||||
text: string; | ||||||
onClick?: () => void; | ||||||
isLoading? boolean; | ||||||
} | ||||||
|
||||||
parichay28 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/** | ||||||
* Forwarded to react-hot-toast | ||||||
* | ||||||
* This can be used to programatically update toasts by providing an id | ||||||
*/ | ||||||
id?: string; | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
``` | ||||||
|
||||||
### useToast | ||||||
|
||||||
The useToast hook will return few modified methods from [react-hot-toast](https://react-hot-toast.com/docs/toast): | ||||||
|
||||||
```ts | ||||||
type useToastReturnType = { | ||||||
/** | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we shall also give an option to know the current id of the toast that is opened? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "current" can also be multiple toasts, we can give an option like: const { toasts } = useToasts(); but not sure about the usecase. |
||||||
* @returns id of the toast | ||||||
*/ | ||||||
showToast: (toast: Toast) => string; | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* id of the toast to be dismissed | ||||||
* | ||||||
* if id is not provided, all the toasts will be dismissed | ||||||
*/ | ||||||
dismissToast: (id?: string) => void; | ||||||
} | ||||||
``` | ||||||
|
||||||
## Examples | ||||||
|
||||||
#### Removing a toast | ||||||
|
||||||
react-hot-toast provides this functionality, for more info see [react-hot-toast docs](https://react-hot-toast.com/docs/toast#dismiss-toast-programmatically) | ||||||
|
||||||
```jsx | ||||||
import { BladeProvider, ToastContainer, useToast } from "@razorpay/blade/components" | ||||||
|
||||||
const Example = () => { | ||||||
const toastId = React.useRef(null); | ||||||
const { showToast, dismissToast } = useToast(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. toast will have a context at the app level right? can't see that in the example? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe, I'm not sure right now actually, so react-hot-toast works in a way that it doesn't need ReactContext. |
||||||
|
||||||
return ( | ||||||
<Box> | ||||||
<Button | ||||||
onClick={() => { | ||||||
toastId.current = showToast({ | ||||||
color: 'success', | ||||||
content: 'Payment Successful', | ||||||
}); | ||||||
}} | ||||||
> | ||||||
Show Toast | ||||||
</Button> | ||||||
<Button onClick={() => dismissToast(toastId.current)}>Dismiss Toast</Button> | ||||||
</Box> | ||||||
); | ||||||
}; | ||||||
``` | ||||||
|
||||||
### Promotional Toast | ||||||
|
||||||
```jsx | ||||||
import { BladeProvider, ToastContainer, useToast } from "@razorpay/blade/components" | ||||||
|
||||||
const Example = () => { | ||||||
const { showToast } = useToast(); | ||||||
|
||||||
return ( | ||||||
<Button | ||||||
onClick={() => { | ||||||
showToast({ | ||||||
type: 'promotional', | ||||||
content: ( | ||||||
<Box> | ||||||
<Heading>Payment Successful</Heading> | ||||||
<Text>Amount: ₹100</Text> | ||||||
</Box> | ||||||
kamleshchandnani marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
), | ||||||
leading: <DollarIcon />, | ||||||
action: { | ||||||
text: 'Okay' | ||||||
} | ||||||
}); | ||||||
}} | ||||||
> | ||||||
Do payment | ||||||
</Button> | ||||||
); | ||||||
}; | ||||||
``` | ||||||
|
||||||
<img src="./example-promotional-toast.png" width="380" alt="Promotional toast example" /> | ||||||
|
||||||
|
||||||
## Motion | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
- TBD | ||||||
|
||||||
## Accessibility | ||||||
|
||||||
- Toast components will follow the WAI-ARIA guidelines for [alert](https://www.w3.org/WAI/ARIA/apg/patterns/alert/examples/alert/). For error toast we will use the `alert` role and for informational toast we will use the `status` role. | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
|
||||||
## References | ||||||
|
||||||
- https://react-hot-toast.com/docs | ||||||
- https://chakra-ui.com/docs/components/toast/usage | ||||||
|
||||||
## Open Questions | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
1. What should be the default duration for auto dismissable toasts? | ||||||
|
||||||
2. Should we call it `onDismissButtonClick` or `onDismiss`? | ||||||
- Should the dismiss handler be called even when the toast is auto dismissed? | ||||||
|
||||||
3. In the `useToast` hook should we call the returned functions `showToast`/`dismissToast` or `show`/`dismiss`? | ||||||
|
||||||
If we call them `show` & `dismiss` consumer can do this and might look more cleaner: | ||||||
anuraghazra marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
```jsx | ||||||
const App = () => { | ||||||
const toast = useToast(); | ||||||
|
||||||
toast.show(); // <-- they will also get auto complete when writing `toast.` | ||||||
toast.dismiss(); | ||||||
} | ||||||
``` | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.