diff --git a/packages/orbit-components/src/Modal/ModalFooter/__tests__/index.test.tsx b/packages/orbit-components/src/Modal/ModalFooter/__tests__/index.test.tsx index 305537d2cb..6736606a80 100644 --- a/packages/orbit-components/src/Modal/ModalFooter/__tests__/index.test.tsx +++ b/packages/orbit-components/src/Modal/ModalFooter/__tests__/index.test.tsx @@ -3,23 +3,8 @@ import React from "react"; import { render, screen } from "../../../test-utils"; import ModalFooter from ".."; import Button from "../../../Button"; -import { getBreakpointWidth } from "../../../utils/mediaQuery"; -import theme from "../../../defaultTheme"; describe("ModalFooter", () => { - it("should render save button with flex-end", () => { - render( - - {null} - - , - ); - - expect(screen.getByTestId("footer")).toHaveStyleRule("justify-content", "flex-end", { - media: getBreakpointWidth("largeMobile", theme), - }); - }); - it("should not have StyledChild wrapper on single children", () => { render( @@ -38,10 +23,6 @@ describe("ModalFooter", () => { , ); - expect(screen.getByTestId("footer")).toHaveStyleRule("justify-content", "space-between", { - media: getBreakpointWidth("largeMobile", theme), - }); - expect(screen.getAllByTestId("footer-el-wrapper")).toHaveLength(2); }); }); diff --git a/packages/orbit-components/src/Modal/ModalFooter/index.js.flow b/packages/orbit-components/src/Modal/ModalFooter/index.js.flow index 219bf90f5e..5ee29976e7 100644 --- a/packages/orbit-components/src/Modal/ModalFooter/index.js.flow +++ b/packages/orbit-components/src/Modal/ModalFooter/index.js.flow @@ -1,6 +1,5 @@ // @flow import * as React from "react"; -import type { StyledComponent } from "styled-components"; import type { Globals } from "../../common/common.js.flow"; @@ -11,5 +10,3 @@ export type Props = {| |}; declare export default React.ComponentType; - -declare export var StyledModalFooter: StyledComponent; diff --git a/packages/orbit-components/src/Modal/ModalFooter/index.tsx b/packages/orbit-components/src/Modal/ModalFooter/index.tsx index feac45eb0a..f8878e71cb 100644 --- a/packages/orbit-components/src/Modal/ModalFooter/index.tsx +++ b/packages/orbit-components/src/Modal/ModalFooter/index.tsx @@ -1,77 +1,31 @@ "use client"; import * as React from "react"; -import styled, { css } from "styled-components"; +import cx from "clsx"; -import transition from "../../utils/transition"; -import media, { getBreakpointWidth } from "../../utils/mediaQuery"; -import defaultTheme from "../../defaultTheme"; -import { rtlSpacing } from "../../utils/rtl"; import { ModalContext } from "../ModalContext"; -import { QUERIES } from "../../utils/mediaQuery/consts"; import useModalContextFunctions from "../helpers/useModalContextFunctions"; import type { Props } from "./types"; -const StyledChild = styled.div<{ flex?: Props["flex"] }>` - ${({ theme, flex }) => css` - flex: ${flex}; - box-sizing: border-box; - padding: ${rtlSpacing(`0 ${theme.orbit.spaceXSmall} 0 0`)}; - `}; -`; +const getChildFlex = (flex: Props["flex"], key: number) => { + if (Array.isArray(flex)) { + return flex.length !== 1 ? flex[key] || flex[0] : flex[0]; + } -StyledChild.defaultProps = { - theme: defaultTheme, + return flex; }; -export const StyledModalFooter = styled.div<{ - isMobileFullPage?: boolean; - childrenLength: number; -}>` - ${({ theme, childrenLength, isMobileFullPage }) => css` - display: flex; - z-index: 800; - width: 100%; - background-color: ${theme.orbit.paletteWhite}; - padding: ${rtlSpacing(`0 ${theme.orbit.spaceMedium} ${theme.orbit.spaceMedium}`)}; - box-sizing: border-box; - transition: ${transition(["box-shadow"], "fast", "ease-in-out")}; - @media (max-width: ${+getBreakpointWidth(QUERIES.LARGEMOBILE, theme, true) - 1}px) { - .orbit-button-primitive { - font-size: ${theme.orbit.fontSizeButtonNormal}; - height: ${theme.orbit.heightButtonNormal}; - } - } - - ${media.largeMobile(css` - justify-content: ${childrenLength > 1 ? "space-between" : "flex-end"}; - ${!isMobileFullPage && - css` - border-bottom-left-radius: 9px; - border-bottom-right-radius: 9px; - `}; - `)}; - - ${StyledChild}:last-of-type { - padding: 0; - } - `} -`; - -StyledModalFooter.defaultProps = { - theme: defaultTheme, -}; - -const getChildFlex = (flex: Props["flex"], key: number) => - Array.isArray(flex) && flex.length !== 1 ? flex[key] || flex[0] : flex; - const wrappedChildren = (children: React.ReactNode, flex: Props["flex"]) => { if (!Array.isArray(children)) return children; return React.Children.map(children, (child, key) => { if (!React.isValidElement(child)) return null; return ( - +
{React.cloneElement(child, { // @ts-expect-error React.cloneElement issue ref: child.ref @@ -87,7 +41,7 @@ const wrappedChildren = (children: React.ReactNode, flex: Props["flex"]) => { } : null, })} - +
); }); }; @@ -95,6 +49,7 @@ const wrappedChildren = (children: React.ReactNode, flex: Props["flex"]) => { const ModalFooter = ({ dataTest, children, flex = "0 1 auto" }: Props) => { const { isMobileFullPage, setFooterHeight } = React.useContext(ModalContext); const containerRef = React.useRef(null); + const childrenLength = React.Children.toArray(children).length; useModalContextFunctions(); @@ -112,14 +67,21 @@ const ModalFooter = ({ dataTest, children, flex = "0 1 auto" }: Props) => { }, [setFooterHeight]); return ( - 1 ? "lm:justify-between" : "lm:justify-end", + !isMobileFullPage && "lm:rounded-b-modal", + "[&_.orbit-modal-footer-child:last-of-type]:p-0", + )} ref={containerRef} data-test={dataTest} - isMobileFullPage={isMobileFullPage} - childrenLength={React.Children.toArray(children).length} > {flex && wrappedChildren(children, flex)} - + ); }; diff --git a/packages/orbit-components/src/Modal/ModalHeader/index.js.flow b/packages/orbit-components/src/Modal/ModalHeader/index.js.flow index 7f840a1933..4147138332 100644 --- a/packages/orbit-components/src/Modal/ModalHeader/index.js.flow +++ b/packages/orbit-components/src/Modal/ModalHeader/index.js.flow @@ -1,6 +1,5 @@ // @flow import * as React from "react"; -import type { StyledComponent } from "styled-components"; import type { Globals } from "../../common/common.js.flow"; @@ -15,9 +14,3 @@ export type Props = {| |}; declare export default React.ComponentType; - -declare export var MobileHeader: StyledComponent; - -declare export var StyledModalHeader: StyledComponent; - -declare export var ModalHeading: StyledComponent; diff --git a/packages/orbit-components/src/Modal/ModalHeader/index.tsx b/packages/orbit-components/src/Modal/ModalHeader/index.tsx index 7f939ff894..09cfbf9703 100644 --- a/packages/orbit-components/src/Modal/ModalHeader/index.tsx +++ b/packages/orbit-components/src/Modal/ModalHeader/index.tsx @@ -1,167 +1,48 @@ "use client"; import * as React from "react"; -import styled, { css } from "styled-components"; +import cx from "clsx"; -import transition from "../../utils/transition"; import Text from "../../Text"; -import defaultTheme from "../../defaultTheme"; -import media from "../../utils/mediaQuery"; -import { StyledModalSection } from "../ModalSection"; -import { left, right, rtlSpacing } from "../../utils/rtl"; import { ModalContext } from "../ModalContext"; import useModalContextFunctions from "../helpers/useModalContextFunctions"; import type { Props } from "./types"; -export const ModalHeading = styled.h2` - ${({ theme }) => css` - margin: 0; - font-size: ${theme.orbit.fontSizeHeadingTitle2}; - font-weight: ${theme.orbit.fontWeightHeadingTitle2}; - line-height: ${theme.orbit.lineHeightHeadingTitle2}; - color: ${theme.orbit.colorHeading}; - ${media.largeMobile(css` - font-size: ${theme.orbit.fontSizeHeadingTitle1}; - font-weight: ${theme.orbit.fontWeightHeadingTitle1}; - line-height: ${theme.orbit.lineHeightHeadingTitle1}; - `)}; - `} -`; - -ModalHeading.defaultProps = { - theme: defaultTheme, -}; - -// TODO: create token marginModalTitle and marginModalTitleWithIllustration -const ModalTitle = styled.div<{ illustration?: boolean }>` - ${({ theme, illustration }) => css` - margin-top: ${illustration && theme.orbit.spaceMedium}; - - ${ModalHeading} { - padding-${right}: ${theme.orbit.spaceXLarge}; - } - - ${media.desktop(css` - ${ModalHeading} { - padding: 0; - } - `)}; - `} -`; - -ModalTitle.defaultProps = { - theme: defaultTheme, -}; - -const ModalDescription = styled.div` - margin-top: ${({ theme }) => theme.orbit.spaceXSmall}; -`; - -ModalDescription.defaultProps = { - theme: defaultTheme, -}; - -const getModalHeaderPadding = - (desktop = false) => - ({ theme, suppressed }) => { - if (desktop) { - if (suppressed) { - return theme.orbit.spaceXLarge; - } - return `${theme.orbit.spaceXLarge} ${theme.orbit.spaceXLarge} 0 ${theme.orbit.spaceXLarge}`; - } - if (suppressed) { - return `${theme.orbit.spaceXLarge} ${theme.orbit.spaceMedium}`; - } - return `${theme.orbit.spaceLarge} ${theme.orbit.spaceMedium} 0 ${theme.orbit.spaceMedium}`; - }; - -export const StyledModalHeader = styled.div<{ - suppressed?: Props["suppressed"]; +export const ModalHeaderWrapper = ({ + className, + suppressed, + isMobileFullPage, + dataTest, + children, +}: { + className?: string; + suppressed?: boolean; isMobileFullPage?: boolean; - illustration?: boolean; -}>` - ${({ theme, suppressed, isMobileFullPage }) => css` - width: 100%; - display: block; - padding: ${rtlSpacing(getModalHeaderPadding()({ theme, suppressed }))}; - border-top-left-radius: ${!isMobileFullPage && "12px"}; - border-top-right-radius: ${!isMobileFullPage && "12px"}; - box-sizing: border-box; - background-color: ${suppressed ? theme.orbit.paletteCloudLight : theme.orbit.paletteWhite}; - - & ~ ${StyledModalSection}:first-of-type { - border-top: ${suppressed && `1px solid ${theme.orbit.paletteCloudNormal}`}; - border-top-left-radius: 0; - border-top-right-radius: 0; - margin-top: ${suppressed && "0!important"}; - } - - ${media.largeMobile(css` - padding: ${rtlSpacing(getModalHeaderPadding(true)({ theme, suppressed }))}; - - & ~ ${StyledModalSection}:first-of-type { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - `)}; - `} -`; - -StyledModalHeader.defaultProps = { - theme: defaultTheme, -}; - -export const MobileHeader = styled.div<{ isMobileFullPage?: boolean }>` - ${({ theme, isMobileFullPage }) => css` - display: inline-block; - position: fixed; - visibility: hidden; - height: 52px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - top: ${isMobileFullPage ? "0" : "16px"}; - ${right}: 48px; - ${left}: 0; - font-family: ${theme.orbit.fontFamily}; - font-weight: ${theme.orbit.fontWeightHeadingDisplay}; - font-size: 18px; - color: ${theme.orbit.colorHeading}; - line-height: 52px; - box-sizing: border-box; - padding: ${rtlSpacing(`0 0 0 ${theme.orbit.spaceLarge}`)}; - opacity: 0; - transition: ${transition(["top", "opacity", "visibility"], "fast", "ease-in-out")}; - z-index: 800; - - ${media.largeMobile(css` - left: auto; - right: auto; - padding: 0; - `)}; - `} -`; - -MobileHeader.defaultProps = { - theme: defaultTheme, -}; - -const getModalHeaderContentMargin = ({ hasHeader, hasDescription, hasChildren }) => { - if (!hasHeader && hasChildren) return "0"; - - return hasDescription ? "32px" : "16px"; + dataTest?: string; + children?: React.ReactNode; +}) => { + return ( +
+ {children} +
+ ); }; -const StyledModalHeaderContent = styled.div<{ - hasHeader: boolean; - hasDescription: boolean; - hasChildren: boolean; -}>` - margin-top: ${({ hasHeader, hasDescription, hasChildren }) => - getModalHeaderContentMargin({ hasHeader, hasDescription, hasChildren })}; -`; - const ModalHeader = ({ illustration, suppressed, @@ -182,43 +63,59 @@ const ModalHeader = ({ }; }, [title, setHasModalTitle]); - const hasHeader = title || description; + const hasHeader = Boolean(title || description); return ( - {illustration} {hasHeader && ( - - {title && {title}} +
+ {title && ( +

+ {title} +

+ )} {description && ( - +
{description} - +
)} - +
)} {children && ( - +
{children} - +
)} {title && hasMobileHeader && ( - +
{title} - +
)} -
+ ); }; diff --git a/packages/orbit-components/src/Modal/ModalSection/index.js.flow b/packages/orbit-components/src/Modal/ModalSection/index.js.flow index 34e46a4076..2ae73f624a 100644 --- a/packages/orbit-components/src/Modal/ModalSection/index.js.flow +++ b/packages/orbit-components/src/Modal/ModalSection/index.js.flow @@ -1,6 +1,5 @@ // @flow import * as React from "react"; -import type { StyledComponent } from "styled-components"; import type { Globals } from "../../common/common.js.flow"; @@ -11,5 +10,3 @@ export type Props = {| |}; declare export default React.ComponentType; - -declare export var StyledModalSection: StyledComponent; diff --git a/packages/orbit-components/src/Modal/ModalSection/index.tsx b/packages/orbit-components/src/Modal/ModalSection/index.tsx index 25ad7f1908..067ce4d1ab 100644 --- a/packages/orbit-components/src/Modal/ModalSection/index.tsx +++ b/packages/orbit-components/src/Modal/ModalSection/index.tsx @@ -1,70 +1,56 @@ "use client"; import * as React from "react"; -import styled, { css } from "styled-components"; +import cx from "clsx"; -import defaultTheme from "../../defaultTheme"; -import media from "../../utils/mediaQuery"; -import { StyledModalFooter } from "../ModalFooter"; import { ModalContext } from "../ModalContext"; import useModalContextFunctions from "../helpers/useModalContextFunctions"; import type { Props } from "./types"; -export const StyledModalSection = styled.section<{ +export const ModalSectionWrapper = ({ + className, + children, + suppressed, + closable, + isMobileFullPage, + dataTest, +}: { + className?: string; + children: React.ReactNode; suppressed?: boolean; closable?: boolean; isMobileFullPage?: boolean; -}>` - ${({ theme, suppressed, closable, isMobileFullPage }) => css` - width: 100%; - padding: ${`${theme.orbit.spaceLarge} ${theme.orbit.spaceMedium}`}; - background-color: ${suppressed ? theme.orbit.paletteCloudLight : theme.orbit.paletteWhite}; - border-bottom: 1px solid ${theme.orbit.paletteCloudNormal}; - box-sizing: border-box; - - &:first-of-type { - border-top: ${suppressed && `1px solid ${theme.orbit.paletteCloudNormal}`}; - border-top-left-radius: ${!isMobileFullPage && "12px"}; - border-top-right-radius: ${!isMobileFullPage && "12px"}; - margin-top: ${suppressed && closable && theme.orbit.spaceLarge}; - } - - &:last-of-type { - border-bottom: ${suppressed ? `1px solid ${theme.orbit.paletteCloudNormal}` : "0"}; - border-bottom-left-radius: ${!isMobileFullPage && "12px"}; - border-bottom-right-radius: ${!isMobileFullPage && "12px"}; - & ~ ${StyledModalFooter} { - margin-top: ${suppressed && theme.orbit.spaceMedium}; - } - &:not(:last-child) { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - } - - ${media.largeMobile(css` - padding: ${theme.orbit.spaceXLarge}; - - &:first-of-type { - margin-top: ${((suppressed && closable) || suppressed) && theme.orbit.spaceXXLarge}; - border-top-left-radius: ${!isMobileFullPage && "9px"}; - border-top-right-radius: ${!isMobileFullPage && "9px"}; - } - - &:last-of-type { - border-bottom-left-radius: ${!isMobileFullPage && "9px"}; - border-bottom-right-radius: ${!isMobileFullPage && "9px"}; - & ~ ${StyledModalFooter} { - padding-top: ${!suppressed && "0"}; - margin-top: 0; - } - } - `)}; - `} -`; - -StyledModalSection.defaultProps = { - theme: defaultTheme, + dataTest?: string; +}) => { + return ( +
+ {children} +
+ ); }; const ModalSection = ({ children, suppressed, dataTest }: Props) => { @@ -87,14 +73,14 @@ const ModalSection = ({ children, suppressed, dataTest }: Props) => { }, [removeHasModalSection]); return ( - {children} - + ); }; diff --git a/packages/orbit-components/src/Modal/__tests__/index.test.tsx b/packages/orbit-components/src/Modal/__tests__/index.test.tsx index 4b6d75ddf1..2aab56d2bd 100644 --- a/packages/orbit-components/src/Modal/__tests__/index.test.tsx +++ b/packages/orbit-components/src/Modal/__tests__/index.test.tsx @@ -17,8 +17,6 @@ jest.mock("../../hooks/useMediaQuery", () => { const mockUseMediaQuery = useMediaQuery; describe("Modal", () => { - const user = userEvent.setup(); - it("should have expected DOM output", () => { render( @@ -52,6 +50,7 @@ describe("Modal", () => { }); it("should be able to focus content", async () => { + const user = userEvent.setup(); jest.useFakeTimers(); render( @@ -127,7 +126,10 @@ describe("Modal", () => { }); it("should call callback function when clicking on close button", async () => { + // Needed the pointerEventsCheck to be 0 because of JestDOM not being able to detect pointer events definition on ModalCloseButton + const user = userEvent.setup({ pointerEventsCheck: 0 }); const onClose = jest.fn(); + render( content diff --git a/packages/orbit-components/src/Modal/index.tsx b/packages/orbit-components/src/Modal/index.tsx index 277ebbcac0..d984c7184d 100644 --- a/packages/orbit-components/src/Modal/index.tsx +++ b/packages/orbit-components/src/Modal/index.tsx @@ -1,296 +1,42 @@ "use client"; import * as React from "react"; -import styled, { css } from "styled-components"; +import cx from "clsx"; import { ModalContext } from "./ModalContext"; -import { MobileHeader, StyledModalHeader, ModalHeading } from "./ModalHeader"; -import { StyledModalFooter } from "./ModalFooter"; -import { StyledModalSection } from "./ModalSection"; import ModalCloseButton from "./ModalCloseButton"; import { SIZES, CLOSE_BUTTON_DATA_TEST } from "./consts"; import KEY_CODE_MAP from "../common/keyMaps"; -import defaultTheme from "../defaultTheme"; -import media from "../utils/mediaQuery"; -import { right } from "../utils/rtl"; -import transition from "../utils/transition"; import useRandomId from "../hooks/useRandomId"; import useMediaQuery from "../hooks/useMediaQuery"; import FOCUSABLE_ELEMENT_SELECTORS from "../hooks/useFocusTrap/consts"; import usePrevious from "../hooks/usePrevious"; import useLockScrolling from "../hooks/useLockScrolling"; import type { Instance, Props } from "./types"; - -const getSizeToken = ({ size, theme }: { size: Props["size"]; theme: typeof defaultTheme }) => { - const tokens = { - [SIZES.EXTRASMALL]: "360px", - [SIZES.SMALL]: theme.orbit.widthModalSmall, - [SIZES.NORMAL]: theme.orbit.widthModalNormal, - [SIZES.LARGE]: theme.orbit.widthModalLarge, - [SIZES.EXTRALARGE]: theme.orbit.widthModalExtraLarge, - }; - - if (!size) return null; - - return tokens[size]; -}; - -const ModalBody = styled.div<{ autoFocus?: boolean; isMobileFullPage: Props["isMobileFullPage"] }>` - ${({ theme, isMobileFullPage }) => css` - width: 100%; - height: 100%; - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: ${theme.orbit.zIndexModalOverlay}; - box-sizing: border-box; - outline: none; - overflow-x: hidden; - background-color: ${!isMobileFullPage && "rgba(0, 0, 0, 0.5)"}; - font-family: ${theme.orbit.fontFamily}; - -webkit-overflow-scrolling: auto; - ${media.largeMobile(css` - overflow-y: auto; - padding: ${theme.orbit.spaceXXLarge}; - background-color: rgba(0, 0, 0, 0.5); - `)}; - `} -`; - -ModalBody.defaultProps = { - theme: defaultTheme, -}; - -const ModalWrapper = styled.div<{ - isMobileFullPage: Props["isMobileFullPage"]; - disableAnimation?: boolean; - loaded: boolean; - fixedFooter: Props["fixedFooter"]; - size: Props["size"]; -}>` - ${({ isMobileFullPage, disableAnimation, loaded }) => css` - box-sizing: border-box; - min-height: 100%; - display: flex; - align-items: flex-start; - margin: 0 auto; - position: fixed; - width: 100%; - border-top-left-radius: ${!isMobileFullPage && "12px"}; - border-top-right-radius: ${!isMobileFullPage && "12px"}; - ${disableAnimation - ? css` - top: ${!isMobileFullPage && "32px"}; - ` - : css` - transition: ${transition(["top"], "normal", "ease-in-out")}; - top: ${loaded ? !isMobileFullPage && "32px" : "100%"}; - `} - - ${media.largeMobile(css` - position: relative; - top: 0; - max-width: ${getSizeToken}; - align-items: center; - `)}; - `} -`; - -ModalWrapper.defaultProps = { - theme: defaultTheme, -}; - -const CloseContainer = styled.div<{ - scrolled?: boolean; - fixedClose?: boolean; - size?: Props["size"]; - isMobileFullPage?: Props["isMobileFullPage"]; - modalWidth?: number; -}>` - ${({ theme, scrolled, fixedClose, isMobileFullPage, modalWidth, size }) => css` - display: flex; - ${ - fixedClose || scrolled - ? css` - position: fixed; - ` - : css` - position: absolute; - ` - }; - position: ${fixedClose || scrolled ? "fixed" : "absolute"}; - top: ${!isMobileFullPage && (fixedClose || scrolled) ? "32px" : "0"}; - right: 0; - z-index: 800; - justify-content: flex-end; - align-items: center; - box-sizing: border-box; - height: 52px; - width: 100%; - max-width: ${modalWidth ? `${modalWidth}px` : getSizeToken({ size, theme })}; - box-shadow: ${scrolled && theme.orbit.boxShadowFixed}; - background-color: ${scrolled && theme.orbit.paletteWhite}; - border-top-left-radius: ${!isMobileFullPage && "12px"}; - border-top-right-radius: ${!isMobileFullPage && "12px"}; - transition: ${transition(["box-shadow", "background-color"], "fast", "ease-in-out")}; - pointer-events: none; - - - ${media.largeMobile(css` - top: ${(fixedClose || scrolled) && "0"}; - right: ${(fixedClose || scrolled) && "auto"}; - border-radius: 0; - `)}; - - & + ${StyledModalSection}:first-of-type { - padding-top: 52px; - border-top: 0; - margin: 0; - } - - .orbit-button-primitive { - pointer-events: auto; - margin-${right}: ${theme.orbit.spaceXXSmall}; - - & svg { - transition: ${transition(["color"], "fast", "ease-in-out")}; - color: ${theme.orbit.paletteInkNormal}; - } - - &:hover svg { - color: ${theme.orbit.paletteInkLightHover}; - } - - &:active svg { - color: ${theme.orbit.paletteInkLightActive}; - } - } -`} -`; - -CloseContainer.defaultProps = { - theme: defaultTheme, -}; - -export const ModalWrapperContent = styled.div<{ - isMobileFullPage: Props["isMobileFullPage"]; - fixedFooter: Props["fixedFooter"]; - footerHeight: number; - fullyScrolled?: boolean; - scrolled?: boolean; - fixedClose?: boolean; - size: Props["size"]; - modalWidth: number; - hasModalSection?: boolean; -}>` - ${({ - theme, - isMobileFullPage, - fixedFooter, - footerHeight, - size, - fullyScrolled, - scrolled, - modalWidth, - hasModalSection, - }) => css` - position: absolute; - box-sizing: border-box; - border-top-left-radius: ${!isMobileFullPage && "12px"}; - border-top-right-radius: ${!isMobileFullPage && "12px"}; - background-color: ${theme.orbit.backgroundModal}; - font-family: ${theme.orbit.fontFamily}; - width: 100%; - ${isMobileFullPage - ? css` - max-height: 100%; - top: 0; - ` - : css` - max-height: calc( - 100% - ${theme.orbit.spaceXLarge} - - ${`${fixedFooter && Boolean(footerHeight) ? footerHeight : 0}px`} - ); - `}; - bottom: ${`${ - (!isMobileFullPage ? parseInt(theme.orbit.spaceXLarge, 10) : 0) + - (fixedFooter && Boolean(footerHeight) ? footerHeight : 0) - }px`}; - box-shadow: ${theme.orbit.boxShadowOverlay}; - overflow-y: auto; - overflow-x: hidden; - - ${fixedFooter && - footerHeight && - css` - ${StyledModalFooter} { - bottom: 0; - padding: ${theme.orbit.spaceMedium}; - box-shadow: ${fullyScrolled - ? `inset 0 1px 0 ${theme.orbit.paletteCloudNormal}, ${theme.orbit.boxShadowFixedReverse}` - : `inset 0 0 0 transparent, ${theme.orbit.boxShadowFixedReverse}`}; - position: fixed; - transition: ${transition(["box-shadow"], "fast", "ease-in-out")}; - } - ${StyledModalSection}:last-of-type { - padding-bottom: ${theme.orbit.spaceLarge}; - margin-bottom: 0; - } - `}; - - ${MobileHeader} { - top: ${!isMobileFullPage && scrolled && theme.orbit.spaceXLarge}; - opacity: ${scrolled && "1"}; - visibility: ${scrolled && "visible"}; - transition: ${scrolled && - css` - ${transition(["top"], "normal", "ease-in-out")}, - ${transition(["opacity", "visibility"], "fast", "ease-in-out")} - `}; - } - - ${StyledModalHeader} { - margin-bottom: ${!hasModalSection && theme.orbit.spaceXLarge}; - } - - ${media.largeMobile(css` - position: relative; - bottom: auto; - border-radius: ${!isMobileFullPage && "9px"}; - padding-bottom: 0; - height: auto; - overflow: visible; - max-height: 100%; - ${StyledModalSection}:last-of-type { - padding-bottom: ${theme.orbit.spaceXXLarge}; - margin-bottom: ${fixedFooter ? `${footerHeight}px` : "0"}; - &::after { - content: none; - } - } - ${StyledModalHeader} { - margin-bottom: ${!hasModalSection && fixedFooter ? `${footerHeight}px` : "0"}; - } - ${StyledModalFooter} { - padding: ${fixedFooter - ? `${theme.orbit.spaceXLarge} ${theme.orbit.spaceXLarge}!important` - : theme.orbit.spaceXLarge}; - max-width: ${modalWidth ? `${modalWidth}px` : getSizeToken({ size, theme })}; - position: ${fixedFooter && fullyScrolled && "absolute"}; - box-shadow: ${fullyScrolled && "none"}; - } - ${MobileHeader} { - top: ${scrolled ? "0" : `-${theme.orbit.spaceXXLarge}`}; - width: ${`calc(${modalWidth}px - 48px - ${theme.orbit.spaceXXLarge})`}; - } - `)}; - `} -`; - -ModalWrapperContent.defaultProps = { - theme: defaultTheme, +import useTheme from "../hooks/useTheme"; + +const maxWidthClasses: { + [K in SIZES | "largeMobile" | "footer"]: K extends SIZES ? string : Record; +} = { + [SIZES.EXTRASMALL]: "max-w-modal-extra-small", + [SIZES.SMALL]: "max-w-modal-small", + [SIZES.NORMAL]: "max-w-modal-normal", + [SIZES.LARGE]: "max-w-modal-large", + [SIZES.EXTRALARGE]: "max-w-modal-extra-large", + largeMobile: { + [SIZES.EXTRASMALL]: "lm:max-w-modal-extra-small", + [SIZES.SMALL]: "lm:max-w-modal-small", + [SIZES.NORMAL]: "lm:max-w-modal-normal", + [SIZES.LARGE]: "lm:max-w-modal-large", + [SIZES.EXTRALARGE]: "lm:max-w-modal-extra-large", + }, + footer: { + [SIZES.EXTRASMALL]: "lm:[&_.orbit-modal-footer]:max-w-modal-extra-small", + [SIZES.SMALL]: "lm:[&_.orbit-modal-footer]:max-w-modal-small", + [SIZES.NORMAL]: "lm:[&_.orbit-modal-footer]:max-w-modal-normal", + [SIZES.LARGE]: "lm:[&_.orbit-modal-footer]:max-w-modal-large", + [SIZES.EXTRALARGE]: "lm:[&_.orbit-modal-footer]:max-w-modal-extra-large", + }, }; const OFFSET = 40; @@ -333,6 +79,7 @@ const Modal = React.forwardRef( const modalContent = React.useRef(null); const modalBody = React.useRef(null); const modalTitleID = useRandomId(); + const theme = useTheme(); const { isLargeMobile } = useMediaQuery(); const scrollingElement = React.useRef(null); @@ -376,8 +123,7 @@ const Modal = React.forwardRef( if (!content) return; - // added in 4.0.3, interpolation of styled component return static className - const footerEl = content.querySelector(`${StyledModalFooter}`); + const footerEl = content.querySelector(".orbit-modal-footer"); const contentDimensions = content.getBoundingClientRect(); setModalWidth(contentDimensions.width); @@ -458,7 +204,8 @@ const Modal = React.forwardRef( modalContent.current && event.target instanceof Element && !modalContent.current.contains(event.target) && - /ModalBody|ModalWrapper/.test(event.target.className); + (event.target.className.includes("orbit-modal-wrapper") || + event.target.className.includes("orbit-modal-body")); if (clickedOutside && onClose) onClose(event); @@ -487,8 +234,7 @@ const Modal = React.forwardRef( ? contentHeight + 80 : target.scrollHeight; - // @ts-expect-error TODO - setScrolled(target.scrollTop >= scrollBegin + (!mobile ? target.scrollTop : 0)); + setScrolled(target.scrollTop >= Number(scrollBegin) + (!mobile ? target.scrollTop : 0)); setFixedClose(target.scrollTop >= fixCloseOffset); // set fullyScrolled state sooner than the exact end of the scroll (with fullScrollOffset value) setFullyScrolled( @@ -501,7 +247,7 @@ const Modal = React.forwardRef( if (!content) return null; - const headingEl = content.querySelector(`${ModalHeading}`); + const headingEl = content.querySelector(".orbit-modal-heading"); if (headingEl) { const { top } = headingEl.getBoundingClientRect(); @@ -629,8 +375,35 @@ const Modal = React.forwardRef( ], ); + const cssVars = { + ...(!isLargeMobile + ? { + maxHeight: isMobileFullPage + ? "100%" + : `calc(100% - ${theme.orbit.spaceXLarge} - ${ + fixedFooter && Boolean(footerHeight) ? footerHeight : 0 + }px)`, + bottom: `${ + (!isMobileFullPage ? parseInt(theme.orbit.spaceXLarge, 10) : 0) + + (fixedFooter && Boolean(footerHeight) ? footerHeight : 0) + }px`, + } + : { + "--orbit-modal-footer-height": fixedFooter ? `${footerHeight}px` : "0", + }), + "--orbit-modal-width": `${modalWidth}px`, + }; + return ( - ( id={id} ref={modalBodyRef} role="dialog" - isMobileFullPage={isMobileFullPage} + // eslint-disable-next-line jsx-a11y/no-autofocus autoFocus={autoFocus} aria-modal="true" aria-labelledby={hasModalTitle ? modalTitleID : ""} > - - {hasCloseContainer && ( - {onClose && hasCloseButton && ( ( title={labelClose} /> )} - + )} {children} - - - + + + ); }, ); @@ -694,6 +519,6 @@ const Modal = React.forwardRef( Modal.displayName = "Modal"; export default Modal; -export { default as ModalHeader, ModalHeading } from "./ModalHeader"; +export { default as ModalHeader } from "./ModalHeader"; export { default as ModalSection } from "./ModalSection"; export { default as ModalFooter } from "./ModalFooter";