Skip to content

Commit

Permalink
feat(Toast)!: introduce 2.0 component
Browse files Browse the repository at this point in the history
- unmark as deprecated
- add tests and 2.0 version
  • Loading branch information
booc0mtaco committed Mar 26, 2024
1 parent ded98b2 commit ee014be
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
* Message of information, success, caution, or warning to the user.
*/
.banner {
/* Position is relative to allow for absolute-positioned close button. */
position: relative;
/* Grid is used to separate the icon from the text with correct spacing. */
display: flex;
gap: 1rem;
padding: 1rem;
Expand Down
6 changes: 3 additions & 3 deletions src/components/BannerNotification/BannerNotification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type BannerNotificationProps = {
*/
className?: string;
/**
* Callback when banner is dismissed. When passed in, renders banner with a close icon in the top right.
* Callback when notification is dismissed. When passed in, renders banner with a close icon in the top right.
*/
onDismiss?: () => void;
// Design API
Expand All @@ -42,7 +42,7 @@ export type BannerNotificationProps = {
*/
subTitle?: string;
/**
* The title/heading of the banner
* The title/heading of the notification
*/
title?: string;
};
Expand Down Expand Up @@ -125,7 +125,7 @@ export const BannerNotification = ({
</div>
)}
</div>

{/* TODO-AH: Use `Button` properly */}
{onDismiss && (
<button
className={styles['banner-notification__close-button']}
Expand Down
35 changes: 35 additions & 0 deletions src/components/Toast/Toast-v2.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@import '../../design-tokens/mixins.css';

/*------------------------------------*\
# TOAST
\*------------------------------------*/

.toast {
display: flex;
gap: 1rem;
padding: 1rem;
align-items: center;

border: 0.125rem solid;
border-left: 1rem solid;
border-radius: calc(var(--eds-theme-border-radius-objects-sm) * 1px);


&.toast--status-critical {
color: var(--eds-theme-color-text-utility-critical);
background-color: var(--eds-theme-color-background-utility-critical-low-emphasis);
}

&.toast--status-favorable {
color: var(--eds-theme-color-text-utility-favorable);
background-color: var(--eds-theme-color-background-utility-favorable-low-emphasis);
}
}

.toast__icon {
flex-shrink: 0;
}

.toast__body {
flex-grow: 2;
}
39 changes: 39 additions & 0 deletions src/components/Toast/Toast-v2.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { StoryObj, Meta } from '@storybook/react';
import type { ComponentProps } from 'react';

import { Toast } from './Toast-v2';

export default {
title: 'Components/V2/Toast',
component: Toast,
parameters: {
badges: ['intro-1.0', 'current-2.0'],
},
argTypes: { onDismiss: { action: 'dismissed' } },
args: {
children: "You've got a temporary notification!",
},
} as Meta<Args>;

type Args = ComponentProps<typeof Toast>;

export const Default: StoryObj<Args> = {};

export const Favorable: StoryObj<Args> = {
args: {
status: 'favorable',
},
};

export const Critical: StoryObj<Args> = {
args: {
status: 'critical',
},
};

export const NotDismissable: StoryObj<Args> = {
args: {
...Default.args,
onDismiss: undefined,
},
};
8 changes: 8 additions & 0 deletions src/components/Toast/Toast-v2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { generateSnapshots } from '@chanzuckerberg/story-utils';
import type { StoryFile } from '@storybook/testing-react';

import * as stories from './Toast.stories';

describe('<Toast /> (v2)', () => {
generateSnapshots(stories as StoryFile);
});
104 changes: 104 additions & 0 deletions src/components/Toast/Toast-v2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import clsx from 'clsx';
import React from 'react';
import type { ReactNode } from 'react';

import type { Status } from '../../util/variant-types';
import { ButtonV2 as Button } from '../Button';
import Icon, { type IconName } from '../Icon';
import Text from '../Text';

import styles from './Toast-v2.module.css';

/**
* TODO-AH:
* - which status is default?
* - default / min / max widths?
*/

export type ToastProps = {
// Component API
/**
* Additional class names that can be appended to the component, passed in for styling.
*/
className?: string;
/**
* The child node(s) contains the toast message.
*/
children: ReactNode;
/**
* Callback when notification is dismissed. When passed in, renders banner with a close icon in the top right.
*/
onDismiss?: () => void;
// Design API
/**
* Keyword to characterize the state of the notification
*/
status?: Extract<Status, 'favorable' | 'critical'>;
/**
* The title/heading of the notification
*/
title: string;
};

/**
* Map statuses to existing icon names
* TODO-AH: de-dupe this with the function in InlineNotification
*
* @param status component status
* @returns the matching icon name
*/
function getIconNameFromStatus(status: Status): IconName {
const map: Record<Status, IconName> = {
informational: 'info',
critical: 'dangerous',
warning: 'warning',
favorable: 'check-circle',
};
return map[status];
}

/**
* `import {Toast} from "@chanzuckerberg/eds";`
*
* Toasts display brief, temporary notifications. They're meant to be noticed without disrupting a user's experience or requiring an action to be taken.
*/
export const Toast = ({
children,
className,
onDismiss,
status = 'favorable',
...other
}: ToastProps) => {
const componentClassName = clsx(
styles['toast'],
status && styles[`toast--status-${status}`],
className,
);
return (
<div className={componentClassName} {...other}>
<Icon
className={styles['toast__icon']}
name={getIconNameFromStatus(status)}
purpose="decorative"
size="1.875rem"
/>
<div className={styles['toast__body']}>
<Text as="span" className={styles['toast__text']} preset="title-md">
{children}
</Text>
</div>
{onDismiss && (
<Button
aria-label="close"
context="default"
icon="close"
iconLayout="icon-only"
onClick={onDismiss}
rank="tertiary"
>
Close
</Button>
)}
</div>
);
};
100 changes: 100 additions & 0 deletions src/components/Toast/__snapshots__/Toast-v2.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<Toast /> (v2) Error story renders snapshot 1`] = `
<div
class="toast toast--error"
>
<div
class="toast__content"
>
<svg
class="icon"
fill="currentColor"
height="1.5rem"
role="img"
style="--icon-size: 1.5rem;"
viewBox="0 0 24 24"
width="1.5rem"
xmlns="http://www.w3.org/2000/svg"
>
<title>
error
</title>
<path
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"
/>
</svg>
<p
class="toast__text"
>
You've got toast!
</p>
</div>
</div>
`;

exports[`<Toast /> (v2) NotDismissable story renders snapshot 1`] = `
<div
class="toast toast--success"
>
<div
class="toast__content"
>
<svg
class="icon"
fill="currentColor"
height="1.5rem"
role="img"
style="--icon-size: 1.5rem;"
viewBox="0 0 24 24"
width="1.5rem"
xmlns="http://www.w3.org/2000/svg"
>
<title>
success
</title>
<path
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"
/>
</svg>
<p
class="toast__text"
>
You've got toast!
</p>
</div>
</div>
`;

exports[`<Toast /> (v2) Success story renders snapshot 1`] = `
<div
class="toast toast--success"
>
<div
class="toast__content"
>
<svg
class="icon"
fill="currentColor"
height="1.5rem"
role="img"
style="--icon-size: 1.5rem;"
viewBox="0 0 24 24"
width="1.5rem"
xmlns="http://www.w3.org/2000/svg"
>
<title>
success
</title>
<path
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"
/>
</svg>
<p
class="toast__text"
>
You've got toast!
</p>
</div>
</div>
`;

0 comments on commit ee014be

Please sign in to comment.