diff --git a/packages/core/src/components/ModalNew/Modal/Modal.tsx b/packages/core/src/components/ModalNew/Modal/Modal.tsx index 81b5e050d6..708e079dbe 100644 --- a/packages/core/src/components/ModalNew/Modal/Modal.tsx +++ b/packages/core/src/components/ModalNew/Modal/Modal.tsx @@ -73,14 +73,14 @@ const Modal = forwardRef( return ( - -
- +
+ +
+ {children} - {children}
-
- + + ); } diff --git a/packages/core/src/components/ModalNew/Modal/__tests__/Modal.test.tsx b/packages/core/src/components/ModalNew/Modal/__tests__/Modal.test.tsx index 855cd07894..e29a7768a8 100644 --- a/packages/core/src/components/ModalNew/Modal/__tests__/Modal.test.tsx +++ b/packages/core/src/components/ModalNew/Modal/__tests__/Modal.test.tsx @@ -181,17 +181,18 @@ describe("Modal", () => { ); - const closeButton = getByLabelText(closeButtonAriaLabel); - expect(closeButton).toHaveFocus(); - - userEvent.tab(); expect(getByText("Focusable 1")).toHaveFocus(); userEvent.tab(); expect(getByText("Focusable 2")).toHaveFocus(); userEvent.tab(); + + const closeButton = getByLabelText(closeButtonAriaLabel); expect(closeButton).toHaveFocus(); + + userEvent.tab(); + expect(getByText("Focusable 1")).toHaveFocus(); }); it.todo("renders the correct aria-labelledby"); diff --git a/packages/core/src/components/ModalNew/ModalContent/ModalContent.tsx b/packages/core/src/components/ModalNew/ModalContent/ModalContent.tsx index ceaed53583..927578119e 100644 --- a/packages/core/src/components/ModalNew/ModalContent/ModalContent.tsx +++ b/packages/core/src/components/ModalNew/ModalContent/ModalContent.tsx @@ -1,32 +1,19 @@ -import React, { forwardRef, UIEventHandler, useCallback } from "react"; -import cx from "classnames"; +import React, { forwardRef } from "react"; import { getTestId } from "../../../tests/test-ids-utils"; import { ComponentDefaultTestId } from "../../../tests/constants"; -import styles from "./ModalContent.module.scss"; import { ModalContentProps } from "./ModalContent.types"; -import { useModal } from "../context/ModalContext"; const ModalContent = forwardRef( ( { children, className, id, "data-testid": dataTestId }: ModalContentProps, ref: React.ForwardedRef ) => { - const { setContentScrolled } = useModal(); - - const onScroll: UIEventHandler = useCallback( - e => { - setContentScrolled(e.currentTarget?.scrollTop > 0); - }, - [setContentScrolled] - ); - return (
{children}
diff --git a/packages/core/src/components/ModalNew/ModalContent/__tests__/ModalContent.test.tsx b/packages/core/src/components/ModalNew/ModalContent/__tests__/ModalContent.test.tsx deleted file mode 100644 index 292d6f52f2..0000000000 --- a/packages/core/src/components/ModalNew/ModalContent/__tests__/ModalContent.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import { render, fireEvent } from "@testing-library/react"; -import ModalContent from "../ModalContent"; -import { useModal } from "../../context/ModalContext"; - -jest.mock("../../context/ModalContext", () => ({ - useModal: jest.fn() -})); -const useModalMocked = jest.mocked(useModal); - -describe("ModalContent", () => { - const childrenContent = My content; - - const useModalMockedReturnedValue = { - modalId: "modal-id", - setTitleId: jest.fn(), - setDescriptionId: jest.fn(), - isContentScrolled: false, - setContentScrolled: jest.fn() - }; - - beforeEach(() => { - useModalMocked.mockReturnValue(useModalMockedReturnedValue); - }); - - it("renders the children correctly", () => { - const { getByText } = render({childrenContent}); - expect(getByText("My content")).toBeInTheDocument(); - }); - - it("renders when no children are provided", () => { - const { container } = render(); - expect(container.firstChild).toBeInTheDocument(); - }); - - it("calls setContentScrolled with 'true' when scrolled down", () => { - const { getByTestId } = render( - - {childrenContent} - - ); - - const contentDiv = getByTestId("modal-content"); - fireEvent.scroll(contentDiv, { target: { scrollTop: 100 } }); - - expect(useModalMockedReturnedValue.setContentScrolled).toHaveBeenCalledWith(true); - }); - - it("calls setContentScrolled with 'false' when scrolled to the top", () => { - const { getByTestId } = render( - - {childrenContent} - - ); - - const contentDiv = getByTestId("modal-content"); - - fireEvent.scroll(contentDiv, { target: { scrollTop: 100 } }); - fireEvent.scroll(contentDiv, { target: { scrollTop: 0 } }); - - expect(useModalMockedReturnedValue.setContentScrolled).toHaveBeenLastCalledWith(false); - }); - - it("does not call setContentScrolled if no scroll occurs", () => { - render( - - {childrenContent} - - ); - - expect(useModalMockedReturnedValue.setContentScrolled).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/core/src/components/ModalNew/ModalHeader/__tests__/ModalHeader.test.tsx b/packages/core/src/components/ModalNew/ModalHeader/__tests__/ModalHeader.test.tsx index 94a8a60a66..83f5fbd0c7 100644 --- a/packages/core/src/components/ModalNew/ModalHeader/__tests__/ModalHeader.test.tsx +++ b/packages/core/src/components/ModalNew/ModalHeader/__tests__/ModalHeader.test.tsx @@ -3,6 +3,7 @@ import { render } from "@testing-library/react"; import ModalHeader from "../ModalHeader"; import { Text as TextIcon } from "../../../Icon/Icons"; import { useModal } from "../../context/ModalContext"; +import { ModalContextProps } from "../../context/ModalContext.types"; jest.mock("../../context/ModalContext", () => ({ useModal: jest.fn() @@ -14,12 +15,10 @@ describe("ModalHeader", () => { const simpleDescription = "This is a description"; const descriptionIcon = TextIcon; - const useModalMockedReturnedValue = { + const useModalMockedReturnedValue: ModalContextProps = { modalId: "modal-id", setTitleId: jest.fn(), - setDescriptionId: jest.fn(), - isContentScrolled: false, - setContentScrolled: jest.fn() + setDescriptionId: jest.fn() }; beforeEach(() => { diff --git a/packages/core/src/components/ModalNew/context/ModalContext.tsx b/packages/core/src/components/ModalNew/context/ModalContext.tsx index bfb37868aa..b7a03d3f53 100644 --- a/packages/core/src/components/ModalNew/context/ModalContext.tsx +++ b/packages/core/src/components/ModalNew/context/ModalContext.tsx @@ -1,21 +1,10 @@ -import React, { createContext, useContext, useMemo, useState } from "react"; +import React, { createContext, useContext } from "react"; import { ModalContextProps, ModalProviderProps } from "./ModalContext.types"; const ModalContext = createContext(undefined); export const ModalProvider = ({ value, children }: ModalProviderProps) => { - const [isContentScrolled, setContentScrolled] = useState(false); - - const contextValue = useMemo( - () => ({ - ...value, - isContentScrolled, - setContentScrolled: (newContentScrolled: boolean) => setContentScrolled(newContentScrolled) - }), - [isContentScrolled, value] - ); - - return {children}; + return {children}; }; export const useModal = (): ModalContextProps => { diff --git a/packages/core/src/components/ModalNew/context/ModalContext.types.ts b/packages/core/src/components/ModalNew/context/ModalContext.types.ts index 9899747e5b..9ec22d54e9 100644 --- a/packages/core/src/components/ModalNew/context/ModalContext.types.ts +++ b/packages/core/src/components/ModalNew/context/ModalContext.types.ts @@ -1,9 +1,6 @@ import React from "react"; -export interface ModalContextProps extends ModalProviderValue { - isContentScrolled: boolean; - setContentScrolled: (scrolled: boolean) => void; -} +export type ModalContextProps = ModalProviderValue; export type ModalProviderValue = { modalId: string; diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.module.scss b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.module.scss index 7556cbbd37..0070099fcc 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.module.scss +++ b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.module.scss @@ -1,12 +1,13 @@ .layout { width: 100%; - flex-grow: 1; + height: 100%; overflow: hidden; .header { width: 100%; - margin-block: var(--spacing-xl) var(--spacing-medium); + margin-block: var(--spacing-xl) var(--spacing-large); padding-inline-end: calc(var(--top-actions-spacing) + var(--top-actions-width) + var(--spacing-medium)); + padding-inline-start: var(--modal-inline-padding); } .divider { @@ -14,6 +15,6 @@ } .content { - padding-inline: var(--modal-inline-padding); + padding-block-end: var(--spacing-xl); } } diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.tsx b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.tsx index e0d4c64ba3..a4a720d069 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.tsx +++ b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.tsx @@ -4,31 +4,38 @@ import { getTestId } from "../../../../tests/test-ids-utils"; import { ComponentDefaultTestId } from "../../../../tests/constants"; import styles from "./ModalBasicLayout.module.scss"; import { ModalBasicLayoutProps } from "./ModalBasicLayout.types"; -import { useModal } from "../../context/ModalContext"; import Flex from "../../../Flex/Flex"; import Divider from "../../../Divider/Divider"; +import ModalFooterShadow from "../ModalFooterShadow"; +import ModalLayoutScrollableContent from "../ModalLayoutScrollableContent"; +import useLayoutScrolledContent from "../useLayoutScrolledContent"; const ModalBasicLayout = forwardRef( ( { children, className, id, "data-testid": dataTestId }: ModalBasicLayoutProps, ref: React.ForwardedRef ) => { + const { isContentScrolled, onScroll } = useLayoutScrolledContent(); const [header, content] = React.Children.toArray(children); - const { isContentScrolled } = useModal(); return ( - -
{header}
- {isContentScrolled && } -
{content}
-
+ <> + +
{header}
+ {isContentScrolled && } + + {content} + +
+ {isContentScrolled && } + ); } ); diff --git a/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.module.scss b/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.module.scss new file mode 100644 index 0000000000..06c764c88c --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.module.scss @@ -0,0 +1,7 @@ +.shadowWrapper::after { + content: ""; + position: absolute; + width: 100%; + height: 10px; + box-shadow: var(--box-shadow-medium); +} diff --git a/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.tsx b/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.tsx new file mode 100644 index 0000000000..60fa4ec879 --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import styles from "./ModalFooterShadow.module.scss"; + +const ModalFooterShadow = () => { + return
; +}; + +export default ModalFooterShadow; diff --git a/packages/core/src/components/ModalNew/ModalContent/ModalContent.module.scss b/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.module.scss similarity index 54% rename from packages/core/src/components/ModalNew/ModalContent/ModalContent.module.scss rename to packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.module.scss index f463fb3e05..b2206322d5 100644 --- a/packages/core/src/components/ModalNew/ModalContent/ModalContent.module.scss +++ b/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.module.scss @@ -1,7 +1,9 @@ @import "~monday-ui-style/dist/mixins"; .content { - padding-block-end: var(--spacing-xl); + width: 100%; + height: 100%; + padding-inline: var(--modal-inline-padding); overflow: auto; @include scroller; } diff --git a/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.tsx b/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.tsx new file mode 100644 index 0000000000..b989c995ca --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import cx from "classnames"; +import styles from "./ModalLayoutScrollableContent.module.scss"; +import { ModalLayoutScrollableContentProps } from "./ModalLayoutScrollableContent.types"; + +const ModalLayoutScrollableContent = ({ onScroll, className, children }: ModalLayoutScrollableContentProps) => { + return ( +
+ {children} +
+ ); +}; + +export default ModalLayoutScrollableContent; diff --git a/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.types.ts b/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.types.ts new file mode 100644 index 0000000000..7fa753ff9a --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.types.ts @@ -0,0 +1,7 @@ +import { ReactNode, UIEventHandler } from "react"; + +export interface ModalLayoutScrollableContentProps { + onScroll?: UIEventHandler; + className?: string; + children: ReactNode; +} diff --git a/packages/core/src/components/ModalNew/layouts/ModalMedia.module.scss b/packages/core/src/components/ModalNew/layouts/ModalMedia.module.scss index 341d94f0d9..9eb13e01cf 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalMedia.module.scss +++ b/packages/core/src/components/ModalNew/layouts/ModalMedia.module.scss @@ -1,8 +1,10 @@ .media { - align-items: normal; - align-self: normal; - width: 100%; - height: auto; + max-width: 100%; max-height: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; overflow: hidden; + position: relative; } diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.module.scss b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.module.scss index 7c3115cdac..c2f884fee1 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.module.scss +++ b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.module.scss @@ -1,21 +1,24 @@ .layout { width: 100%; - flex-grow: 1; + height: 100%; overflow: hidden; .media { + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + position: relative; width: 100%; - height: var(--modal-top-media-height, 240px); flex-shrink: 0; + height: var(--modal-top-media-height, 240px); @media (min-width: 1280px) { --modal-top-media-height: 260px; } - @media (min-width: 1440px) { --modal-top-media-height: 260px; } - @media (min-width: 1720px) { --modal-top-media-height: 320px; } @@ -23,13 +26,16 @@ .header { width: 100%; - margin-block: var(--spacing-large) var(--spacing-small); text-align: center; + margin-block: var(--spacing-xl) var(--spacing-small); + padding-inline: var(--modal-inline-padding); } .content { width: 100%; flex: 1; align-self: flex-start; + padding-block-end: var(--spacing-large); + text-align: center; } } diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.tsx b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.tsx index e11a792264..b7819817ae 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.tsx +++ b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.tsx @@ -5,6 +5,8 @@ import { ComponentDefaultTestId } from "../../../../tests/constants"; import styles from "./ModalMediaLayout.module.scss"; import { ModalMediaLayoutProps } from "./ModalMediaLayout.types"; import Flex from "../../../Flex/Flex"; +import ModalLayoutScrollableContent from "../ModalLayoutScrollableContent"; +import ModalFooterShadow from "../ModalFooterShadow"; const ModalMediaLayout = forwardRef( ( @@ -14,18 +16,21 @@ const ModalMediaLayout = forwardRef( const [media, header, content] = React.Children.toArray(children); return ( - -
{media}
-
{header}
-
{content}
-
+ <> + +
{media}
+
{header}
+ {content} +
+ + ); } ); diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.module.scss b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.module.scss index 4b135c6272..90114cf2a7 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.module.scss +++ b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.module.scss @@ -1,25 +1,35 @@ .layout { width: 100%; - flex-grow: 1; - overflow: hidden; + height: 100%; + display: grid; + grid-template-columns: 60% 40%; + grid-template-rows: auto 1fr; + min-height: 0; - .leftPane { - height: 100%; - width: 60%; + .header { + width: 100%; + margin-block: var(--spacing-xl) var(--spacing-large); padding-inline: var(--modal-inline-padding); + grid-column: 1 / 2; + grid-row: 1; + } - .header { - width: 100%; - margin-block: var(--spacing-xl) var(--spacing-large); - } - - .content { - width: 100%; - } + .content { + grid-column: 1 / 2; + grid-row: 2; + padding-block-end: var(--spacing-xl); } .media { - flex-shrink: 0; - width: 40%; + grid-column: 2 / 3; + grid-row: 1 / 3; + + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + overflow: hidden; + position: relative; } } diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.tsx b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.tsx index b36093ec23..7f2cd02e9f 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.tsx +++ b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.tsx @@ -4,7 +4,8 @@ import { getTestId } from "../../../../tests/test-ids-utils"; import { ComponentDefaultTestId } from "../../../../tests/constants"; import styles from "./ModalSideBySideLayout.module.scss"; import { ModalSideBySideLayoutProps } from "./ModalSideBySideLayout.types"; -import Flex from "../../../Flex/Flex"; +import ModalLayoutScrollableContent from "../ModalLayoutScrollableContent"; +import ModalFooterShadow from "../ModalFooterShadow"; const ModalSideBySideLayout = forwardRef( ( @@ -13,18 +14,19 @@ const ModalSideBySideLayout = forwardRef( ) => { const [header, content, media] = React.Children.toArray(children); return ( - - + <> +
{header}
-
{content}
- -
{media}
- + {content} +
{media}
+
+ + ); } ); diff --git a/packages/core/src/components/ModalNew/layouts/useLayoutScrolledContent.ts b/packages/core/src/components/ModalNew/layouts/useLayoutScrolledContent.ts new file mode 100644 index 0000000000..63cae6d323 --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/useLayoutScrolledContent.ts @@ -0,0 +1,16 @@ +import { UIEventHandler, useCallback, useState } from "react"; + +const useLayoutScrolledContent = () => { + const [isContentScrolled, setContentScrolled] = useState(false); + + const onScroll: UIEventHandler = useCallback( + e => { + setContentScrolled(e.currentTarget?.scrollTop > 0); + }, + [setContentScrolled] + ); + + return { isContentScrolled, onScroll }; +}; + +export default useLayoutScrolledContent;