Skip to content
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

Merged
merged 45 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7051454
chore: wip toast
anuraghazra Jan 28, 2024
dfa7939
chore: wip toast stacking
anuraghazra Feb 1, 2024
2323145
chore: update stacking animation
anuraghazra Feb 5, 2024
39fb2bb
chore: update toast stacking animation
anuraghazra Feb 6, 2024
efa548f
chore: revise stacking
anuraghazra Feb 7, 2024
d1672e4
feat: promo toast stacking
anuraghazra Feb 7, 2024
7eedb4e
chore: fix pointer event bug
anuraghazra Feb 7, 2024
101094b
chore: update
anuraghazra Feb 7, 2024
5cbbc61
chore: cleanup & mobile ux fixes
anuraghazra Feb 7, 2024
3319520
chore: fix promo toast stack bug
anuraghazra Feb 8, 2024
5c23c83
chore: fix exit animation
anuraghazra Feb 9, 2024
3331954
chore: fix abrupt exit animation
anuraghazra Feb 9, 2024
5185747
chore: add duration,autoDismiss logic
anuraghazra Feb 11, 2024
469e8f5
chore: add docs
anuraghazra Feb 11, 2024
a0dfac3
chore: update mobile check
anuraghazra Feb 12, 2024
0120aed
chore: update timing
anuraghazra Feb 12, 2024
ffe9e69
chore: fix build
anuraghazra Feb 12, 2024
45f535f
chore: update spacing tokens
anuraghazra Feb 12, 2024
a51eada
chore: update border color
anuraghazra Feb 13, 2024
8c77908
chore: update
anuraghazra Feb 13, 2024
4354e65
chore: review comments
anuraghazra Feb 13, 2024
13cba57
chore: fix timers starting/pausing repeatedly
anuraghazra Feb 13, 2024
cc4d1e3
fix: first toast height calculation going wrong with invisible toast
anuraghazra Feb 13, 2024
6ff58ad
refactor: stacking logic and added video for understanding
anuraghazra Feb 13, 2024
9b56142
chore: add dev warning for 1 promo toast
anuraghazra Feb 14, 2024
feefb6e
chore: fix toast prop drilling
anuraghazra Feb 14, 2024
53f308a
chore: fix ts
anuraghazra Feb 14, 2024
5ba4e5c
chore: update peek gutter
anuraghazra Feb 14, 2024
87bff48
chore: update zindex
anuraghazra Feb 14, 2024
c819313
chore: move to constants
anuraghazra Feb 14, 2024
1b62f74
chore: update height
anuraghazra Feb 14, 2024
b380ff5
chore: review update
anuraghazra Feb 14, 2024
f7e9b65
chore: change warning to notice
anuraghazra Feb 14, 2024
5a51c09
Merge remote-tracking branch 'origin' into anu/toast
anuraghazra Feb 14, 2024
78340b8
Merge branch 'master' into anu/toast
anuraghazra Feb 14, 2024
d83cf0f
chore: update warning to notive
anuraghazra Feb 14, 2024
8c1b236
chore: update
anuraghazra Feb 14, 2024
9a472f6
chore: update height calculation bug
anuraghazra Feb 14, 2024
6cb1941
chore: update mouse over container
anuraghazra Feb 15, 2024
19953d7
Merge remote-tracking branch 'origin' into anu/toast
anuraghazra Feb 15, 2024
5980893
chore: update lock
anuraghazra Feb 15, 2024
62e45aa
test(e2e): add toast interaction tests (#2012)
anuraghazra Feb 15, 2024
ff8de33
Create lovely-eggs-juggle.md
anuraghazra Feb 15, 2024
e90ac1c
chore: update texts
anuraghazra Feb 15, 2024
0a90bd6
chore: update
anuraghazra Feb 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lovely-eggs-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@razorpay/blade": minor
---

feat(blade): add toast component
2 changes: 2 additions & 0 deletions packages/blade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Collaborator

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?

Copy link
Member Author

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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"
Expand Down Expand Up @@ -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"
Expand Down
17 changes: 17 additions & 0 deletions packages/blade/src/components/Toast/Toast.native.tsx
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 <></>;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 };
209 changes: 209 additions & 0 deletions packages/blade/src/components/Toast/Toast.stories.tsx
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 />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There will always be 1 ToastContainer, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will be on the root level of the app.

Copy link
Member Author

Choose a reason for hiding this comment

The 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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets move the show toast button to the right side corner so it doesn't overlap in the basic story?
Screenshot 2024-02-13 at 12 41 36 PM

Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, added a note in the top.

title: 'Components/Toast',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default promotional toast that shows up seems too dull & basic? Should add some content to it by default?

Screenshot 2024-02-13 at 12 47 21 PM

Copy link
Member Author

Choose a reason for hiding this comment

The 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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The leading icon on promotional toast seems misaligned?
Screenshot 2024-02-13 at 12 48 11 PM

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah actually because we are using <Text size="medium"> since the content supports React.ReactNode.

I've changed it to use <Text size="small">

tags: ['autodocs'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we exposing duration as a prop? If yes, then it means "4000 for informational toast 8000 for promotional toast" is just a default that can be overridden?
Screenshot 2024-02-13 at 12 49 10 PM

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is just a default that can be overridden?

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) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where we demo the toast stacking behaviour and how it behaves with a promotional toast + informational toast

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';
Loading
Loading