From 311a301cd0c6018012aa1e77173ec5d96c6a23e6 Mon Sep 17 00:00:00 2001 From: DipperTheDan Date: Wed, 22 Nov 2023 11:27:37 +0000 Subject: [PATCH] refactor(toast): decouple Toast component from Message component It was decided in 2019 that `Toast` should share the same functionality and styling as `Message`. This was the correct decision at the time but with the new Design System and the changes coming to these components, it will be easier to maintain these as two seperate components. --- .../message-content/message-content.style.ts | 8 -- src/components/toast/toast.component.tsx | 14 ++-- src/components/toast/toast.spec.tsx | 34 ++++---- src/components/toast/toast.stories.mdx | 8 ++ src/components/toast/toast.style.ts | 83 +++++++++++++++---- 5 files changed, 99 insertions(+), 48 deletions(-) diff --git a/src/components/message/message-content/message-content.style.ts b/src/components/message/message-content/message-content.style.ts index 8c9fb13bf5..a1b68b53c4 100644 --- a/src/components/message/message-content/message-content.style.ts +++ b/src/components/message/message-content/message-content.style.ts @@ -8,14 +8,6 @@ const MessageContentStyle = styled.div< 20px; white-space: pre-wrap; flex: 1; - - .carbon-content__title { - margin-bottom: 2px; - } - - .carbon-content__body { - margin-top: 0px; - } `; export default MessageContentStyle; diff --git a/src/components/toast/toast.component.tsx b/src/components/toast/toast.component.tsx index 32c16f3871..76d1d50157 100644 --- a/src/components/toast/toast.component.tsx +++ b/src/components/toast/toast.component.tsx @@ -4,9 +4,9 @@ import { TransitionGroup, CSSTransition } from "react-transition-group"; import Icon from "../icon"; import tagComponent from "../../__internal__/utils/helpers/tags/tags"; import { - ToastStyle, + StyledToast, TypeIcon, - ToastContentStyle, + StyledToastContent, ToastWrapper, StyledPortal, } from "./toast.style"; @@ -202,7 +202,7 @@ export const Toast = React.forwardRef( function renderToastContent() { if (!open) return null; - let toastVariant; + let toastVariant: ToastVariants = "success"; if (!isNotice && !isNotification) { toastVariant = variant; @@ -215,7 +215,7 @@ export const Toast = React.forwardRef( timeout={{ appear: 1600, enter: 1500, exit: 500 }} nodeRef={toastContentNodeRef} > - ( )} - + {children} - + {renderCloseIcon()} - + ); } diff --git a/src/components/toast/toast.spec.tsx b/src/components/toast/toast.spec.tsx index 5f2ebe6500..ad004bdb6b 100644 --- a/src/components/toast/toast.spec.tsx +++ b/src/components/toast/toast.spec.tsx @@ -7,8 +7,8 @@ import Logger from "../../__internal__/utils/logger"; import guid from "../../__internal__/utils/helpers/guid"; import Toast, { ToastProps } from "./toast.component"; import { - ToastStyle, - ToastContentStyle, + StyledToast, + StyledToastContent, ToastWrapper, StyledPortal, TypeIcon, @@ -205,14 +205,14 @@ describe("Toast", () => { }); it("auto focuses the toast component", () => { - const toast = wrapper.find(ToastStyle); + const toast = wrapper.find(StyledToast); jest.runAllTimers(); expect(toast).toBeFocused(); }); it("sets a tabIndex on the toast component and removes onBlur", () => { - const toast = wrapper.find(ToastStyle); + const toast = wrapper.find(StyledToast); expect(toast.getDOMNode().hasAttribute("tabIndex")).toBe(true); @@ -325,7 +325,7 @@ describe("Toast", () => { Child ); - const toast = wrapper.find(ToastStyle); + const toast = wrapper.find(StyledToast); expect(toast.getDOMNode().hasAttribute("tabIndex")).toBe(false); jest.runAllTimers(); expect(toast).not.toBeFocused(); @@ -401,9 +401,9 @@ describe("Toast", () => { wrapper.unmount(); }); - it("should render ToastStyle with correct maxWidth", () => { + it("should render StyledToast with correct maxWidth", () => { wrapper = mount(Child); - assertStyleMatch({ maxWidth: "200px" }, wrapper.find(ToastStyle)); + assertStyleMatch({ maxWidth: "200px" }, wrapper.find(StyledToast)); }); }); @@ -419,8 +419,8 @@ describe("Toast", () => { ); }); - it("then the prop should be passed to ToastStyle", () => { - expect(wrapper.find(ToastStyle).props().isNotice).toBe(true); + it("then the prop should be passed to StyledToast", () => { + expect(wrapper.find(StyledToast).props().isNotice).toBe(true); }); it("then the prop should be passed to StyledPortal", () => { @@ -431,8 +431,8 @@ describe("Toast", () => { expect(wrapper.find(ToastWrapper).props().isNotice).toBe(true); }); - it("then the prop should be passed to ToastContentStyle", () => { - expect(wrapper.find(ToastContentStyle).props().isNotice).toBe(true); + it("then the prop should be passed to StyledToastContent", () => { + expect(wrapper.find(StyledToastContent).props().isNotice).toBe(true); }); it("then the TypeIcon should not be rendered", () => { @@ -479,7 +479,7 @@ describe("Toast", () => { }); }); -describe("ToastStyle", () => { +describe("StyledToast", () => { let wrapper: ReactWrapper; afterEach(() => { @@ -487,12 +487,12 @@ describe("ToastStyle", () => { }); it("should render with correct style based on default theme", () => { - wrapper = mount(); + wrapper = mount(); assertStyleMatch( { boxShadow: - "0 10px 30px 0 rgba(0,20,29,0.1), 0 30px 60px 0 rgba(0,20,29,0.1)", + "0 10px 30px 0 rgba(0,20,29,0.1),0 30px 60px 0 rgba(0,20,29,0.1)", lineHeight: "22px", marginTop: "30px", maxWidth: "300px", @@ -506,7 +506,7 @@ describe("ToastStyle", () => { { borderRadius: "var(--borderRadius100)", }, - wrapper.find(ToastStyle) + wrapper.find(StyledToast) ); }); @@ -563,7 +563,7 @@ describe("TestContentStyle", () => { }); it("should render with correct style based on default theme", () => { - wrapper = mount(); + wrapper = mount(); assertStyleMatch( { @@ -622,7 +622,7 @@ describe("Align vertical", () => { Foo ); - assertStyleMatch({ marginTop: "0" }, wrapper.find(ToastStyle)); + assertStyleMatch({ marginTop: "0" }, wrapper.find(StyledToast)); }); }); diff --git a/src/components/toast/toast.stories.mdx b/src/components/toast/toast.stories.mdx index 95e0cdde0d..2fd9fe1d12 100644 --- a/src/components/toast/toast.stories.mdx +++ b/src/components/toast/toast.stories.mdx @@ -23,6 +23,14 @@ import * as stories from "./toast.stories"; Toast is part of Carbon's Flashes components. Its purpose is to present quick confirmation of success or failure, relating to the task the user has just performed. Alternatively, use Toast messages for a longer, timely message that the user must dismiss. +Various types are available: + +- **Error** - Shows an error or failed action or task. Includes next steps. +- **Info** - Shows additional information when a task is completed. +- **Success** - Shows a successful action or completion of a task. +- **Warning** - Shows information about external factors that impact a task. For example, the user does not have access rights. +- **Neutral** - Shows information or advice, or provides context. + ## Contents - [Quick Start](#quick-start) diff --git a/src/components/toast/toast.style.ts b/src/components/toast/toast.style.ts index 68e976cdff..25cfb6c6b1 100644 --- a/src/components/toast/toast.style.ts +++ b/src/components/toast/toast.style.ts @@ -1,12 +1,10 @@ import styled, { css } from "styled-components"; - -import MessageStyle from "../message/message.style"; -import MessageContentStyle from "../message/message-content/message-content.style"; import TypeIcon from "../message/type-icon/type-icon.style"; import StyledIconButton from "../icon-button/icon-button.style"; import Portal from "../portal/portal"; import baseTheme from "../../style/themes/base"; import StyledIcon from "../icon/icon.style"; +import { MessageVariant } from "../message/message.component"; const StyledPortal = styled(Portal)<{ align?: "left" | "center" | "right"; @@ -17,7 +15,6 @@ const StyledPortal = styled(Portal)<{ ${({ theme, isCenter, isNotice, align, alignY }) => css` position: fixed; top: 0; - z-index: ${theme.zIndex.notification}; ${isCenter && @@ -49,7 +46,7 @@ const StyledPortal = styled(Portal)<{ bottom: 0; top: auto; width: 100%; - `} + `} ${alignY === "top" && css` @@ -79,17 +76,66 @@ StyledPortal.defaultProps = { const animationName = ".toast"; const alternativeAnimationName = ".toast-alternative"; -const ToastStyle = styled(MessageStyle)<{ + +const ToastColourVariants = { + error: "var(--colorsSemanticNegative500)", + info: "var(--colorsSemanticInfo500)", + success: "var(--colorsSemanticPositive500)", + warning: "var(--colorsSemanticCaution500)", + neutral: "var(--colorsSemanticNeutral500)", +}; + +type ToastVariants = MessageVariant; + +type ToastStyleProps = { align?: "left" | "center" | "right"; alignY?: "top" | "center" | "bottom"; maxWidth?: string; isCenter?: boolean; isNotice?: boolean; isNotification?: boolean; -}>` - ${({ maxWidth, isCenter, align, isNotification, alignY, isNotice }) => css` - box-shadow: 0 10px 30px 0 rgba(0, 20, 29, 0.1), - 0 30px 60px 0 rgba(0, 20, 29, 0.1); + variant: ToastVariants; +}; + +const boxShadow = + "0 10px 30px 0 rgba(0, 20, 29, 0.1), 0 30px 60px 0 rgba(0, 20, 29, 0.1)"; + +const iconPositionStyles = css` + position: absolute; + right: 15px; + top: 50%; + transform: translateY(-50%); +`; + +const StyledToast = styled.div` + ${({ + maxWidth, + isCenter, + align, + isNotification, + alignY, + isNotice, + variant, + }) => css` + position: relative; + display: flex; + justify-content: flex-start; + align-content: center; + border-radius: var(--borderRadius100); + overflow: hidden; + border: 1px solid ${ToastColourVariants[variant]}; + background-color: var(--colorsUtilityYang100); + min-height: 38px; + + :focus { + outline: none; + } + + ${StyledIconButton} { + ${iconPositionStyles} + } + + box-shadow: ${boxShadow}; line-height: 22px; margin-top: ${(alignY === "top" && isNotice) || alignY === "center" ? "0" @@ -132,10 +178,7 @@ const ToastStyle = styled(MessageStyle)<{ } ${StyledIconButton} { - position: absolute; - right: 15px; - top: 50%; - transform: translateY(-50%); + ${iconPositionStyles} } ${({ isNotice, alignY }) => @@ -180,11 +223,13 @@ const ToastStyle = styled(MessageStyle)<{ `} `; -const ToastContentStyle = styled(MessageContentStyle)<{ +const StyledToastContent = styled.div<{ isNotice?: boolean; isDismiss?: boolean; }>` padding: 8px 16px 8px 16px; + white-space: pre-wrap; + flex: 1; ${({ isNotice }) => isNotice && @@ -233,4 +278,10 @@ const ToastWrapper = styled.div<{ `} `; -export { ToastStyle, TypeIcon, ToastContentStyle, ToastWrapper, StyledPortal }; +export { + StyledPortal, + StyledToast, + TypeIcon, + StyledToastContent, + ToastWrapper, +};