From cd107882a3c65916716811384acfcf77c3a8f370 Mon Sep 17 00:00:00 2001 From: Clinton <80900245+pravton@users.noreply.github.com> Date: Mon, 17 Jun 2024 19:17:48 -0400 Subject: [PATCH 1/9] WIP - Removed appendToBody, added renderTo prop to accept string | HTMLElement and updated test modals --- src/lib/components/modal/modal.tsx | 37 ++++++++++------ src/main.tsx | 71 +++++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/lib/components/modal/modal.tsx b/src/lib/components/modal/modal.tsx index 906460e..e26816e 100644 --- a/src/lib/components/modal/modal.tsx +++ b/src/lib/components/modal/modal.tsx @@ -6,16 +6,28 @@ import type { ModalInnerProps } from "../modal-inner" import { classnames } from "../../../utils/classnames" import styles from "./modal.module.scss" - export interface ModalProps extends ModalInnerProps { /** - * If true, the modal will be appended to the body instead of being rendered in place. - * @defaultValue true - */ - appendToBody?: boolean + * If provided, the modal will be appended to the provided element instead of being rendered in place. + * @defaultValue defaults to document.body + **/ + renderTo?: string | HTMLElement } -export function Modal({ appendToBody, className, ...props }: ModalProps) { +const getModalRoot = (renderTo?: string | HTMLElement) => { + if (typeof renderTo === "string") { + if (renderTo === "parent") return + + return document.querySelector(renderTo) || document.body + } + + return renderTo || document.body +} + +export function Modal({ renderTo, className, ...props }: ModalProps) { + const modalRoot = getModalRoot(renderTo) + const appendToBody = modalRoot === document.body + usePreventScroll(appendToBody) const classes = classnames([ @@ -23,12 +35,11 @@ export function Modal({ appendToBody, className, ...props }: ModalProps) { className, ]) - if (appendToBody) { - return ReactDOM.createPortal( - , - document.body - ) - } else { - return + const modalContent = + + if (modalRoot) { + return ReactDOM.createPortal(modalContent, modalRoot) } + + return modalContent } diff --git a/src/main.tsx b/src/main.tsx index f628efc..db42242 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -16,11 +16,67 @@ function CustomModal() { <> {/* `triggerRef` allows the focus to shift to whatever triggered the modal, on close. */} - Open the modal window! + Open the modal (body) {isOpen && ( - + + + + + Close + + Voluptate Lorem ut minim excepteur sit fugiat anim magna aliquip. + + + )} + > + ) +} + +function ModalWithSelector() { + const triggerButton = useRef(null) + const { isOpen, toggle } = useModal({ + triggerRef: triggerButton, + }) + + return ( + <> + {/* `triggerRef` allows the focus to shift to whatever triggered the modal, on close. */} + + Open the modal (selector) + + + {isOpen && ( + + + + + Close + + Voluptate Lorem ut minim excepteur sit fugiat anim magna aliquip. + + + )} + > + ) +} + +function ModalRenderInPlace() { + const triggerButton = useRef(null) + const { isOpen, toggle } = useModal({ + triggerRef: triggerButton, + }) + + return ( + <> + {/* `triggerRef` allows the focus to shift to whatever triggered the modal, on close. */} + + Open modal (in place - "parent") + + + {isOpen && ( + @@ -36,6 +92,7 @@ function CustomModal() { function ModalWithHash() { const triggerButton = useRef(null) + const modalRootRef = useRef(null) const { isOpen, toggle } = useModal({ triggerRef: triggerButton, hash: "modal-with-hash", @@ -44,11 +101,12 @@ function ModalWithHash() { return ( <> - Using a hash + Using a hash (HTMLElement) + - {isOpen && ( - + {isOpen && modalRootRef.current && ( + @@ -66,7 +124,10 @@ function App() { return ( <> + + + Open modal from anchor without events > ) From 308d0616f8ec4852b9c3cbabd36eebc63a5b3ec4 Mon Sep 17 00:00:00 2001 From: Clinton <80900245+pravton@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:34:10 -0400 Subject: [PATCH 2/9] WIP - Removed render to same parent, can use ref/selecter to pass any element --- src/lib/components/modal/modal.tsx | 4 +--- src/main.tsx | 29 ----------------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/src/lib/components/modal/modal.tsx b/src/lib/components/modal/modal.tsx index e26816e..e35288d 100644 --- a/src/lib/components/modal/modal.tsx +++ b/src/lib/components/modal/modal.tsx @@ -8,7 +8,7 @@ import { classnames } from "../../../utils/classnames" import styles from "./modal.module.scss" export interface ModalProps extends ModalInnerProps { /** - * If provided, the modal will be appended to the provided element instead of being rendered in place. + * If provided, the modal will be appended to the provided element instead of being rendered in body * @defaultValue defaults to document.body **/ renderTo?: string | HTMLElement @@ -16,8 +16,6 @@ export interface ModalProps extends ModalInnerProps { const getModalRoot = (renderTo?: string | HTMLElement) => { if (typeof renderTo === "string") { - if (renderTo === "parent") return - return document.querySelector(renderTo) || document.body } diff --git a/src/main.tsx b/src/main.tsx index db42242..5426624 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -62,34 +62,6 @@ function ModalWithSelector() { ) } -function ModalRenderInPlace() { - const triggerButton = useRef(null) - const { isOpen, toggle } = useModal({ - triggerRef: triggerButton, - }) - - return ( - <> - {/* `triggerRef` allows the focus to shift to whatever triggered the modal, on close. */} - - Open modal (in place - "parent") - - - {isOpen && ( - - - - - Close - - Voluptate Lorem ut minim excepteur sit fugiat anim magna aliquip. - - - )} - > - ) -} - function ModalWithHash() { const triggerButton = useRef(null) const modalRootRef = useRef(null) @@ -126,7 +98,6 @@ function App() { - Open modal from anchor without events > From 210f29c599552f74e565ff56493d065b56aea97a Mon Sep 17 00:00:00 2001 From: Clinton <80900245+pravton@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:00:44 -0400 Subject: [PATCH 3/9] WIP - Updated documentaion to reflect the changes --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e9ef2f..e119d3d 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ import { function MyModal() { const triggerButton = useRef(null) + const modalRootRef = useRef(null) const { isOpen, toggle } = useModal({ // `triggerRef` allows the focus to shift to whatever triggered the modal @@ -132,9 +133,12 @@ function MyModal() { Open the modal window! + - {render && ( - @@ -158,7 +162,7 @@ function MyModal() { | prop | type | default value | description | | -------------------- | --------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| appendToBody | Boolean | true | Optional. Whether to append the Modal markup to the HTML `` element, or to leave it where it exists within your component structure. Setting this to `false` can be useful for "local" dialog boxes. | +| renderTo | String / HTMLElement | null | Optional. If not provided or the selector/element provided is undefined/null the modal will be appended to document.body If a valid selector string is provided, the modal will be appended to the element returned by document.querySelector(renderTo). If a valid HTMLElement is provided, the modal will be appended to that element. | | className | String | null | | | children | ReactNode | null | | From d6187725704def641afa4ece6f9bc3cbb669ad44 Mon Sep 17 00:00:00 2001 From: Clinton <80900245+pravton@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:31:57 -0400 Subject: [PATCH 4/9] WIP - added client side check to prevent ssr issues on next js --- src/lib/components/modal/modal.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/components/modal/modal.tsx b/src/lib/components/modal/modal.tsx index e35288d..f1910e3 100644 --- a/src/lib/components/modal/modal.tsx +++ b/src/lib/components/modal/modal.tsx @@ -15,16 +15,20 @@ export interface ModalProps extends ModalInnerProps { } const getModalRoot = (renderTo?: string | HTMLElement) => { - if (typeof renderTo === "string") { - return document.querySelector(renderTo) || document.body + if (typeof window !== "undefined") { + if (typeof renderTo === "string") { + return document.querySelector(renderTo) || document.body + } + + return renderTo || document.body } - return renderTo || document.body + return null } export function Modal({ renderTo, className, ...props }: ModalProps) { const modalRoot = getModalRoot(renderTo) - const appendToBody = modalRoot === document.body + const appendToBody = modalRoot ? modalRoot === document.body : false usePreventScroll(appendToBody) From 0337d299a308c12baca5d957f31a203309ed02bf Mon Sep 17 00:00:00 2001 From: Clinton <80900245+pravton@users.noreply.github.com> Date: Fri, 21 Jun 2024 18:50:47 -0400 Subject: [PATCH 5/9] WIP - moved getModalRoot inside component, added error handling and updated variable name --- src/lib/components/modal/modal.tsx | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/lib/components/modal/modal.tsx b/src/lib/components/modal/modal.tsx index f1910e3..43286fe 100644 --- a/src/lib/components/modal/modal.tsx +++ b/src/lib/components/modal/modal.tsx @@ -6,6 +6,7 @@ import type { ModalInnerProps } from "../modal-inner" import { classnames } from "../../../utils/classnames" import styles from "./modal.module.scss" +import { useMemo } from "react" export interface ModalProps extends ModalInnerProps { /** * If provided, the modal will be appended to the provided element instead of being rendered in body @@ -14,26 +15,29 @@ export interface ModalProps extends ModalInnerProps { renderTo?: string | HTMLElement } -const getModalRoot = (renderTo?: string | HTMLElement) => { - if (typeof window !== "undefined") { +export function Modal({ renderTo, className, ...props }: ModalProps) { + const isClient = typeof window !== "undefined" && typeof document !== "undefined" + // get modalRoot + const modalRoot = useMemo(() => { + if (!isClient) return null + if (typeof renderTo === "string") { - return document.querySelector(renderTo) || document.body + const element = document.querySelector(renderTo) + if (element) return element + // Console error when element not found + console.error(`Element not found for selector: ${renderTo}`) + return null } return renderTo || document.body - } + }, [renderTo, isClient]) - return null -} - -export function Modal({ renderTo, className, ...props }: ModalProps) { - const modalRoot = getModalRoot(renderTo) - const appendToBody = modalRoot ? modalRoot === document.body : false + const lockScrolling = isClient ? modalRoot === document.body : false - usePreventScroll(appendToBody) + usePreventScroll(lockScrolling) const classes = classnames([ - appendToBody ? styles.ModalFixed : styles.ModalAbsolute, + lockScrolling ? styles.ModalFixed : styles.ModalAbsolute, className, ]) From 2cd8e404bee8b884abf4796ef588581e20fde14f Mon Sep 17 00:00:00 2001 From: Clinton <80900245+pravton@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:30:29 -0400 Subject: [PATCH 6/9] update(react-modal): WIP - removed string prop, made prop mandatory and only accept HTMLElement --- src/lib/components/modal/modal.tsx | 40 ++++++---------------------- src/main.tsx | 42 +++++------------------------- 2 files changed, 15 insertions(+), 67 deletions(-) diff --git a/src/lib/components/modal/modal.tsx b/src/lib/components/modal/modal.tsx index 43286fe..1d978c5 100644 --- a/src/lib/components/modal/modal.tsx +++ b/src/lib/components/modal/modal.tsx @@ -1,50 +1,26 @@ -import ReactDOM from "react-dom" -import { usePreventScroll } from "@wethegit/react-hooks" +"use client" +import ReactDOM from "react-dom" import { ModalInner } from "../modal-inner" import type { ModalInnerProps } from "../modal-inner" import { classnames } from "../../../utils/classnames" import styles from "./modal.module.scss" -import { useMemo } from "react" export interface ModalProps extends ModalInnerProps { /** - * If provided, the modal will be appended to the provided element instead of being rendered in body - * @defaultValue defaults to document.body + * The modal will be appended to the passed element instead of being rendered in place + * @defaultValue defaults inPlace **/ - renderTo?: string | HTMLElement + renderTo: HTMLElement } export function Modal({ renderTo, className, ...props }: ModalProps) { - const isClient = typeof window !== "undefined" && typeof document !== "undefined" - // get modalRoot - const modalRoot = useMemo(() => { - if (!isClient) return null - - if (typeof renderTo === "string") { - const element = document.querySelector(renderTo) - if (element) return element - // Console error when element not found - console.error(`Element not found for selector: ${renderTo}`) - return null - } - - return renderTo || document.body - }, [renderTo, isClient]) - - const lockScrolling = isClient ? modalRoot === document.body : false - - usePreventScroll(lockScrolling) - - const classes = classnames([ - lockScrolling ? styles.ModalFixed : styles.ModalAbsolute, - className, - ]) + const classes = classnames([styles.ModalFixed, className]) const modalContent = - if (modalRoot) { - return ReactDOM.createPortal(modalContent, modalRoot) + if (renderTo) { + return ReactDOM.createPortal(modalContent, renderTo) } return modalContent diff --git a/src/main.tsx b/src/main.tsx index 5426624..94fde4e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -8,6 +8,7 @@ import styles from "./main.module.css" function CustomModal() { const triggerButton = useRef(null) + const modalRootRef = useRef(null) const { isOpen, toggle } = useModal({ triggerRef: triggerButton, }) @@ -16,39 +17,12 @@ function CustomModal() { <> {/* `triggerRef` allows the focus to shift to whatever triggered the modal, on close. */} - Open the modal (body) - - - {isOpen && ( - - - - - Close - - Voluptate Lorem ut minim excepteur sit fugiat anim magna aliquip. - - - )} - > - ) -} - -function ModalWithSelector() { - const triggerButton = useRef(null) - const { isOpen, toggle } = useModal({ - triggerRef: triggerButton, - }) - - return ( - <> - {/* `triggerRef` allows the focus to shift to whatever triggered the modal, on close. */} - - Open the modal (selector) + Open the modal (HTMLElement) + - {isOpen && ( - + {isOpen && modalRootRef.current && ( + @@ -73,9 +47,9 @@ function ModalWithHash() { return ( <> - Using a hash (HTMLElement) + Using a hash - + {isOpen && modalRootRef.current && ( @@ -96,9 +70,7 @@ function App() { return ( <> - - Open modal from anchor without events > ) From a71a040387d6f7d61688838bc4b50cfd380f0240 Mon Sep 17 00:00:00 2001 From: Clinton <80900245+pravton@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:05:01 -0400 Subject: [PATCH 7/9] udpate(react-modal): WIP - updated documentation to reflect the updated changes --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e119d3d..a4001c7 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ Custom transition, focus management and hash-based state management. Use your favorite animation library, [@wethegit/react-hooks](https://wethegit.github.io/react-hooks/use-animate-presence) provides a simple one for these cases. ```jsx +import { useRef } from 'react' import { useAnimatePresence } from '@wethegit/react-hooks' import { Modal, @@ -162,7 +163,7 @@ function MyModal() { | prop | type | default value | description | | -------------------- | --------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| renderTo | String / HTMLElement | null | Optional. If not provided or the selector/element provided is undefined/null the modal will be appended to document.body If a valid selector string is provided, the modal will be appended to the element returned by document.querySelector(renderTo). If a valid HTMLElement is provided, the modal will be appended to that element. | +| renderTo | HTMLElement | null | If a valid HTMLElement is provided, the modal will be appended to that element. Otherwise will be rendered in place. | | className | String | null | | | children | ReactNode | null | | From 3744520181c3039e08e4e082d4316e201a59c58b Mon Sep 17 00:00:00 2001 From: Clinton <80900245+pravton@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:27:51 -0400 Subject: [PATCH 8/9] update(react-modal): added an example of appending to body and updated anchor text --- src/main.module.css | 4 ++++ src/main.tsx | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/main.module.css b/src/main.module.css index f6d1eef..4651be0 100644 --- a/src/main.module.css +++ b/src/main.module.css @@ -31,3 +31,7 @@ --ease: cubic-bezier(0.77, 0, 0.1, 1.39); --scale: 1; } + +.CustomModalAbsolute { + position: absolute; +} diff --git a/src/main.tsx b/src/main.tsx index 94fde4e..c71a5b2 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from "react" +import React, { useRef, useEffect } from "react" import { createRoot } from "react-dom/client" import { Modal, ModalContent, useModal, ModalBackdrop } from "./lib" @@ -36,6 +36,39 @@ function CustomModal() { ) } +function CustomModalOnBody() { + const triggerButton = useRef(null) + const bodyRef = useRef(null) + const { isOpen, toggle } = useModal({ + triggerRef: triggerButton, + }) + + useEffect(() => { + bodyRef.current = document.body + }, []) + + return ( + <> + {/* `triggerRef` allows the focus to shift to whatever triggered the modal, on close. */} + + Open the modal (Append to body) + + + {isOpen && bodyRef.current && ( + + + + + Close + + Voluptate Lorem ut minim excepteur sit fugiat anim magna aliquip. + + + )} + > + ) +} + function ModalWithHash() { const triggerButton = useRef(null) const modalRootRef = useRef(null) @@ -47,7 +80,7 @@ function ModalWithHash() { return ( <> - Using a hash + Open the modal using a hash @@ -70,8 +103,11 @@ function App() { return ( <> + - Open modal from anchor without events + + Open modal using exisiting hash (#modal-with-hash) without events + > ) } From 6610bbf3632a78afd27bad86a9e31a804e86dfbe Mon Sep 17 00:00:00 2001 From: Clinton <80900245+pravton@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:21:17 -0400 Subject: [PATCH 9/9] update(react-modal): bumping the version using changeset --- .changeset/perfect-plums-pretend.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .changeset/perfect-plums-pretend.md diff --git a/.changeset/perfect-plums-pretend.md b/.changeset/perfect-plums-pretend.md new file mode 100644 index 0000000..b690cc5 --- /dev/null +++ b/.changeset/perfect-plums-pretend.md @@ -0,0 +1,27 @@ +--- +"@wethegit/react-modal": major +--- + +## Breaking changes + +- **Removed** `appendToBody` prop. + - The `appendToBody` prop has been removed. This prop was previously used to determine whether the modal should be appended to the body element. + +## New Features + +- **Added** `renderTo` prop. + - Introduced the `renderTo` prop, which accepts an HTMLElement where the modal will be appended. This provides greater flexibilty, allowing users to specify any element to render the modal, including the body. This change enhances the customization options for the modal rendering. + +## Migration + +- **Before** + + ```javascript + + ``` + +- **After** + + ```javascript + + ```
Voluptate Lorem ut minim excepteur sit fugiat anim magna aliquip.