Giter Site home page Giter Site logo

react-modal2's Introduction

react-modal2

Simple modal component for React.

Installation

$ npm install --save react-modal2

Usage

ReactModal2 tries to be as minimal as possible. This means it requires a little bit of setup, but gives you complete flexibility to do what you want.

Let's start off with the actual API of ReactModal2:

<ReactModal2
  // A callback that gets called whenever the `esc` key is pressed, or the
  // backdrop is clicked.
  onClose={this.handleClose.bind(this)}

  // Enable/Disable calling `onClose` when the `esc` key is pressed.
  closeOnEsc={true}

  // Enable/Disable calling `onClose` when the backdrop is clicked.
  closeOnBackdropClick={true}

  // Add a className to either the backdrop or modal element.
  backdropClassName='my-custom-backdrop-class'
  modalClassName='my-custom-modal-class'

  // Add styles to either the backdrop or modal element.
  backdropStyles={{ my: 'custom', backdrop: 'styles' }}
  modalStyles={{ my: 'custom', modal: 'styles' }}>
  ...
</ReactModal2>

If we use it like this it will simply render those two elements in the dom like this:

<div> <!-- Backdrop -->
  <div>...</div> <!-- Modal -->
</div>

However, you likely want to render the modal somewhere else in the DOM (in most cases at the end of the document.body.

For this there is a separate library called React Gateway. You can use it like this:

import {
  Gateway,
  GatewayDest,
  GatewayProvider
} from 'react-gateway';
import ReactModal2 from 'react-modal2';

class Application extends React.Component {
  render() {
    return (
      <GatewayProvider>
        <div className="app">
          <div className="app-content">
            <h1>My Application</h1>
            <Gateway into="modal">
              <ReactModal2 backdropClassName="modal-backdrop" modalClassName="modal">
                ...
              </ReactModal2>
            </Gateway>
          </div>
          <GatewayDest name="modal" className="modal-container"/>
        </div>
      </GatewayProvider>
    );
  }
}

Which will render as:

<div class="app">
  <div class="app-content">
    <h1>My Application</h1>
    <noscript/>
  </div>
  <div class="modal-container">
    <div class="modal-backdrop">
      <div class="modal">...</div>
    </div>
  </div>
</div>

Now this might seem like a lot to do every time you want to render a modal, but this is by design. You are meant to wrap ReactModal2 with your own component that you use everywhere. Your component can add it's own DOM, styles, animations, and behavior.

import React from 'react';
import {Gateway} from 'react-gateway';
import ReactModal2 from 'react-modal2';

export default class MyCustomModal extends React.Component {
  static propTypes = {
    onClose: React.PropTypes.func.isRequired,
    closeOnEsc: React.PropTypes.bool,
    closeOnBackdropClick: React.PropTypes.bool
  };

  getDefaultProps() {
    return {
      closeOnEsc: true,
      closeOnBackdropClick: true
    };
  }

  render() {
    return (
      <Gateway into="modal">
        <ReactModal2
          onClose={this.props.onClose}
          closeOnEsc={this.props.closeOnEsc}
          closeOnBackdropClick={this.props.closeOnEsc}
          backdropClassName='my-custom-backdrop-class'
          modalClassName='my-custom-modal-class'>
          {this.props.children}
        </ReactModal2>
      </Gateway>
    );
  }
}

Then simply setup your application once:

import {
  GatewayDest,
  GatewayProvider
} from 'react-gateway';

export default class Application extends React.Component {
  render() {
    return (
      <GatewayProvider>
        <div className="app">
          <div className="app-content">
            ...
          </div>
          <GatewayDest name="modal" className="modal-container"/>
        </div>
      </GatewayProvider>
    );
  }
}

Then you have your own ideal API for working with modals in any of your components.

import MyCustomModal from './my-custom-modal';

export default class MyComponent extends React.Component {
  state = {
    isModalOpen: false
  };

  handleOpen() {
    this.setState({ isModalOpen: true });
  }

  handleClose() {
    this.setState({ isModalOpen: false });
  }

  render() {
    return (
      <div>
        <button onClick={this.handleOpen.bind(this)}>Open</button>
        {this.state.isModalOpen && (
          <MyCustomModal onClose={this.handleClose.bind(this)}>
            <h1>Hello from Modal</h1>
            <button onClick={this.handleClose.bind(this)}>Close</button>
          </MyCustomModal>
        )}
      </div>
    );
  }
}

Props

Name Type Description
onClose Function Required. A callback to handle an event that is attempting to close the modal.
closeOnEsc Boolean Should this modal call onClose when the esc key is pressed?
closeOnBackdropClick Boolean Should this modal call onClose when the backdrop is clicked?
backdropClassName String An optional className for the backdrop element.
modalClassName String An optional className for the modal element.
backdropStyles Object Optional style for the backdrop element.
modalStyles Object Optional style for the modal element.

Accessibility

One of ReactModal2's opinions is that modals should be as accessible as possible. It does much of the work for you, but there's one little thing you need to help it with.

In order to "hide" your application from screenreaders while a modal is open you need to let ReactModal2 what the root element for your application is.

Note: The root element should not contain the GatewayDest or whereever the modal is getting rendered. This will break all the things.

import ReactModal2 from 'react-modal2';

ReactModal2.getApplicationElement = () => document.getElementById('application');

FAQ

How do I close the modal?

ReactModal2 is designed to have no state, if you put it in the DOM then it will render. So if you don't want to show it then simply do not render it in your parent component. For this reason there is no isOpen property to pass.

react-modal2's People

Contributors

1000hz avatar hawkrives avatar jamiebuilds avatar jaredpalmer avatar jwineman avatar manatarms avatar nadbm avatar ntharim avatar tajo avatar torifat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-modal2's Issues

Modal in File

Hello,

I'm wondering if I have to declare a modal component every time I use this like you did in the redux example? or do I just import react-modal2 like so

import Modal from 'react-modal2'

and declare a

<Modal> ... <Modal>

Interaction with other uses of Esc

I'm using react-modal2 as part of a more complex app that does its own keyboard handling, and I noticed that it doesn't do anything to keep its Esc event from also being seen by other parts of the site. (In my example, I have an Esc handler on my app container, and pressing Esc to close the modal also fires the app's event.)

Possible solutions:

  1. Pass useCapture true to document.addEventListener and document.removeEventListener so that react-modal2's global Esc handler fires before any individual React components'.
  2. Move the keyboard handler to the ref="modal" component; since it captures the focus, it should see any Esc keypresses.

And whether 1 or 2 is chosen, update handleDocumentKeydown to call event.stopImmediatePropagation().

I'm not an expert on DOM events, so I don't know which is better. (2 seems cleaner, but I assume there was some reason for going with document-level events in the current implementation.)

Opt-in auto fucus

Current solution when modal automatically searches for tabbable element on open sometimes leads to not desired behavior.

For instance there is a bunch of links inside modal and action button in the end. I don't want to first link be highlighted when user opens a modal.

I think any tabbable element should not be focused by default, default should be focusing on modal div itself with tabIndex='-1' (a11y-focus-scope does it now only if tabbable element can't be found).

And if you want to focus some element, then set option on react-modal2 to enable current mechanic of searching first tabbable element.

What is 4.0.0

Seems like 3.2.0 is newer than 4.0.0 and contains more fixes according to commits history?

Refs in index.js seems to create trouble in 2.0.0

After migrating from 1.0.3 to 2.0.0, started to receive the following error:

Uncaught Error: Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. 
You might be adding a ref to a component that was not created inside a component's `render` method, 
or you have multiple copies of React loaded (details: https://fb.me/react-refs-must-have-owner).

And it was still persistent even after cleaning my component from using ref altogether.

Once commented out the ref points in index.js got the modal shown without the above error:

setFocusOn(this.refs.modal);

ref: 'backdrop',

ref: 'modal',

By inspecting the difference with 1.0.3, the ref was already used. Any idea what could have made the difference?

I am using the ReactGateway wrapped around the modal, as it has been suggested in the README.md.

Modal breaks popovers, dropdowns

It appears that react-modal2 breaks dialogues (components that also use react-gateway) that require focus (e.g., inputs). This seems to be an effect of Modal.js#L10.

I'm not sure if I've seen this from other libraries, but it seems unnecessary. I also didn't find any tests for this ๐Ÿค” .

Anybody else having issues with this? ๐Ÿ˜“

Animated example breaks with multiple dialogs

When rendering multiple modal dialogs based on the example in https://github.com/cloudflare/react-modal2/blob/master/example/animated/script.js only one of them will ever work, as only a single component can be rendered into a given GatewayDest. This applies even if one one modal is ever opened at a time, so long as there is more than one instance of the app-specific Modal class.

As a workaround I'm using CSSTransitionGroup as the tagName for GatewayDest so that the transition group is always rendered, which works as long as at most one modal is rendered into it at any given time.

Here's the code I'm using

const Modal = propTypes({
  onBackgroundClick: PropTypes.func,
  show: PropTypes.bool.isRequired,
  children: PropTypes.node,
  className: PropTypes.string
})(defaultProps({
  onBackgroundClick: event => void 0
})(props => {
  const { show, children, className, onBackgroundClick } = props;
  return show ? <Gateway into='modal'>
    <ReactModal
      onClose={onBackgroundClick}
      modalClassName={classNames('reveal-modal', className)}
      backdropClassName='reveal-modal-bg'
    >
      { children }
    </ReactModal>
  </Gateway> : <div/>;
}));

export class ModalWrapper extends Component {

  static init() {
    ReactModal.getApplicationElement = () => document.getElementById('app-content');
  }

  render() {
    return <GatewayProvider>
      <application>
        <div id="app-content">
          {this.props.children}
        </div>
        <GatewayDest
          name="modal"
          className="modal-container"
          tagName={TransitionGroup}
          transitionName='modal'
          transitionEnterTimeout={300}
          transitionLeaveTimeout={300}
        />
      </application>
    </GatewayProvider>;
  }
}

Note that the above produces a bogus prop type warning in development mode because the tagName proptype is set as string even though any value suitable for React.createElement is accepted.

Warning Use prop-type package

I updated to new version but still getting it -

Warning: Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm

Name

react-modal2 is a dumb name and I want to change it, but I have no idea what to call it.

Suggestions?

window is not defined

One more thing, if using with server side rendering, there is a window is not defined error occurring due to the focusin package used in your a11y-focus-scope module.

Error on /
  ReferenceError: window is not defined

  - focusin.js:2 Object.<anonymous>
    [diego-web]/[react-modal2]/[a11y-focus-scope]/[focusin]/focusin.js:2:9

  - node.js:214 Object.require.extensions.(anonymous function) [as .js]
    [diego-web]/[babel-core]/lib/api/register/node.js:214:7

  - index.js:4 Object.<anonymous>
    [diego-web]/[react-modal2]/[a11y-focus-scope]/index.js:4:1

  - node.js:214 Object.require.extensions.(anonymous function) [as .js]
    [diego-web]/[babel-core]/lib/api/register/node.js:214:7

Here is the problem, this should be scoped to a function:

var w = window
var d = w.document

if (w.onfocusin === undefined) {
  d.addEventListener('focus', addPolyfill, true)
  d.addEventListener('blur', addPolyfill, true)
  d.addEventListener('focusin', removePolyfill, true)
  d.addEventListener('focusout', removePolyfill, true)
}

function addPolyfill (e) {
  var type = e.type === 'focus' ? 'focusin' : 'focusout'
  var event = new window.CustomEvent(type, { bubbles: true, cancelable: false })
  event.c1Generated = true
  e.target.dispatchEvent(event)
}

function removePolyfill (e) {
  if (!e.c1Generated) {
    d.removeEventListener('focus', addPolyfill, true)
    d.removeEventListener('blur', addPolyfill, true)
    d.removeEventListener('focusin', removePolyfill, true)
    d.removeEventListener('focusout', removePolyfill, true)
  }
  setTimeout(function () {
    d.removeEventListener('focusin', removePolyfill, true)
    d.removeEventListener('focusout', removePolyfill, true)
  })
}

Modal closes on partial click

When the user starts a click on the modal and ends it elsewhere (or vice versa) the modal is not prevented from closing. This is not a great experience, since the user might be trying to select text inside of an input or accidentally started their click outside the modal and doesn't intend to actually close it.

Handle scrollbar compensation

Usually, you would set overflow: hidden to body when the modal mounts. However, this causes a weird adjustment to the layout (because the scrollbar disappears). This mostly affects Windows/Linux users, and some OSX users that use a mouse (because it forces the scrollbar to appear).

As an example, compare Semantic UI's Modal and TWBS' Modal. TWBS solved this by adding a padding-right to the body when the modal mounts.

* I'm not sure whether this would make this project more opinionated. Moreover, I'm torn whether this belongs to the core functionality or to userland code.

attribute styles => style

Hey, I guess you might have gotten a bit confused with using react-native, you used a 'styles'-attribute instead of 'style', therefore inline styles won't be applied.

Good job on everything else though.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.