Skip to content

Commit 497799d

Browse files
authored
feat(blade): add Toast component (#2000)
1 parent 8260c2a commit 497799d

15 files changed

+1161
-3
lines changed

.changeset/lovely-eggs-juggle.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@razorpay/blade": minor
3+
---
4+
5+
feat(blade): add toast component

packages/blade/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
"use-presence": "1.1.0",
139139
"@use-gesture/react": "10.2.24",
140140
"@floating-ui/react": "0.25.4",
141+
"react-hot-toast": "2.4.1",
141142
"@emotion/react": "11.11.1",
142143
"@table-library/react-table-library": "4.1.7",
143144
"tinycolor2": "1.6.0"
@@ -284,6 +285,7 @@
284285
"react-native-pager-view": "^6.2.1",
285286
"react-native-svg": "^12.3.0",
286287
"react-native-gesture-handler": "^2.9.0",
288+
"react-hot-toast": "2.4.1",
287289
"@gorhom/bottom-sheet": "^4.4.6",
288290
"@gorhom/portal": "^1.0.14",
289291
"@razorpay/i18nify-js": "^1.4.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { ToastProps } from './types';
2+
import { throwBladeError } from '~utils/logger';
3+
4+
const Toast = (
5+
_props: ToastProps & {
6+
isVisible?: boolean;
7+
},
8+
): React.ReactElement => {
9+
throwBladeError({
10+
message: 'Toast is not yet implemented for native',
11+
moduleName: 'Toast',
12+
});
13+
14+
return <></>;
15+
};
16+
17+
export { Toast };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
2+
/* eslint-disable jsx-a11y/label-has-associated-control */
3+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
4+
import { Title } from '@storybook/addon-docs';
5+
import type { StoryFn, Meta } from '@storybook/react';
6+
import React from 'react';
7+
import { useToast } from './useToast';
8+
import { Toast } from './Toast';
9+
import type { ToastProps } from './';
10+
import { ToastContainer } from './';
11+
import StoryPageWrapper from '~utils/storybook/StoryPageWrapper';
12+
import { Sandbox } from '~utils/storybook/Sandbox';
13+
import { Box } from '~components/Box';
14+
import { Button } from '~components/Button';
15+
import { Heading, Text } from '~components/Typography';
16+
17+
const Page = (): React.ReactElement => {
18+
return (
19+
<StoryPageWrapper
20+
componentName="Toast"
21+
componentDescription="Toast is a feedback element to display temporary short messages in the interface"
22+
figmaURL="https://www.figma.com/file/jubmQL9Z8V7881ayUD95ps/Blade-DSL?type=design&node-id=75839-1125191&mode=design&t=SLxhqgKm27oCjSYV-4"
23+
>
24+
<Title>Usage</Title>
25+
<Sandbox>
26+
{`
27+
import { ToastContainer, useToast } from '@razorpay/blade/components';
28+
29+
function App(): React.ReactElement {
30+
const toast = useToast();
31+
32+
// Integrating Blade Toast in your App
33+
// 1. Render the ToastContainer component at the root of your app
34+
// 2. Utilize the methods exposed via useToast hook to show/dismiss toasts
35+
return (
36+
<Box>
37+
<ToastContainer />
38+
<Button
39+
onClick={() => {
40+
toast.show({ content: 'Payment successful', color: 'positive' })
41+
}}
42+
>
43+
Show Toast
44+
</Button>
45+
</Box>
46+
);
47+
}
48+
49+
export default App;
50+
`}
51+
</Sandbox>
52+
</StoryPageWrapper>
53+
);
54+
};
55+
56+
export default {
57+
title: 'Components/Toast',
58+
component: Toast,
59+
tags: ['autodocs'],
60+
argTypes: {
61+
isVisible: {
62+
table: {
63+
disable: true,
64+
},
65+
},
66+
id: {
67+
table: {
68+
disable: true,
69+
},
70+
},
71+
},
72+
parameters: {
73+
docs: {
74+
page: Page,
75+
},
76+
},
77+
} as Meta<ToastProps>;
78+
79+
const texts = {
80+
negative: 'Unable to fetch merchant details',
81+
positive: 'Customer details failed successfully',
82+
notice: 'Your KYC is pending',
83+
information: 'Your transaction will be settled in 3 business days',
84+
neutral: 'Your transaction will be settled in 3 business days',
85+
} as const;
86+
87+
const BasicToastTemplate: StoryFn<ToastProps> = (args) => {
88+
const toast = useToast();
89+
90+
if (args.type === 'promotional') {
91+
args.content = <Text size="small">{args.content}</Text>;
92+
}
93+
94+
return (
95+
<Box height="80vh">
96+
<Text marginBottom="spacing.3" color="surface.text.gray.subtle">
97+
After changing storybook controls, press the show "toast button" to see changes
98+
</Text>
99+
<Button
100+
onClick={() => {
101+
toast.show(args);
102+
}}
103+
>
104+
Show Toast
105+
</Button>
106+
<ToastContainer />
107+
</Box>
108+
);
109+
};
110+
111+
BasicToastTemplate.storyName = 'Basic';
112+
export const Basic = BasicToastTemplate.bind({});
113+
Basic.args = {
114+
color: 'neutral',
115+
type: 'informational',
116+
autoDismiss: false,
117+
content: 'Payment successful',
118+
action: {
119+
text: 'Okay',
120+
onClick: ({ toastId }) => console.log(toastId),
121+
},
122+
};
123+
124+
const ToastVariantsTemplate: StoryFn<ToastProps> = () => {
125+
const toast = useToast();
126+
const hasPromoToast = toast.toasts.some((t) => t.type === 'promotional');
127+
128+
const showInformationalToast = ({ color }: { color: ToastProps['color'] }) => {
129+
toast.show({
130+
content: texts[color!],
131+
color,
132+
action: {
133+
text: 'Okay',
134+
onClick: ({ toastId }) => toast.dismiss(toastId),
135+
},
136+
onDismissButtonClick: ({ toastId }) => console.log(`${toastId} Dismissed!`),
137+
});
138+
};
139+
140+
const showPromotionalToast = () => {
141+
toast.show({
142+
type: 'promotional',
143+
content: (
144+
<Box display="flex" gap="spacing.3" flexDirection="column">
145+
<Heading>Introducing TurboUPI</Heading>
146+
<img
147+
loading="lazy"
148+
width="100%"
149+
height="100px"
150+
alt="Promotional Toast"
151+
style={{ objectFit: 'cover', borderRadius: '8px' }}
152+
src="https://d6xcmfyh68wv8.cloudfront.net/blog-content/uploads/2023/05/Features-blog.png"
153+
/>
154+
<Text weight="semibold">Lightning-fast payments with the new Razorpay Turbo UPI</Text>
155+
<Text size="xsmall">
156+
Turbo UPI allows end-users to complete their payment in-app, with no redirections or
157+
dependence on third-party UPI apps. With Turbo UPI, payments will be 5x faster with a
158+
significantly-improved success rate of 10%!
159+
</Text>
160+
</Box>
161+
),
162+
action: {
163+
text: 'Try TurboUPI',
164+
onClick: ({ toastId }) => toast.dismiss(toastId),
165+
},
166+
onDismissButtonClick: ({ toastId }) => console.log(`${toastId} Dismissed!`),
167+
});
168+
};
169+
170+
return (
171+
<Box height="80vh">
172+
<Text>Show Informational Toasts:</Text>
173+
<Box display="flex" gap="spacing.3" marginY="spacing.5">
174+
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'positive' })}>
175+
Positive
176+
</Button>
177+
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'negative' })}>
178+
Negative
179+
</Button>
180+
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'notice' })}>
181+
Notice
182+
</Button>
183+
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'information' })}>
184+
Information
185+
</Button>
186+
<Button variant="tertiary" onClick={() => showInformationalToast({ color: 'neutral' })}>
187+
Neutral
188+
</Button>
189+
</Box>
190+
<Text>Show Promotional Toasts:</Text>
191+
<Text size="small" color="surface.text.gray.muted">
192+
Note: There can only be 1 promotional toast at a time
193+
</Text>
194+
<Box display="flex" gap="spacing.3" marginY="spacing.5">
195+
<Button
196+
variant="tertiary"
197+
onClick={() => showPromotionalToast()}
198+
isDisabled={hasPromoToast}
199+
>
200+
Promotional
201+
</Button>
202+
</Box>
203+
<ToastContainer />
204+
</Box>
205+
);
206+
};
207+
208+
export const ToastVariants = ToastVariantsTemplate.bind({});
209+
ToastVariants.storyName = 'Toast Variants';

0 commit comments

Comments
 (0)