From bcf0d7aab617a2a00708fb0b339f48599f42453e Mon Sep 17 00:00:00 2001 From: nuria1110 Date: Thu, 26 Oct 2023 17:17:28 +0100 Subject: [PATCH] feat(toast): toast vertical align prop added a prop to toast which allowes to vertically align the component on the screen fix #6301 --- src/components/toast/toast-test.stories.tsx | 31 +++++- src/components/toast/toast.component.tsx | 8 +- src/components/toast/toast.spec.tsx | 23 ++++- src/components/toast/toast.stories.mdx | 33 ++++-- src/components/toast/toast.stories.tsx | 105 ++++++++++++++++++++ src/components/toast/toast.style.ts | 51 ++++++++-- 6 files changed, 230 insertions(+), 21 deletions(-) diff --git a/src/components/toast/toast-test.stories.tsx b/src/components/toast/toast-test.stories.tsx index a64b616e19..36e7c66ee6 100644 --- a/src/components/toast/toast-test.stories.tsx +++ b/src/components/toast/toast-test.stories.tsx @@ -7,7 +7,7 @@ import { TOAST_COLORS } from "./toast.config"; export default { title: "Toast/Test", - includeStories: ["Default", "Visual"], + includeStories: ["Default", "Visual", "TopAndBottom"], parameters: { info: { disable: true }, chromatic: { @@ -234,6 +234,35 @@ Visual.parameters = { themeProvider: { chromatic: { theme: "sage" } }, }; +export const TopAndBottom = () => { + const [isOpen, setIsOpen] = useState(true); + const handleOpen = () => { + setIsOpen(!isOpen); + action("open")(!isOpen); + }; + return ( + <> + + + My Toast A + + + My Toast B + + + ); +}; + +TopAndBottom.storyName = "top and bottom"; + export const ToastComponent = ({ children = "Toast", ...props diff --git a/src/components/toast/toast.component.tsx b/src/components/toast/toast.component.tsx index d2a600dc0e..b889a35213 100644 --- a/src/components/toast/toast.component.tsx +++ b/src/components/toast/toast.component.tsx @@ -26,6 +26,7 @@ type ToastVariants = | "notification"; type AlignOptions = "left" | "center" | "right"; +type AlignYOptions = "top" | "center" | "bottom"; interface IconTypes { notification: "alert"; @@ -38,8 +39,10 @@ interface IconTypes { } export interface ToastProps { - /** Sets the alignment of the component. */ + /** Sets the horizontal alignment of the component. */ align?: AlignOptions; + /** Sets the vertical alignment of the component */ + alignY?: AlignYOptions; /** The rendered children of the component. */ children: React.ReactNode; /** Customizes the appearance in the DLS theme */ @@ -77,6 +80,7 @@ export const Toast = React.forwardRef( ( { align, + alignY, children, className, id, @@ -208,6 +212,7 @@ export const Toast = React.forwardRef( > ( diff --git a/src/components/toast/toast.spec.tsx b/src/components/toast/toast.spec.tsx index f555c48de1..d9dc586877 100644 --- a/src/components/toast/toast.spec.tsx +++ b/src/components/toast/toast.spec.tsx @@ -571,7 +571,7 @@ describe("TestContentStyle", () => { }); }); -describe("Align", () => { +describe("Align horizontal", () => { let wrapper: ReactWrapper; afterEach(() => { @@ -593,6 +593,27 @@ describe("Align", () => { ); }); +describe("Align vertical", () => { + let wrapper: ReactWrapper; + + afterEach(() => { + wrapper.unmount(); + }); + + it.each(["top", "center", "bottom"] as const)( + "should then pass the prop to Styled Portal", + (alignYValue) => { + wrapper = mount( + + FooBar + + ); + + expect(wrapper.find(StyledPortal).props().alignY).toBe(alignYValue); + } + ); +}); + describe("Notification variant", () => { let wrapper: ReactWrapper; diff --git a/src/components/toast/toast.stories.mdx b/src/components/toast/toast.stories.mdx index 1a14ee5376..95e0cdde0d 100644 --- a/src/components/toast/toast.stories.mdx +++ b/src/components/toast/toast.stories.mdx @@ -76,22 +76,40 @@ Toast variant is `success` by default -### Align - Left +### Horizontal Align - Left - + -### Align - Center +### Horizontal Align - Center - + -### Align - Right +### Horizontal Align - Right - + + + +### Vertical Align - Top + + + + + +### Vertical Align - Center + + + + + +### Vertical Align - Bottom + + + ### Info @@ -121,7 +139,8 @@ Toast variant is `success` by default ### Notice When the "notice" variant is set, the Toast component is rendered at the bottom of the screen with alternative styling and animation. -The "isCenter" and "maxWidth" props will be ignored in this variant. +The "isCenter" and "maxWidth" props will be ignored in this variant. +To render it at the top of the screen use the "alignY" prop set to "top". diff --git a/src/components/toast/toast.stories.tsx b/src/components/toast/toast.stories.tsx index 8b96d85702..c3cba5263b 100644 --- a/src/components/toast/toast.stories.tsx +++ b/src/components/toast/toast.stories.tsx @@ -354,6 +354,111 @@ export const AlignedRight = () => { ); }; +export const AlignedYTop = () => { + const [isOpen, setIsOpen] = useState(false); + const onDismissClick = () => { + setIsOpen(!isOpen); + }; + const handleToggle = () => { + if (!isOpen) { + window.scrollTo(0, 0); + } + setIsOpen(!isOpen); + }; + + return ( +
+ + Toggle - Preview is: {isOpen ? "ON" : "OFF"} + + + My text + +
+ ); +}; + +export const AlignedYCenter = () => { + const [isOpen, setIsOpen] = useState(false); + const onDismissClick = () => { + setIsOpen(!isOpen); + }; + const handleToggle = () => { + if (!isOpen) { + window.scrollTo(0, 0); + } + setIsOpen(!isOpen); + }; + + return ( +
+ + Toggle - Preview is: {isOpen ? "ON" : "OFF"} + + + My text + +
+ ); +}; + +export const AlignedYBottom = () => { + const [isOpen, setIsOpen] = useState(false); + const onDismissClick = () => { + setIsOpen(!isOpen); + }; + const handleToggle = () => { + if (!isOpen) { + window.scrollTo(0, 0); + } + setIsOpen(!isOpen); + }; + + return ( +
+ + Toggle - Preview is: {isOpen ? "ON" : "OFF"} + + + My text + +
+ ); +}; + export const CustomMaxWidth = () => { const [isOpen, setIsOpen] = useState(false); const onDismissClick = () => { diff --git a/src/components/toast/toast.style.ts b/src/components/toast/toast.style.ts index 493041a681..d6ce9f9ed3 100644 --- a/src/components/toast/toast.style.ts +++ b/src/components/toast/toast.style.ts @@ -10,10 +10,11 @@ import StyledIcon from "../icon/icon.style"; const StyledPortal = styled(Portal)<{ align?: "left" | "center" | "right"; + alignY?: "top" | "center" | "bottom"; isCenter?: boolean; isNotice?: boolean; }>` - ${({ theme, isCenter, isNotice, align }) => css` + ${({ theme, isCenter, isNotice, align, alignY }) => css` position: fixed; top: 0; @@ -27,8 +28,9 @@ const StyledPortal = styled(Portal)<{ ${align === "left" && css` - left: 12%; - transform: translateX(-50%); + display: flex; + left: 0; + transform: translateX(50%); `} ${align === "center" && @@ -49,6 +51,26 @@ const StyledPortal = styled(Portal)<{ bottom: 0; top: auto; width: 100%; + `} + + ${alignY === "top" && + css` + top: 0; + bottom: auto; + `} + + ${alignY === "center" && + css` + top: 50%; + transform: translate(${align === "left" ? "50%" : "-50%"}, -50%); + `} + + ${alignY === "bottom" && + css` + bottom: 0; + top: auto; + display: flex; + flex-direction: column-reverse; `} `} `; @@ -61,19 +83,24 @@ const animationName = ".toast"; const alternativeAnimationName = ".toast-alternative"; const ToastStyle = styled(MessageStyle)<{ align?: "left" | "center" | "right"; + alignY?: "top" | "center" | "bottom"; maxWidth?: string; isCenter?: boolean; isNotice?: boolean; isNotification?: boolean; }>` - ${({ maxWidth, isCenter, align, isNotification }) => css` + ${({ 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); line-height: 22px; - margin-top: 30px; + margin-top: ${(alignY === "top" && isNotice) || alignY === "center" + ? "0" + : "30px"}; + margin-bottom: ${alignY === "bottom" && !isNotice ? "30px" : "0"}; max-width: ${!maxWidth ? "300px" : maxWidth}; position: relative; margin-right: ${isCenter || align === "right" ? "auto" : "30px"}; + margin-left: ${isCenter || align === "left" ? "auto" : "30px"}; ${isNotification && css` @@ -100,7 +127,9 @@ const ToastStyle = styled(MessageStyle)<{ &${animationName}-exit${animationName}-exit-active { opacity: 0; - margin-top: -40px; + + ${({ alignY }) => + alignY === "bottom" ? "margin-bottom: -40px;" : "margin-top: -40px;"}; transition: all 150ms ease-out; } @@ -111,7 +140,7 @@ const ToastStyle = styled(MessageStyle)<{ transform: translateY(-50%); } - ${({ isNotice }) => + ${({ isNotice, alignY }) => isNotice && css` background-color: var(--colorsUtilityMajor400); @@ -129,24 +158,24 @@ const ToastStyle = styled(MessageStyle)<{ } &${alternativeAnimationName}-appear, &${alternativeAnimationName}-enter { - bottom: -40px; + ${alignY === "top" ? "top: -40px;" : "bottom: -40px;"}; opacity: 0; } &${alternativeAnimationName}-exit { - bottom: 0; + ${alignY === "top" ? "top: 0;" : "bottom: 0;"}; opacity: 1; } &${alternativeAnimationName}-appear${alternativeAnimationName}-appear-active, &${alternativeAnimationName}-enter${alternativeAnimationName}-enter-active { - bottom: 0; + ${alignY === "top" ? "top: 0;" : "bottom: 0;"}; opacity: 1; transition: all 400ms ease; } &${alternativeAnimationName}-exit${alternativeAnimationName}-exit-active { - bottom: -40px; + ${alignY === "top" ? "top: -40px;" : "bottom: -40px;"}; opacity: 0; transition: all 200ms ease; }