diff --git a/packages/react/src/components/Modal/ModalProvider.tsx b/packages/react/src/components/Modal/ModalProvider.tsx index f9dab63..cad3cda 100644 --- a/packages/react/src/components/Modal/ModalProvider.tsx +++ b/packages/react/src/components/Modal/ModalProvider.tsx @@ -44,6 +44,7 @@ interface ModalContextType { props?: Partial, ) => Promise; messageModal: (props?: Partial) => Promise; + modalParentSelector?: () => HTMLElement; } const ModalContext = createContext(undefined); @@ -102,7 +103,14 @@ const ModalContainer = ({ type ModalMap = Map; -export const ModalProvider = ({ children }: PropsWithChildren) => { +interface ModalProviderProps extends PropsWithChildren { + modalParentSelector?: () => HTMLElement; +} + +export const ModalProvider = ({ + children, + modalParentSelector, +}: ModalProviderProps) => { const [modals, setModals] = useState({} as ModalMap); useEffect(() => { @@ -151,6 +159,12 @@ export const ModalProvider = ({ children }: PropsWithChildren) => { deleteConfirmationModal: ModalHelper(DeleteConfirmationModal), confirmationModal: ModalHelper(ConfirmationModal), messageModal: ModalHelper(MessageModal), + modalParentSelector: () => { + if (modalParentSelector) { + return modalParentSelector(); + } + return document.getElementById("c__modals-portal")!; + }, }), [], ); diff --git a/packages/react/src/components/Modal/index.spec.tsx b/packages/react/src/components/Modal/index.spec.tsx index 26e089b..6c752b3 100644 --- a/packages/react/src/components/Modal/index.spec.tsx +++ b/packages/react/src/components/Modal/index.spec.tsx @@ -69,6 +69,43 @@ describe("", () => { expect(screen.getByText("Modal Content")).toBeInTheDocument(); expect(app).toHaveAttribute("aria-hidden", "true"); }); + it("use modalParentSelector to change the modal portal", async () => { + const Wrapper = () => { + const modal = useModal(); + return ( + <> + + document.querySelector("#my-custom-portal")! + } + > + + +
Modal Content
+
+
+
+ + ); + }; + + render(); + const user = userEvent.setup(); + const button = screen.getByText("Open Modal"); + const portal = document.querySelector("#my-custom-portal")!; + + expect(portal.children.length).toEqual(0); + expect(screen.queryByText("Modal Content")).not.toBeInTheDocument(); + + await user.click(button); + + const content = screen.getByText("Modal Content"); + expect(content).toBeInTheDocument(); + expect(portal.children.length).toEqual(1); + expect(portal.children[0].className).toEqual("ReactModalPortal"); + expect(portal.children[0].children.length).toEqual(1); + expect(portal).toContain(content); + }); it("closes the modal when clicking on the close button", async () => { const Wrapper = () => { const modal = useModal(); diff --git a/packages/react/src/components/Modal/index.stories.tsx b/packages/react/src/components/Modal/index.stories.tsx index 6f8738f..90d72b4 100644 --- a/packages/react/src/components/Modal/index.stories.tsx +++ b/packages/react/src/components/Modal/index.stories.tsx @@ -21,10 +21,10 @@ const meta: Meta = { }, []); return ( - + <> - + ); }, ], @@ -197,3 +197,20 @@ export const FullWithContent: Story = { children: longLorem.text, }, }; + +export const CustomParentSelect: Story = { + render: () => { + return ( + + document.querySelector("#my-custom-modal-parent")! + } + > + {}} size={ModalSize.MEDIUM}> + I am rendered inside #my-custom-modal-parent + +
+ + ); + }, +}; diff --git a/packages/react/src/components/Modal/index.tsx b/packages/react/src/components/Modal/index.tsx index 153a777..7367ab6 100644 --- a/packages/react/src/components/Modal/index.tsx +++ b/packages/react/src/components/Modal/index.tsx @@ -2,7 +2,7 @@ import React, { PropsWithChildren, ReactNode, useEffect } from "react"; import classNames from "classnames"; import ReactModal from "react-modal"; import { Button } from ":/components/Button"; -import { NOSCROLL_CLASS } from ":/components/Modal/ModalProvider"; +import { NOSCROLL_CLASS, useModals } from ":/components/Modal/ModalProvider"; export type ModalHandle = {}; diff --git a/packages/react/src/components/Provider/index.tsx b/packages/react/src/components/Provider/index.tsx index adaf2b9..72dd1af 100644 --- a/packages/react/src/components/Provider/index.tsx +++ b/packages/react/src/components/Provider/index.tsx @@ -34,6 +34,7 @@ interface Props extends PropsWithChildren { customLocales?: Record; currentLocale?: string; theme?: string; + modalParentSelector?: () => HTMLElement; } export const DEFAULT_LOCALE = Locales.enUS; @@ -53,6 +54,7 @@ export const CunninghamProvider = ({ currentLocale = DEFAULT_LOCALE, customLocales, theme = DEFAULT_THEME, + modalParentSelector, children, }: Props) => { const locales: Record = useMemo( @@ -102,7 +104,7 @@ export const CunninghamProvider = ({ return ( - +
{children}