Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add keepMounted option and fix screen glitch #516

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 21 additions & 3 deletions react-responsive-modal/src/index.tsx
Expand Up @@ -158,6 +158,11 @@ export interface ModalProps {
* Callback fired when the Modal has exited and the animation is finished.
*/
onAnimationEnd?: () => void;
/**
* Keeps the Modal mounted even when it is hidden. This is useful for keeping the DOM state
* inside the Modal as well as for SEO purposes.
*/
keepMounted?: boolean;
children?: React.ReactNode;
}

Expand Down Expand Up @@ -187,6 +192,7 @@ export const Modal = React.forwardRef(
onEscKeyDown,
onOverlayClick,
onAnimationEnd,
keepMounted = false,
children,
reserveScrollBarGap,
}: ModalProps,
Expand All @@ -206,6 +212,9 @@ export const Modal = React.forwardRef(
// it will match the server rendered content
const [showPortal, setShowPortal] = useState(false);

// Used to hide the modal when keepMounted is true
const [display, setDisplay] = useState(false);

// Hook used to manage multiple modals opened at the same time
useModalManager(refModal, open);

Expand Down Expand Up @@ -260,10 +269,13 @@ export const Modal = React.forwardRef(
useEffect(() => {
// If the open prop is changing, we need to open the modal
// This is also called on the first render if the open prop is true when the modal is created
if (open && !showPortal) {
if ((open || keepMounted) && !showPortal) {
setShowPortal(true);
handleOpen();
}
if (open && !display) {
setDisplay(true);
}
}, [open]);

const handleClickOverlay = (
Expand Down Expand Up @@ -293,7 +305,10 @@ export const Modal = React.forwardRef(

const handleAnimationEnd = () => {
if (!open) {
setShowPortal(false);
setDisplay(false);
if (!keepMounted) {
setShowPortal(false);
}
}

onAnimationEnd?.();
Expand All @@ -313,7 +328,10 @@ export const Modal = React.forwardRef(
? ReactDom.createPortal(
<div
className={cx(classes.root, classNames?.root)}
style={styles?.root}
style={{
...styles?.root,
...(display ? {} : { display: 'none', pointerEvents: 'none' }),
}}
data-testid="root"
>
<div
Expand Down
26 changes: 17 additions & 9 deletions react-responsive-modal/styles.css
Expand Up @@ -22,23 +22,21 @@
outline: 0;
overflow-x: hidden;
overflow-y: auto;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}

/* Used to trick the browser to center the modal content properly */
.react-responsive-modal-containerCenter:after {
width: 0;
height: 100%;
.react-responsive-modal-containerCenter::before,
.react-responsive-modal-containerCenter::after {
flex: 1 1 0;
content: '';
display: inline-block;
vertical-align: middle;
}

.react-responsive-modal-modal {
flex: 0 0 auto;
max-width: 800px;
display: inline-block;
text-align: left;
vertical-align: middle;
background: #ffffff;
box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.25);
margin: 1.2rem;
Expand All @@ -62,6 +60,7 @@
0% {
opacity: 0;
}

100% {
opacity: 1;
}
Expand All @@ -71,6 +70,7 @@
0% {
opacity: 1;
}

100% {
opacity: 0;
}
Expand All @@ -81,6 +81,7 @@
transform: scale(0.96);
opacity: 0;
}

100% {
transform: scale(100%);
opacity: 1;
Expand All @@ -92,8 +93,15 @@
transform: scale(100%);
opacity: 1;
}

100% {
transform: scale(0.96);
opacity: 0;
}
}

.react-responsive-modal-overlay,
.react-responsive-modal-container,
.react-responsive-modal-modal {
animation-fill-mode: forwards !important;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please explain why this is needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes a separate issue #495. I copied the solution from one of the people in that thread. To be honest, I'm not sure why it helps, but it does.

}
2 changes: 2 additions & 0 deletions website/src/components/ExampleRendered.tsx
Expand Up @@ -3,6 +3,7 @@ import ExampleMultiple from '../examples/Multiple';
import LongContent from '../examples/LongContent';
import FocusTrapped from '../examples/FocusTrapped';
import FocusTrappedInitialFocus from '../examples/FocusTrappedInitialFocus';
import KeepMounted from '../examples/KeepMounted';
import CustomCssStyle from '../examples/CustomCssStyle';
import CustomAnimation from '../examples/CustomAnimation';
import CustomCloseIcon from '../examples/CustomCloseIcon';
Expand All @@ -14,6 +15,7 @@ const examples: Record<string, () => JSX.Element> = {
longContent: LongContent,
focusTrapped: FocusTrapped,
focusTrappedInitialFocus: FocusTrappedInitialFocus,
keepMounted: KeepMounted,
customCssStyle: CustomCssStyle,
customAnimation: CustomAnimation,
customCloseIcon: CustomCloseIcon,
Expand Down
22 changes: 22 additions & 0 deletions website/src/docs/index.mdx
Expand Up @@ -8,6 +8,7 @@ A simple responsive and accessible react modal.
- Multiple modals.
- Accessible modals.
- Easily customizable via props.
- Optionally keep hidden modals mounted to the DOM
- Typescript support
- [Small bundle size](https://bundlephobia.com/result?p=react-responsive-modal)

Expand Down Expand Up @@ -103,6 +104,26 @@ You can also set to trap focus within the modal, but decide where to put focus w

```

### Keeping a hidden modal mounted

By setting the `keepMounted` property to `true`, you can specify that the modal should always be mounted to the DOM, even when hidden it is hidden.
Copy link

@akoll akoll Apr 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a small typo:

Suggested change
By setting the `keepMounted` property to `true`, you can specify that the modal should always be mounted to the DOM, even when hidden it is hidden.
By setting the `keepMounted` property to `true`, you can specify that the modal should always be mounted to the DOM, even when it is hidden.

Instead of removing the DOM nodes, the modal is hidden using `display: none`.

```js
<Modal
...
keepMounted
>
...
</Modal>
```

This is useful when you want the state of the DOM nodes (e.g. the text inside an `<input>` element) to stay alive, as well as for search engine optimization (Google can see the content of the hidden modal).

Press the button below and watch the element tree in your browser. The modal is a `<div>` at the very bottom of the `<body>`, and will not disappear even when the modal is closed!

<ExampleRendered name="keepMounted" />

### Custom styling with css

Customising the Modal style via css is really easy. For example if you add the following css to your app you will get the following result:
Expand Down Expand Up @@ -205,6 +226,7 @@ By default, the Modal will be rendered at the end of the html body tag. If you w
| **onEscKeyDown\*** | `(event: KeyboardEvent) => void` | | Callback fired when the escape key is pressed. |
| **onOverlayClick\*** | `(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void` | | Callback fired when the overlay is clicked. |
| **onAnimationEnd\*** | `() => void` | | Callback fired when the Modal has exited and the animation is finished. |
| **keepMounted** | `boolean` | false | Keeps the Modal mounted to the DOM tree even when it is hidden. This is useful for keeping DOM state inside the Modal as well as for SEO purposes. |

## License

Expand Down
32 changes: 32 additions & 0 deletions website/src/examples/KeepMounted.tsx
@@ -0,0 +1,32 @@
import React from 'react';
import { Modal } from 'react-responsive-modal';

const App = () => {
const [open, setOpen] = React.useState(false);

return (
<>
<button className="button" onClick={() => setOpen(true)}>
Open modal
</button>

<Modal open={open} onClose={() => setOpen(false)} keepMounted center>
<div style={{ padding: '24px' }}>
This modal will stay mounted even when closed.
<br />
<br />
<input
defaultValue="And DOM state of this <input> will be kept alive!"
style={{
border: '1px solid black',
borderRadius: '4px',
padding: '2px',
}}
/>
</div>
</Modal>
</>
);
};

export default App;
8 changes: 8 additions & 0 deletions website/src/pages/index.tsx
Expand Up @@ -46,6 +46,14 @@ const IndexPage = () => (
Focus Trapped modal
</a>
</li>
<li className="mb-4">
<a
className="text-sm hover:text-watermelon"
href="#keeping-a-hidden-modal-mounted"
>
Keeping a hidden modal mounted
</a>
</li>
<li className="mb-4">
<a
className="text-sm hover:text-watermelon"
Expand Down