<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React Custom Modal Example & Tutorial</title>
<link rel="stylesheet" href="_content/app.css" />
<link rel="stylesheet" href="_content/modal.css" />
<!-- transpile and run react code in browser with jspm -->
<script src="https://jspm.io/system.js"></script>
<script src="config.js"></script>
<script type="text/javascript">
System.import('./index.jsx');
</script>
</head>
<body>
<div id="app">Loading...</div>
<!-- credits -->
<div class="credits">
<p>
<a href="http://jasonwatmore.com/post/2018/01/23/react-custom-modal-window-dialog-box" target="_top">React - Custom Modal Window / Dialog Box</a>
</p>
<p>
<a href="http://jasonwatmore.com" target="_top">JasonWatmore.com</a>
</p>
</div>
</body>
</html>
import React from 'react';
import { render } from 'react-dom';
import { App } from './App';
render(
<App />,
document.getElementById('app')
);
export * from './App.jsx';
import React from 'react';
import { Router, Route } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
import { HomePage } from '../HomePage';
import { TestPage } from '../TestPage';
class App extends React.Component {
constructor(props) {
super(props);
// this line is required to work on plunker because the app preview runs on a subfolder url
history.push('/');
}
render() {
return (
<Router history={history}>
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/test-page">Test Page</Link>
</nav>
<div>
<Route exact path="/" component={HomePage} />
<Route path="/test-page" component={TestPage} />
</div>
</div>
</Router>
);
}
}
export { App };
/*global System */
'use strict';
System.config({
transpiler: "babel",
packages: {
'./': { defaultExtension: false },
'./_components': { main: 'index.js', defaultExtension: false },
'./App': { main: 'index.js', defaultExtension: false },
'./HomePage': { main: 'index.js', defaultExtension: false },
'./TestPage': { main: 'index.js', defaultExtension: false }
},
paths: {
"*": "https://npm.jspm.io/*.js"
}
});
export * from './JwModal.jsx';
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
id: PropTypes.string.isRequired
};
class JwModal extends React.Component {
constructor(props) {
super(props);
this.state = { isOpen: false };
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
// move element to bottom of page (just before </body>) so it can be displayed above everything else
document.body.appendChild(this.element);
// add this modal instance to the modal service so it's accessible from other components
JwModal.modals.push(this);
}
componentWillUnmount() {
// remove this modal instance from modal service
JwModal.modals = JwModal.modals.filter(x => x.props.id !== this.props.id);
this.element.remove();
}
handleClick(e) {
// close modal on background click
if (e.target.className === 'jw-modal') {
JwModal.close(this.props.id)(e);
}
}
render() {
return (
<div style={{display: + this.state.isOpen ? '' : 'none'}} onClick={this.handleClick} ref={el => this.element = el}>
<div className="jw-modal">
<div className="jw-modal-body">
{this.props.children}
</div>
</div>
<div className="jw-modal-background"></div>
</div>
);
}
}
JwModal.modals = [];
JwModal.open = (id) => (e) => {
e.preventDefault();
// open modal specified by id
let modal = JwModal.modals.find(x => x.props.id === id);
modal.setState({ isOpen: true });
document.body.classList.add('jw-modal-open');
}
JwModal.close = (id) => (e) => {
e.preventDefault();
// close modal specified by id
let modal = JwModal.modals.find(x => x.props.id === id);
modal.setState({ isOpen: false });
document.body.classList.remove('jw-modal-open');
}
JwModal.propTypes = propTypes;
export { JwModal };
/* MODAL STYLES
-------------------------------*/
.jw-modal {
/* modal container fixed across whole screen */
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
/* z-index must be higher than .modal-background */
z-index: 1000;
/* enables scrolling for tall modals */
overflow: auto;
}
.jw-modal-body {
padding: 20px;
background: #fff;
/* margin exposes part of the modal background */
margin: 40px;
}
.jw-modal-background {
/* modal background fixed across whole screen */
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
/* semi-transparent black */
background-color: #000;
opacity: 0.75;
/* z-index must be below .modal and above everything else */
z-index: 900;
}
body.jw-modal-open {
/* body overflow is hidden to hide main scrollbar when modal window is open */
overflow: hidden;
}
/* STYLES FOR EXAMPLE APP
-------------------------------*/
body {
font-family: roboto;
padding: 20px;
}
nav {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #ddd;
}
nav a {
margin-right: 8px;
}
h1 {
font-weight: normal;
margin-top: 0;
}
input[type="text"] {
display:block;
width: 100%;
font-family: roboto;
}
.credits {
margin-top: 30px;
border-top: 1px solid #ddd;
text-align: center;
}
import React from 'react';
import { JwModal } from '../_components';
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
bodyText: 'This text can be updated in modal 1'
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const { name, value } = e.target;
this.setState({ [name]: value });
}
render() {
const { bodyText } = this.state;
return (
<div>
<h1>Home</h1>
<p>{bodyText}</p>
<button onClick={JwModal.open('custom-modal-1')}>Open Modal 1</button>
<button onClick={JwModal.open('custom-modal-2')}>Open Modal 2</button>
<JwModal id="custom-modal-1">
<h1>A Custom Modal!</h1>
<p>
Home page text: <input type="text" name="bodyText" value={bodyText} onChange={this.handleChange} />
</p>
<button onClick={JwModal.close('custom-modal-1')}>Close</button>
</JwModal>
<JwModal id="custom-modal-2">
<h1 style={{height: 1000}}>A Tall Custom Modal!</h1>
<button onClick={JwModal.close('custom-modal-2')}>Close</button>
</JwModal>
</div>
);
}
}
export { HomePage };
export * from './HomePage.jsx';
export * from './TestPage.jsx';
import React from 'react';
class TestPage extends React.Component {
render() {
return (
<div>
<h1>Test Page</h1>
<p>This one doesn't have any modals...</p>
</div>
);
}
}
export { TestPage };