Skip to content

Commit

Permalink
Merge pull request #280 from LifeSG/BOOKINGSG-4430
Browse files Browse the repository at this point in the history
[Enhancement] Toast to make it sticky
  • Loading branch information
qroll authored Aug 22, 2023
2 parents 58c1ee9 + 9ab3eff commit 653e727
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 26 deletions.
30 changes: 16 additions & 14 deletions src/toast/toast.styles.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { CrossIcon } from "@lifesg/react-icons/cross";
import { animated } from "react-spring";
import { ValidationElementAttributes } from "src/color";
import { PropertiesToType } from "src/util/utility-types";
Expand All @@ -7,12 +6,14 @@ import { Color } from "../color/color";
import { ClickableIcon } from "../shared/clickable-icon";
import { Text } from "../text";
import { ToastType } from "./types";
import { MediaQuery } from "../media";

//=============================================================================
// STYLE INTERFACE
//=============================================================================
interface StyleProps {
$type: ToastType;
$fixed?: boolean | undefined;
}

const getValidationColorAttributes = (
Expand All @@ -37,18 +38,26 @@ const getValidationColorAttributes = (
// =============================================================================
export const Wrapper = styled(animated.div)<StyleProps>`
display: flex;
position: relative;
position: ${(props) => (props.$fixed ? "fixed" : "relative")};
margin: ${(props) => (props.$fixed ? "1rem" : 0)};
top: 0;
right: 0;
padding: 1rem;
border-radius: 0.5rem;
line-height: 0;
z-index: 10;
${MediaQuery.MaxWidth.tablet} {
left: 0;
}
${(props) => {
return css`
background: ${getValidationColorAttributes(props).Background};
border: 1px solid ${getValidationColorAttributes(props).Border};
color: ${getValidationColorAttributes(props).Text};
svg {
& > svg {
width: 1.5rem;
height: 1.5rem;
margin-right: 0.5rem;
Expand All @@ -61,7 +70,6 @@ export const Wrapper = styled(animated.div)<StyleProps>`
export const TextContainer = styled.div`
display: flex;
flex-direction: column;
padding: 0 2rem 0 0;
padding-right: 2rem;
flex: 1;
`;
Expand All @@ -88,22 +96,16 @@ export const Description = styled.div<StyleProps>`
}}
`;

export const CloseIcon = styled(CrossIcon)`
margin-top: 0.2rem;
`;

export const DismissButton = styled(ClickableIcon)<StyleProps>`
padding-top: 0px;
padding-bottom: 0px;
margin-left: 5px;
margin-right: -1rem;
height: max-content;
padding: 0.75rem;
margin: -0.75rem;
${(props) => {
return css`
svg {
width: 1.5rem;
height: 1.5rem;
color: ${getValidationColorAttributes(props).Text};
margin-right: -0.5rem !important;
}
:hover {
background: transparent;
Expand Down
19 changes: 13 additions & 6 deletions src/toast/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {
CrossIcon,
ExclamationCircleFillIcon,
ExclamationTriangleFillIcon,
ICircleFillIcon,
TickCircleFillIcon,
} from "@lifesg/react-icons";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useMediaQuery } from "react-responsive";
import { easings, useSpring } from "react-spring";
import { MediaWidths } from "../spec/media-spec";
import { Text } from "../text";
import {
CloseIcon,
Description,
DismissButton,
TextContainer,
Expand All @@ -28,16 +28,18 @@ export const Toast = ({
autoDismiss,
autoDismissTime = DEFAULT_AUTO_DISMISS_TIME,
onDismiss,
fixed = true,
...otherProps
}: ToastProps) => {
// =============================================================================
// CONST, STATE
// =============================================================================

const [isVisible, setVisible] = useState<boolean>(false);

const isMobile = useMediaQuery({
maxWidth: MediaWidths.mobileL,
maxWidth: MediaWidths.tablet,
});

// =============================================================================
// EFFECTS
// =============================================================================
Expand Down Expand Up @@ -102,7 +104,12 @@ export const Toast = ({
};

return (
<Wrapper style={transitions} $type={type} {...otherProps}>
<Wrapper
style={transitions}
$type={type}
$fixed={fixed}
{...otherProps}
>
{renderIcon()}
<TextContainer>
{title && (
Expand All @@ -121,7 +128,7 @@ export const Toast = ({
)}
</TextContainer>
<DismissButton $type={type} onClick={handleDismiss}>
<CloseIcon />
<CrossIcon />
</DismissButton>
</Wrapper>
);
Expand Down
2 changes: 2 additions & 0 deletions src/toast/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ export interface ToastProps extends React.HTMLAttributes<HTMLDivElement> {
autoDismissTime?: number | undefined;
/** If given, the function will be called when the Toast is dismissed */
onDismiss?: () => void;
/** Specifies if Toast should be fixed to top. Defaults to true */
fixed?: boolean | undefined;
}
15 changes: 14 additions & 1 deletion stories/toast/props-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ const DATA: ApiTableSectionProps[] = [
description: (
<>
Specifies if the <code>Toast</code> will be
automatically dismissed after <code>autoDismissTime</code>.
automatically dismissed after{" "}
<code>autoDismissTime</code>.
</>
),
propTypes: ["boolean"],
Expand Down Expand Up @@ -86,6 +87,18 @@ const DATA: ApiTableSectionProps[] = [
),
propTypes: ["() => void"],
},
{
name: "fixed",
description: (
<>
Specifies if the <code>Toast</code> is to remain
displayed at the top of the page even though a scroll
has happened
</>
),
propTypes: ["boolean"],
defaultValue: "true",
},
],
},
];
Expand Down
69 changes: 64 additions & 5 deletions stories/toast/toast.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { Toast } from "src/toast";
import { Secondary, Heading3, Title } from "../storybook-common";
import { Text } from "src/text";
import { PropsTable } from "./props-table";
import { Button } from "src/button";

<Meta
title="Modules/Toast"
component={Toast}
parameters={{ layout: "padded" }}
parameters={{ layout: "centered" }}
/>

<Title>Toast</Title>
Expand All @@ -23,13 +24,29 @@ import { Toast } from "@lifesg/react-design-system/toast";

<Canvas>
<Story name="Toast">
<Toast type="success" label="This is a success toast message" />
<Toast
type="success"
label="This is a success toast message"
fixed={false}
/>
<br />
<Toast type="warning" label="This is a warning toast message" />
<Toast
type="warning"
label="This is a warning toast message"
fixed={false}
/>
<br />
<Toast type="error" label="This is an error toast message" />
<Toast
type="error"
label="This is an error toast message"
fixed={false}
/>
<br />
<Toast type="info" label="This is an info toast message" />
<Toast
type="info"
label="This is an info toast message"
fixed={false}
/>
</Story>
</Canvas>

Expand All @@ -40,54 +57,62 @@ import { Toast } from "@lifesg/react-design-system/toast";
<Toast
type="success"
label="Your bookings has been updated and received by the service provider."
fixed={false}
/>
<br />
<Toast
type="success"
title="Template successfully updated"
label="Your bookings has been updated and received by the service provider."
fixed={false}
/>
<br />
<br />
<Toast
type="warning"
label="The template contains characters that cannot be updated. Please
remove the characters and try again."
fixed={false}
/>
<br />
<Toast
type="warning"
title="Unknown characters"
label="The template contains characters that cannot be updated. Please
remove the characters and try again."
fixed={false}
/>
<br />
<br />
<Toast
type="error"
label="An internal system error had occured. Please log out and try
again."
fixed={false}
/>
<br />
<Toast
type="error"
title="System error"
label="An internal system error had occured. Please log out and try
again."
fixed={false}
/>
<br />
<br />
<Toast
type="info"
label="The calendar will be automatically updated when you have done
editing the event information."
fixed={false}
/>
<br />
<Toast
type="info"
title="Updated automatically"
label="The calendar will be automatically updated when you have done
editing the event information."
fixed={false}
/>
</Story>
</Canvas>
Expand All @@ -103,16 +128,50 @@ is **4 seconds** and you can specify using the `autoDismiss` property.
type="success"
label="Your bookings has been updated and received by the service provider."
autoDismiss
fixed={false}
/>
<br />
<Toast
type="info"
label="This toast has a custom auto dismiss time of 8 seconds."
autoDismiss
autoDismissTime={8000}
fixed={false}
/>
</Story>
</Canvas>

<Heading3>Fixed positioning</Heading3>

This example demonstrates the default positioning of the Toast. It remains fixed at the top while the page is scrolled.

<Canvas>
<Story name="Fixed positioning">
{() => {
const [isVisible, setIsVisible] = useState(false);
const openToast = () => setIsVisible(true);
const closeToast = () => setIsVisible(false);
return (
<>
<div>
{isVisible ? (
<Button.Default onClick={closeToast}>
Click to close
</Button.Default>
) : (
<Button.Default onClick={openToast}>
Click to open
</Button.Default>
)}
</div>
{isVisible && (
<Toast label="Notification" onDismiss={closeToast} />
)}
</>
);
}}
</Story>
</Canvas>

<Secondary>Component API</Secondary>
<PropsTable />

0 comments on commit 653e727

Please sign in to comment.