Skip to content

Commit 8bce819

Browse files
authored
feat(Toast)!: introduce 2.0 component (#1906)
- unmark as deprecated - add tests and 2.0 version - update documentation for related components
1 parent ded98b2 commit 8bce819

File tree

7 files changed

+285
-7
lines changed

7 files changed

+285
-7
lines changed

src/components/BannerNotification/BannerNotification.module.css

-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
* Message of information, success, caution, or warning to the user.
99
*/
1010
.banner {
11-
/* Position is relative to allow for absolute-positioned close button. */
12-
position: relative;
13-
/* Grid is used to separate the icon from the text with correct spacing. */
1411
display: flex;
1512
gap: 1rem;
1613
padding: 1rem;

src/components/BannerNotification/BannerNotification.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ export type BannerNotificationProps = {
2121
*/
2222
className?: string;
2323
/**
24-
* Callback when banner is dismissed. When passed in, renders banner with a close icon in the top right.
24+
* Callback when notification is dismissed. When passed in, renders banner with a close icon in the top right.
2525
*/
2626
onDismiss?: () => void;
2727
// Design API
2828
/**
29-
*
29+
* Whether the button layout for the call to action is vertical or horizontal.
3030
*/
3131
buttonLayout?: 'vertical' | 'horizontal';
3232
/**
@@ -42,7 +42,7 @@ export type BannerNotificationProps = {
4242
*/
4343
subTitle?: string;
4444
/**
45-
* The title/heading of the banner
45+
* The title/heading of the notification
4646
*/
4747
title?: string;
4848
};
@@ -125,7 +125,7 @@ export const BannerNotification = ({
125125
</div>
126126
)}
127127
</div>
128-
128+
{/* TODO-AH: Use `Button` properly */}
129129
{onDismiss && (
130130
<button
131131
className={styles['banner-notification__close-button']}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
@import '../../design-tokens/mixins.css';
2+
3+
/*------------------------------------*\
4+
# TOAST
5+
\*------------------------------------*/
6+
7+
.toast {
8+
display: flex;
9+
gap: 1rem;
10+
padding: 1rem;
11+
align-items: center;
12+
13+
border: 0.125rem solid;
14+
border-left: 1rem solid;
15+
border-radius: calc(var(--eds-theme-border-radius-objects-sm) * 1px);
16+
17+
18+
&.toast--status-critical {
19+
color: var(--eds-theme-color-text-utility-critical);
20+
background-color: var(--eds-theme-color-background-utility-critical-low-emphasis);
21+
}
22+
23+
&.toast--status-favorable {
24+
color: var(--eds-theme-color-text-utility-favorable);
25+
background-color: var(--eds-theme-color-background-utility-favorable-low-emphasis);
26+
}
27+
}
28+
29+
.toast__icon {
30+
flex-shrink: 0;
31+
}
32+
33+
.toast__body {
34+
flex-grow: 2;
35+
}
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { StoryObj, Meta } from '@storybook/react';
2+
import type { ComponentProps } from 'react';
3+
4+
import { Toast } from './Toast-v2';
5+
6+
export default {
7+
title: 'Components/V2/Toast',
8+
component: Toast,
9+
parameters: {
10+
badges: ['intro-1.0', 'current-2.0'],
11+
},
12+
argTypes: { onDismiss: { action: 'dismissed' } },
13+
args: {
14+
title: "You've got a temporary notification!",
15+
},
16+
} as Meta<Args>;
17+
18+
type Args = ComponentProps<typeof Toast>;
19+
20+
export const Default: StoryObj<Args> = {};
21+
22+
export const Favorable: StoryObj<Args> = {
23+
args: {
24+
status: 'favorable',
25+
},
26+
};
27+
28+
export const Critical: StoryObj<Args> = {
29+
args: {
30+
status: 'critical',
31+
},
32+
};
33+
34+
export const NotDismissable: StoryObj<Args> = {
35+
args: {
36+
...Default.args,
37+
onDismiss: undefined,
38+
},
39+
};

src/components/Toast/Toast-v2.test.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { generateSnapshots } from '@chanzuckerberg/story-utils';
2+
import type { StoryFile } from '@storybook/testing-react';
3+
4+
import * as stories from './Toast.stories';
5+
6+
describe('<Toast /> (v2)', () => {
7+
generateSnapshots(stories as StoryFile);
8+
});

src/components/Toast/Toast-v2.tsx

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import clsx from 'clsx';
2+
import React from 'react';
3+
4+
import type { Status } from '../../util/variant-types';
5+
import { ButtonV2 as Button } from '../Button';
6+
import Icon, { type IconName } from '../Icon';
7+
import Text from '../Text';
8+
9+
import styles from './Toast-v2.module.css';
10+
11+
/**
12+
* TODO-AH:
13+
* - which status is default?
14+
* - default / min / max widths?
15+
*/
16+
17+
export type ToastProps = {
18+
// Component API
19+
/**
20+
* Additional class names that can be appended to the component, passed in for styling.
21+
*/
22+
className?: string;
23+
/**
24+
* Callback when notification is dismissed. When passed in, renders banner with a close icon in the top right.
25+
*/
26+
onDismiss?: () => void;
27+
// Design API
28+
/**
29+
* Keyword to characterize the state of the notification
30+
*/
31+
status?: Extract<Status, 'favorable' | 'critical'>;
32+
/**
33+
* The title/heading of the notification
34+
*/
35+
title: string;
36+
};
37+
38+
/**
39+
* Map statuses to existing icon names
40+
* TODO-AH: de-dupe this with the function in InlineNotification
41+
*
42+
* @param status component status
43+
* @returns the matching icon name
44+
*/
45+
function getIconNameFromStatus(status: Status): IconName {
46+
const map: Record<Status, IconName> = {
47+
informational: 'info',
48+
critical: 'dangerous',
49+
warning: 'warning',
50+
favorable: 'check-circle',
51+
};
52+
return map[status];
53+
}
54+
55+
/**
56+
* `import {Toast} from "@chanzuckerberg/eds";`
57+
*
58+
* Toasts display brief, temporary notifications. They're meant to be noticed without disrupting a user's experience or requiring an action to be taken.
59+
*/
60+
export const Toast = ({
61+
className,
62+
onDismiss,
63+
status = 'favorable',
64+
title,
65+
...other
66+
}: ToastProps) => {
67+
const componentClassName = clsx(
68+
styles['toast'],
69+
status && styles[`toast--status-${status}`],
70+
className,
71+
);
72+
return (
73+
<div className={componentClassName} {...other}>
74+
<Icon
75+
className={styles['toast__icon']}
76+
name={getIconNameFromStatus(status)}
77+
purpose="decorative"
78+
size="1.875rem"
79+
/>
80+
<div className={styles['toast__body']}>
81+
<Text as="span" className={styles['toast__text']} preset="title-md">
82+
{title}
83+
</Text>
84+
</div>
85+
{onDismiss && (
86+
<Button
87+
aria-label="close"
88+
context="default"
89+
icon="close"
90+
iconLayout="icon-only"
91+
onClick={onDismiss}
92+
rank="tertiary"
93+
>
94+
Close
95+
</Button>
96+
)}
97+
</div>
98+
);
99+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<Toast /> (v2) Error story renders snapshot 1`] = `
4+
<div
5+
class="toast toast--error"
6+
>
7+
<div
8+
class="toast__content"
9+
>
10+
<svg
11+
class="icon"
12+
fill="currentColor"
13+
height="1.5rem"
14+
role="img"
15+
style="--icon-size: 1.5rem;"
16+
viewBox="0 0 24 24"
17+
width="1.5rem"
18+
xmlns="http://www.w3.org/2000/svg"
19+
>
20+
<title>
21+
error
22+
</title>
23+
<path
24+
d="M1 21L12 2L23 21H1ZM11 15H13V10H11V15ZM12 18C12.2833 18 12.521 17.904 12.713 17.712C12.9043 17.5207 13 17.2833 13 17C13 16.7167 12.9043 16.4793 12.713 16.288C12.521 16.096 12.2833 16 12 16C11.7167 16 11.4793 16.096 11.288 16.288C11.096 16.4793 11 16.7167 11 17C11 17.2833 11.096 17.5207 11.288 17.712C11.4793 17.904 11.7167 18 12 18Z"
25+
/>
26+
</svg>
27+
<p
28+
class="toast__text"
29+
>
30+
You've got toast!
31+
</p>
32+
</div>
33+
</div>
34+
`;
35+
36+
exports[`<Toast /> (v2) NotDismissable story renders snapshot 1`] = `
37+
<div
38+
class="toast toast--success"
39+
>
40+
<div
41+
class="toast__content"
42+
>
43+
<svg
44+
class="icon"
45+
fill="currentColor"
46+
height="1.5rem"
47+
role="img"
48+
style="--icon-size: 1.5rem;"
49+
viewBox="0 0 24 24"
50+
width="1.5rem"
51+
xmlns="http://www.w3.org/2000/svg"
52+
>
53+
<title>
54+
success
55+
</title>
56+
<path
57+
d="M12 22C10.6167 22 9.31667 21.7373 8.1 21.212C6.88333 20.6873 5.825 19.975 4.925 19.075C4.025 18.175 3.31267 17.1167 2.788 15.9C2.26267 14.6833 2 13.3833 2 12C2 10.6167 2.26267 9.31667 2.788 8.1C3.31267 6.88333 4.025 5.825 4.925 4.925C5.825 4.025 6.88333 3.31233 8.1 2.787C9.31667 2.26233 10.6167 2 12 2C13.3833 2 14.6833 2.26233 15.9 2.787C17.1167 3.31233 18.175 4.025 19.075 4.925C19.975 5.825 20.6873 6.88333 21.212 8.1C21.7373 9.31667 22 10.6167 22 12C22 13.3833 21.7373 14.6833 21.212 15.9C20.6873 17.1167 19.975 18.175 19.075 19.075C18.175 19.975 17.1167 20.6873 15.9 21.212C14.6833 21.7373 13.3833 22 12 22ZM10.6 16.6L17.65 9.55L16.25 8.15L10.6 13.8L7.75 10.95L6.35 12.35L10.6 16.6Z"
58+
/>
59+
</svg>
60+
<p
61+
class="toast__text"
62+
>
63+
You've got toast!
64+
</p>
65+
</div>
66+
</div>
67+
`;
68+
69+
exports[`<Toast /> (v2) Success story renders snapshot 1`] = `
70+
<div
71+
class="toast toast--success"
72+
>
73+
<div
74+
class="toast__content"
75+
>
76+
<svg
77+
class="icon"
78+
fill="currentColor"
79+
height="1.5rem"
80+
role="img"
81+
style="--icon-size: 1.5rem;"
82+
viewBox="0 0 24 24"
83+
width="1.5rem"
84+
xmlns="http://www.w3.org/2000/svg"
85+
>
86+
<title>
87+
success
88+
</title>
89+
<path
90+
d="M12 22C10.6167 22 9.31667 21.7373 8.1 21.212C6.88333 20.6873 5.825 19.975 4.925 19.075C4.025 18.175 3.31267 17.1167 2.788 15.9C2.26267 14.6833 2 13.3833 2 12C2 10.6167 2.26267 9.31667 2.788 8.1C3.31267 6.88333 4.025 5.825 4.925 4.925C5.825 4.025 6.88333 3.31233 8.1 2.787C9.31667 2.26233 10.6167 2 12 2C13.3833 2 14.6833 2.26233 15.9 2.787C17.1167 3.31233 18.175 4.025 19.075 4.925C19.975 5.825 20.6873 6.88333 21.212 8.1C21.7373 9.31667 22 10.6167 22 12C22 13.3833 21.7373 14.6833 21.212 15.9C20.6873 17.1167 19.975 18.175 19.075 19.075C18.175 19.975 17.1167 20.6873 15.9 21.212C14.6833 21.7373 13.3833 22 12 22ZM10.6 16.6L17.65 9.55L16.25 8.15L10.6 13.8L7.75 10.95L6.35 12.35L10.6 16.6Z"
91+
/>
92+
</svg>
93+
<p
94+
class="toast__text"
95+
>
96+
You've got toast!
97+
</p>
98+
</div>
99+
</div>
100+
`;

0 commit comments

Comments
 (0)