-
Notifications
You must be signed in to change notification settings - Fork 151
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
feat(blade): add toast component #2000
Changes from all commits
7051454
dfa7939
2323145
39fb2bb
efa548f
d1672e4
7eedb4e
101094b
5cbbc61
3319520
5c23c83
3331954
5185747
469e8f5
a0dfac3
0120aed
ffe9e69
45f535f
a51eada
8c77908
4354e65
13cba57
cc4d1e3
6ff58ad
9b56142
feefb6e
53f308a
5ba4e5c
87bff48
c819313
1b62f74
b380ff5
f7e9b65
5a51c09
78340b8
d83cf0f
8c1b236
9a472f6
6cb1941
19953d7
5980893
62e45aa
ff8de33
e90ac1c
0a90bd6
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,5 @@ | ||
--- | ||
"@razorpay/blade": minor | ||
--- | ||
|
||
feat(blade): add toast component |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -138,6 +138,7 @@ | |
"use-presence": "1.1.0", | ||
"@use-gesture/react": "10.2.24", | ||
"@floating-ui/react": "0.25.4", | ||
"react-hot-toast": "2.4.1", | ||
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. Would RN consumers need to install this too even though they won't be using it at all? 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. Dependencies will anyways be installed regardless, since most of your deps are inside "dependencies" |
||
"@emotion/react": "11.11.1", | ||
"@table-library/react-table-library": "4.1.7", | ||
"tinycolor2": "1.6.0" | ||
|
@@ -284,6 +285,7 @@ | |
"react-native-pager-view": "^6.2.1", | ||
"react-native-svg": "^12.3.0", | ||
"react-native-gesture-handler": "^2.9.0", | ||
"react-hot-toast": "2.4.1", | ||
"@gorhom/bottom-sheet": "^4.4.6", | ||
"@gorhom/portal": "^1.0.14", | ||
"@razorpay/i18nify-js": "^1.4.0" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type { ToastProps } from './types'; | ||
import { throwBladeError } from '~utils/logger'; | ||
|
||
const Toast = ( | ||
_props: ToastProps & { | ||
isVisible?: boolean; | ||
}, | ||
): React.ReactElement => { | ||
throwBladeError({ | ||
message: 'Toast is not yet implemented for native', | ||
moduleName: 'Toast', | ||
}); | ||
|
||
return <></>; | ||
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. Add a basic Text here saying that toast is not available? So its easily visible to people? 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 are throwing an error on ReactNative. So it will give users a fullscreen error anyways. |
||
}; | ||
|
||
export { Toast }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ | ||
/* eslint-disable jsx-a11y/label-has-associated-control */ | ||
/* eslint-disable @typescript-eslint/explicit-function-return-type */ | ||
import { Title } from '@storybook/addon-docs'; | ||
import type { StoryFn, Meta } from '@storybook/react'; | ||
import React from 'react'; | ||
import { useToast } from './useToast'; | ||
import { Toast } from './Toast'; | ||
import type { ToastProps } from './'; | ||
import { ToastContainer } from './'; | ||
import StoryPageWrapper from '~utils/storybook/StoryPageWrapper'; | ||
import { Sandbox } from '~utils/storybook/Sandbox'; | ||
import { Box } from '~components/Box'; | ||
import { Button } from '~components/Button'; | ||
import { Heading, Text } from '~components/Typography'; | ||
|
||
const Page = (): React.ReactElement => { | ||
return ( | ||
<StoryPageWrapper | ||
componentName="Toast" | ||
componentDescription="Toast is a feedback element to display temporary short messages in the interface" | ||
figmaURL="https://www.figma.com/file/jubmQL9Z8V7881ayUD95ps/Blade-DSL?type=design&node-id=75839-1125191&mode=design&t=SLxhqgKm27oCjSYV-4" | ||
> | ||
<Title>Usage</Title> | ||
<Sandbox> | ||
{` | ||
import { ToastContainer, useToast } from '@razorpay/blade/components'; | ||
|
||
function App(): React.ReactElement { | ||
const toast = useToast(); | ||
|
||
// Integrating Blade Toast in your App | ||
// 1. Render the ToastContainer component at the root of your app | ||
// 2. Utilize the methods exposed via useToast hook to show/dismiss toasts | ||
return ( | ||
<Box> | ||
<ToastContainer /> | ||
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 will always be 1 ToastContainer, right? 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. Yes, this will be on the root level of the app. 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. Yes, The user will put 1 ToastContainer on root of their app. |
||
<Button | ||
onClick={() => { | ||
toast.show({ content: 'Payment successful', color: 'positive' }) | ||
}} | ||
> | ||
Show Toast | ||
</Button> | ||
</Box> | ||
); | ||
} | ||
|
||
export default App; | ||
`} | ||
</Sandbox> | ||
</StoryPageWrapper> | ||
); | ||
}; | ||
|
||
export 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. 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 basic story is a bit misleading. Changing props on it doesn't change toast visual instead it changes properties for the next toast. Not sure if we should keep it this way or not. Regardless, we need to communicate it better that whatever props they change on storybook would be applied to the next toast and not the existing one 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. Rather lets add some gap below the button so that the toasts collapse when they reach a certain stack height and button is never hidden 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. Sure, added a note in the top. |
||
title: 'Components/Toast', | ||
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. The content is fully configurable via the storybook controls nah? Can't really change it dynamically based on info/promo toasts. |
||
component: Toast, | ||
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. Yeah actually because we are using I've changed it to use |
||
tags: ['autodocs'], | ||
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.
Yes exactly, defaults which can be overridden depending on product usecase. |
||
argTypes: { | ||
isVisible: { | ||
table: { | ||
disable: true, | ||
}, | ||
}, | ||
id: { | ||
table: { | ||
disable: true, | ||
}, | ||
}, | ||
}, | ||
parameters: { | ||
docs: { | ||
page: Page, | ||
}, | ||
}, | ||
} as Meta<ToastProps>; | ||
|
||
const texts = { | ||
negative: 'Unable to fetch merchant details', | ||
positive: 'Customer details failed successfully', | ||
notice: 'Your KYC is pending', | ||
information: 'Your transaction will be settled in 3 business days', | ||
neutral: 'Your transaction will be settled in 3 business days', | ||
} as const; | ||
|
||
const BasicToastTemplate: StoryFn<ToastProps> = (args) => { | ||
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. Lets add a story where we demo the toast stacking behaviour and how it behaves with a promotional toast + informational toast. This would also be a place where we can explain that there can be only 1 promotional toast at a time, etc. 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.
This you can trigger via the "Toasts Variants" story. I can add some message at top to explain the process. |
||
const toast = useToast(); | ||
|
||
if (args.type === 'promotional') { | ||
args.content = <Text size="small">{args.content}</Text>; | ||
} | ||
|
||
return ( | ||
<Box height="80vh"> | ||
<Text marginBottom="spacing.3" color="surface.text.gray.subtle"> | ||
After changing storybook controls, press the show "toast button" to see changes | ||
</Text> | ||
<Button | ||
onClick={() => { | ||
toast.show(args); | ||
}} | ||
> | ||
Show Toast | ||
</Button> | ||
<ToastContainer /> | ||
</Box> | ||
); | ||
}; | ||
|
||
BasicToastTemplate.storyName = 'Basic'; | ||
export const Basic = BasicToastTemplate.bind({}); | ||
Basic.args = { | ||
color: 'neutral', | ||
type: 'informational', | ||
autoDismiss: false, | ||
content: 'Payment successful', | ||
action: { | ||
text: 'Okay', | ||
onClick: ({ toastId }) => console.log(toastId), | ||
}, | ||
}; | ||
|
||
const ToastVariantsTemplate: StoryFn<ToastProps> = () => { | ||
const toast = useToast(); | ||
const hasPromoToast = toast.toasts.some((t) => t.type === 'promotional'); | ||
|
||
const showInformationalToast = ({ color }: { color: ToastProps['color'] }) => { | ||
toast.show({ | ||
content: texts[color!], | ||
color, | ||
action: { | ||
text: 'Okay', | ||
onClick: ({ toastId }) => toast.dismiss(toastId), | ||
}, | ||
onDismissButtonClick: ({ toastId }) => console.log(`${toastId} Dismissed!`), | ||
}); | ||
}; | ||
|
||
const showPromotionalToast = () => { | ||
toast.show({ | ||
type: 'promotional', | ||
content: ( | ||
<Box display="flex" gap="spacing.3" flexDirection="column"> | ||
<Heading>Introducing TurboUPI</Heading> | ||
<img | ||
loading="lazy" | ||
width="100%" | ||
height="100px" | ||
alt="Promotional Toast" | ||
style={{ objectFit: 'cover', borderRadius: '8px' }} | ||
src="https://d6xcmfyh68wv8.cloudfront.net/blog-content/uploads/2023/05/Features-blog.png" | ||
/> | ||
<Text weight="semibold">Lightning-fast payments with the new Razorpay Turbo UPI</Text> | ||
<Text size="xsmall"> | ||
Turbo UPI allows end-users to complete their payment in-app, with no redirections or | ||
dependence on third-party UPI apps. With Turbo UPI, payments will be 5x faster with a | ||
significantly-improved success rate of 10%! | ||
</Text> | ||
</Box> | ||
), | ||
action: { | ||
text: 'Try TurboUPI', | ||
onClick: ({ toastId }) => toast.dismiss(toastId), | ||
}, | ||
onDismissButtonClick: ({ toastId }) => console.log(`${toastId} Dismissed!`), | ||
}); | ||
}; | ||
|
||
return ( | ||
<Box height="80vh"> | ||
<Text>Show Informational Toasts:</Text> | ||
<Box display="flex" gap="spacing.3" marginY="spacing.5"> | ||
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'positive' })}> | ||
Positive | ||
</Button> | ||
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'negative' })}> | ||
Negative | ||
</Button> | ||
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'notice' })}> | ||
Notice | ||
</Button> | ||
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'information' })}> | ||
Information | ||
</Button> | ||
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'neutral' })}> | ||
Neutral | ||
</Button> | ||
</Box> | ||
<Text>Show Promotional Toasts:</Text> | ||
<Text size="small" color="surface.text.gray.muted"> | ||
Note: There can only be 1 promotional toast at a time | ||
</Text> | ||
<Box display="flex" gap="spacing.3" marginY="spacing.5"> | ||
<Button | ||
variant="tertiary" | ||
onClick={() => showPromotionalToast()} | ||
isDisabled={hasPromoToast} | ||
> | ||
Promotional | ||
</Button> | ||
</Box> | ||
<ToastContainer /> | ||
</Box> | ||
); | ||
}; | ||
|
||
export const ToastVariants = ToastVariantsTemplate.bind({}); | ||
ToastVariants.storyName = 'Toast Variants'; |
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.
Update documentation to mention that users need to add this too?
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.
Users won't need to install this separately, since it's in
dependencies
If users are using react-hot-toast separately in their own codebase then they need to install it.