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

🪟 🎨 Update toast design #19980

Merged
merged 30 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
92888ab
feat: changed styling for toast
harshithmullapudi Nov 10, 2022
9c964a8
fix: made changes requested
harshithmullapudi Nov 15, 2022
3630e5c
Merge branch 'master' into harshith/toast-component
harshithmullapudi Nov 15, 2022
cb51664
fix: made changes requested
harshithmullapudi Nov 16, 2022
0fca566
Merge branch 'harshith/toast-component' into vlad/18631-update-toast-…
dizel852 Nov 30, 2022
7515dcf
update Toast design
dizel852 Dec 1, 2022
290921d
update Toast storybook
dizel852 Dec 1, 2022
e21b2fc
replace "hasError" with actual type of Toast
dizel852 Dec 1, 2022
1bd1ea8
replace classes with mixins
dizel852 Dec 1, 2022
0bffea6
fix broken notifications
dizel852 Dec 1, 2022
ee76084
Merge branch 'master' into vlad/18631-update-toast-design
dizel852 Dec 1, 2022
2248340
change the animation to "ease-out"
dizel852 Dec 2, 2022
3945164
replace Notification type with interface
dizel852 Dec 2, 2022
1b89f15
make enum const
dizel852 Dec 2, 2022
a56065a
Merge branch 'master' into vlad/18631-update-toast-design
dizel852 Dec 2, 2022
6dd3f50
use button dark theme
dizel852 Dec 2, 2022
4e0c7c2
fixed stretched icon
dizel852 Dec 2, 2022
77c3bff
move z-index value to css variables file
dizel852 Dec 2, 2022
e4ed432
fix toast bottom-margin and update animation
dizel852 Dec 2, 2022
d9ec81e
add shadow mixin
dizel852 Dec 2, 2022
8334b3f
Merge branch 'master' into vlad/18631-update-toast-design
dizel852 Dec 2, 2022
6c72ace
reduce spacing-page-bottom to 88px
dizel852 Dec 5, 2022
6c9c440
Revert "reduce spacing-page-bottom to 88px"
dizel852 Dec 5, 2022
24e223a
extend base Button component: do not break the text
dizel852 Dec 5, 2022
0323965
align text to left
dizel852 Dec 5, 2022
0203774
set max width for notification container
dizel852 Dec 5, 2022
620beff
Merge branch 'master' into vlad/18631-update-toast-design
dizel852 Dec 5, 2022
00fcf2c
fix text center alignment
dizel852 Dec 7, 2022
9883747
fix action button margin
dizel852 Dec 7, 2022
3ad56ab
Merge branch 'master' into vlad/18631-update-toast-design
dizel852 Dec 8, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DataGeographyDropdown } from "components/common/DataGeographyDropdown";
import { ControlLabels } from "components/LabeledControl";
import { Card } from "components/ui/Card";
import { Spinner } from "components/ui/Spinner";
import { ToastType } from "components/ui/Toast";

import { Geography } from "core/request/AirbyteClient";
import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService";
Expand Down Expand Up @@ -32,8 +33,8 @@ export const UpdateConnectionDataResidency: React.FC = () => {
} catch (e) {
registerNotification({
id: "connection.geographyUpdateError",
title: formatMessage({ id: "connection.geographyUpdateError" }),
isError: true,
text: formatMessage({ id: "connection.geographyUpdateError" }),
type: ToastType.ERROR,
});
}
setSelectedValue(undefined);
Expand Down
28 changes: 0 additions & 28 deletions airbyte-webapp/src/components/ui/Toast/ErrorSign.tsx

This file was deleted.

88 changes: 87 additions & 1 deletion airbyte-webapp/src/components/ui/Toast/Toast.module.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,89 @@
@use "scss/colors";
@use "scss/variables" as vars;

$toast-icon-size: 13px;
$toast-icon-container-size: 34px;

@keyframes slide-up-animations {
0% {
transform: translate(-50%, -100%);
bottom: -49px;
edmundito marked this conversation as resolved.
Show resolved Hide resolved
}

100% {
transform: translate(-50%, 0);
bottom: 49px;
}
}

@mixin type($name, $color, $background) {
&.#{$name} {
background-color: $background;
border: 1px solid $color;

.iconContainer {
background-color: $color;
}

.toastIcon {
color: $color;
}
}
}

.toastContainer {
airbyteio marked this conversation as resolved.
Show resolved Hide resolved
display: flex;
flex-direction: row;
align-items: center;
gap: vars.$spacing-md;
position: fixed;
box-sizing: border-box;
bottom: 49px;
left: 50%;
transform: translate(-50%, 0);
z-index: 20;
edmundito marked this conversation as resolved.
Show resolved Hide resolved
padding: vars.$spacing-md;
border-radius: vars.$border-radius-md;
animation: slide-up-animations 0.25s linear;
edmundito marked this conversation as resolved.
Show resolved Hide resolved

@include type("info", colors.$blue-400, colors.$blue-50);
@include type("warning", colors.$yellow-500, colors.$yellow-50);
@include type("success", colors.$green-200, colors.$green-50);
@include type("error", colors.$red-300, colors.$red-50);
}

.iconContainer {
width: $toast-icon-container-size;
height: $toast-icon-container-size;
padding: vars.$border-radius-md;
border-radius: vars.$border-radius-md;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}

.toastIcon {
width: $toast-icon-size;
height: $toast-icon-size;
background: colors.$white;
border-radius: 50%;
}

.text {
line-height: 17px;
text-align: center;
}

// style can be removed when Button Dark style will be implemented
dizel852 marked this conversation as resolved.
Show resolved Hide resolved
// https://github.com/airbytehq/airbyte/issues/19445
.actionButton {
background-color: colors.$dark-blue-900 !important;
}
edmundito marked this conversation as resolved.
Show resolved Hide resolved

.closeButton {
dizel852 marked this conversation as resolved.
Show resolved Hide resolved
margin-left: 10px;
svg {
font-size: 20px;
color: colors.$dark-blue-900;
}
}
128 changes: 55 additions & 73 deletions airbyte-webapp/src/components/ui/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,68 @@
import { faTimes } from "@fortawesome/free-solid-svg-icons";
import { faCheck, faExclamation, faTimes } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import classNames from "classnames";
import React from "react";
import styled, { keyframes } from "styled-components";

import { H5 } from "components/base/Titles";
import { Text } from "components/ui/Text";

import { Button } from "../Button";
import { ErrorSign } from "./ErrorSign";
import styles from "./Toast.module.scss";

interface ToastProps {
title: string | React.ReactNode;
text?: string | React.ReactNode;
hasError?: boolean;
onClose?: () => void;
export enum ToastType {
edmundito marked this conversation as resolved.
Show resolved Hide resolved
WARNING = "warning",
SUCCESS = "success",
ERROR = "error",
INFO = "info",
}

export const SlideUpAnimation = keyframes`
0% {
translate(-50%, -100%);
bottom: -49px;
}
100% {
translate(-50%, 0);
bottom: 49px;
}
`;

const Singleton = styled.div<{ hasError?: boolean }>`
position: fixed;
bottom: 49px;
left: 50%;
transform: translate(-50%, 0);
z-index: 20;

padding: 25px 25px 22px;

background: ${({ theme, hasError }) => (hasError ? theme.lightDangerColor : theme.lightPrimaryColor)};
border: 1px solid ${({ theme }) => theme.greyColor20};
box-shadow: 0 1px 2px ${({ theme }) => theme.shadowColor};
border-radius: 8px;

display: flex;
flex-direction: row;
align-items: center;

animation: ${SlideUpAnimation} 0.25s linear;
`;

const Title = styled(H5)<{ hasError?: boolean }>`
color: ${({ theme, hasError }) => (hasError ? theme.dangerColor : theme.primaryColor)};

font-style: normal;
font-weight: bold;
font-size: 15px;
line-height: 18px;
`;
export interface ToastProps {
text: string | React.ReactNode;
type?: ToastType;
onAction?: () => void;
actionBtnText?: string;
onClose?: () => void;
}

const Text = styled.div`
color: ${({ theme }) => theme.mediumPrimaryColor};
const ICON_MAPPING = {
[ToastType.WARNING]: faExclamation,
[ToastType.ERROR]: faTimes,
[ToastType.SUCCESS]: faCheck,
[ToastType.INFO]: faExclamation,
};

font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 17px;
margin-top: 5px;
`;
const STYLES_BY_TYPE: Readonly<Record<ToastType, string>> = {
[ToastType.WARNING]: styles.warning,
[ToastType.ERROR]: styles.error,
[ToastType.SUCCESS]: styles.success,
[ToastType.INFO]: styles.info,
};

export const Toast: React.FC<ToastProps> = (props) => (
<Singleton hasError={props.hasError}>
{props.hasError && <ErrorSign />}
<div>
<Title hasError={props.hasError}>{props.title}</Title>
{props.text && <Text>{props.text}</Text>}
export const Toast: React.FC<ToastProps> = ({ type = ToastType.INFO, onAction, actionBtnText, onClose, text }) => {
return (
<div className={classNames(styles.toastContainer, STYLES_BY_TYPE[type])}>
<div className={classNames(styles.iconContainer)}>
<FontAwesomeIcon icon={ICON_MAPPING[type]} className={styles.toastIcon} />
</div>
<div>
dizel852 marked this conversation as resolved.
Show resolved Hide resolved
{text && (
<Text size="lg" className={styles.text}>
{text}
</Text>
)}
</div>
{onAction && (
<Button className={styles.actionButton} onClick={onAction}>
{actionBtnText}
</Button>
)}
{onClose && (
<Button
className={styles.closeButton}
variant="clear"
onClick={onClose}
icon={<FontAwesomeIcon icon={faTimes} />}
edmundito marked this conversation as resolved.
Show resolved Hide resolved
/>
)}
</div>
{props.onClose && (
<Button
className={styles.closeButton}
variant="clear"
onClick={props.onClose}
icon={<FontAwesomeIcon icon={faTimes} />}
/>
)}
</Singleton>
);
);
};
55 changes: 48 additions & 7 deletions airbyte-webapp/src/components/ui/Toast/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { ComponentStory, ComponentMeta } from "@storybook/react";

import { Toast } from "./Toast";
import { Toast, ToastType } from "./Toast";

export default {
title: "UI/Toast",
component: Toast,
argTypes: {
title: { type: { name: "string", required: false } },
text: { type: { name: "string", required: false } },
type: { type: { name: "string", required: false } },
onAction: { table: { disable: true } },
actionBtnText: { type: { name: "string", required: false } },
onClose: { table: { disable: true } },
},
} as ComponentMeta<typeof Toast>;
Expand All @@ -19,17 +21,56 @@ Basic.args = {
text: "This is a basic card",
};

export const WithTitle = Template.bind({});
WithTitle.args = {
title: "With Title",
text: "This is a card with a title",
export const WithText = Template.bind({});
WithText.args = {
text: "This is a card with a text",
};

export const WithLongText = Template.bind({});
WithLongText.args = {
text: "This is a card with a long text, very very long text message. Just an example how ",
};

export const WithCloseButton = Template.bind({});
WithCloseButton.args = {
title: "With Close button",
text: "This is a card with a close button",
onClose: () => {
console.log("Closed!");
},
};

export const WithActionButton = Template.bind({});
WithActionButton.args = {
text: "This is a card with an action button button",
onAction: () => console.log("Action btn clicked!"),
actionBtnText: "Click me!",
};

export const WithActionAndCloseButton = Template.bind({});
WithActionAndCloseButton.args = {
text: "This is a card with an action button button",
onAction: () => console.log("Action btn clicked!"),
actionBtnText: "Click me!",
onClose: () => console.log("Closed!"),
};

export const WarningToast = Template.bind({});
WarningToast.args = {
text: "This is a card with a close button",
onClose: () => console.log("Closed!"),
type: ToastType.WARNING,
};

export const ErrorToast = Template.bind({});
ErrorToast.args = {
text: "This is a card with a close button",
onClose: () => console.log("Closed!"),
type: ToastType.ERROR,
};

export const SuccessToast = Template.bind({});
SuccessToast.args = {
text: "This is a card with a close button",
onClose: () => console.log("Closed!"),
type: ToastType.SUCCESS,
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { HealthService } from "core/health/HealthService";
import { useGetService } from "core/servicesProvider";
import { useNotificationService } from "hooks/services/Notification/NotificationService";

import { ToastType } from "../../../components/ui/Toast";
import { Notification } from "../Notification";

const HEALTH_NOTIFICATION_ID = "health.error";
const HEALTHCHECK_MAX_COUNT = 3;

Expand All @@ -17,10 +20,10 @@ function useApiHealthPoll(): void {
const { registerNotification, unregisterNotificationById } = useNotificationService();

useEffect(() => {
const errorNotification = {
const errorNotification: Notification = {
id: HEALTH_NOTIFICATION_ID,
title: formatMessage({ id: "notifications.error.health" }),
isError: true,
text: formatMessage({ id: "notifications.error.health" }),
type: ToastType.ERROR,
};

const interval = setInterval(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ const NotificationService = ({ children }: { children: React.ReactNode }) => {
{firstNotification ? (
// Show only first notification
<Toast
title={firstNotification.title}
text={firstNotification.text}
hasError={firstNotification.isError}
type={firstNotification.type}
onClose={
firstNotification.nonClosable
? undefined
Expand Down
Loading