Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .changeset/perfect-plums-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
"@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.

## Fixes

- Mark argument of hook as optional [#62](https://github.com/wethegit/react-modal/issues/62)

## Migration

- **Before**

```javascript
<Modal appendToBody={true} />
```

- **After**

```javascript
<Modal renderTo={modalRef} />
```
8 changes: 8 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mode": "pre",
"tag": "beta",
"initialVersions": {
"@wethegit/react-modal": "2.2.2"
},
"changesets": ["perfect-plums-pretend"]
}
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -114,6 +115,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
Expand All @@ -132,9 +134,12 @@ function MyModal() {
<button ref={triggerButton} onClick={toggle}>
Open the modal window!
</button>
<div ref={modalRootRef}></div>

{render && (
<Modal style={{
{render && modalRootRef.current && (
<Modal
renderTo={modalRootRef.current}
style={{
transition: `opacity 800ms ease-in-out`,
opacity: animate ? 1 : 0
}}>
Expand All @@ -158,7 +163,7 @@ function MyModal() {

| prop | type | default value | description |
| -------------------- | --------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| appendToBody | Boolean | true | Optional. Whether to append the Modal markup to the HTML `<body>` element, or to leave it where it exists within your component structure. Setting this to `false` can be useful for "local" dialog boxes. |
| 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 | |

Expand Down
33 changes: 13 additions & 20 deletions src/lib/components/modal/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
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"

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
* The modal will be appended to the passed element instead of being rendered in place
* @defaultValue defaults inPlace
**/
renderTo: HTMLElement
}

export function Modal({ appendToBody, className, ...props }: ModalProps) {
usePreventScroll(appendToBody)
export function Modal({ renderTo, className, ...props }: ModalProps) {
const classes = classnames([styles.ModalFixed, className])

const classes = classnames([
appendToBody ? styles.ModalFixed : styles.ModalAbsolute,
className,
])
const modalContent = <ModalInner className={classes} {...props} />

if (appendToBody) {
return ReactDOM.createPortal(
<ModalInner className={classes} {...props} />,
document.body
)
} else {
return <ModalInner className={classes} {...props} />
if (renderTo) {
return ReactDOM.createPortal(modalContent, renderTo)
}

return modalContent
}
4 changes: 2 additions & 2 deletions src/lib/hooks/use-modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export interface UseModalOptions {
hash?: string
}

export function useModal(props: UseModalOptions) {
const { triggerRef, hash } = props || {}
export function useModal(props: UseModalOptions = {}) {
const { triggerRef, hash } = props
const [state, setState] = useState<ModalStates>(ModalStates.CLOSED)

const handleClose = useCallback(() => {
Expand Down
4 changes: 4 additions & 0 deletions src/main.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@
--ease: cubic-bezier(0.77, 0, 0.1, 1.39);
--scale: 1;
}

.CustomModalAbsolute {
position: absolute;
}
56 changes: 48 additions & 8 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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,
})
Expand All @@ -16,11 +17,45 @@ function CustomModal() {
<>
{/* `triggerRef` allows the focus to shift to whatever triggered the modal, on close. */}
<button ref={triggerButton} onClick={toggle}>
Open the modal window!
Open the modal (HTMLElement)
</button>
<div ref={modalRootRef}></div>

{isOpen && (
<Modal appendToBody>
{isOpen && modalRootRef.current && (
<Modal renderTo={modalRootRef.current}>
<ModalBackdrop onClick={toggle} className={styles.CustomModalOverlay} />
<ModalContent className={classnames([styles.CustomModalContent])}>
<button onClick={toggle} className={styles.CustomModalClose}>
Close
</button>
<p>Voluptate Lorem ut minim excepteur sit fugiat anim magna aliquip.</p>
</ModalContent>
</Modal>
)}
</>
)
}

function CustomModalOnBody() {
const triggerButton = useRef(null)
const bodyRef = useRef<HTMLElement | null>(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. */}
<button ref={triggerButton} onClick={toggle}>
Open the modal (Append to body)
</button>

{isOpen && bodyRef.current && (
<Modal renderTo={bodyRef.current} className={styles.CustomModalAbsolute}>
<ModalBackdrop onClick={toggle} className={styles.CustomModalOverlay} />
<ModalContent className={classnames([styles.CustomModalContent])}>
<button onClick={toggle} className={styles.CustomModalClose}>
Expand All @@ -36,6 +71,7 @@ function CustomModal() {

function ModalWithHash() {
const triggerButton = useRef(null)
const modalRootRef = useRef(null)
const { isOpen, toggle } = useModal({
triggerRef: triggerButton,
hash: "modal-with-hash",
Expand All @@ -44,11 +80,12 @@ function ModalWithHash() {
return (
<>
<button ref={triggerButton} onClick={toggle}>
Using a hash
Open the modal using a hash
</button>
<div ref={modalRootRef}></div>

{isOpen && (
<Modal>
{isOpen && modalRootRef.current && (
<Modal renderTo={modalRootRef.current}>
<ModalBackdrop onClick={toggle} className={styles.CustomModalOverlay} />
<ModalContent className={styles.CustomModalContent}>
<button onClick={toggle} className={styles.CustomModalClose}>
Expand All @@ -66,8 +103,11 @@ function App() {
return (
<>
<CustomModal />
<CustomModalOnBody />
<ModalWithHash />
<a href="#modal-with-hash">Open modal from anchor without events</a>
<a href="#modal-with-hash">
Open modal using exisiting hash (#modal-with-hash) without events
</a>
</>
)
}
Expand Down