Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Adding the modal

  1. Requirements for using semantic-ui-react modal
    1. Add the dependencies to package.json...if they are not:

      Code Block
      languagexml
      themeDJango
      "semantic-ui-react":"^0.67.1",
       "focus-trap-react":"^2.0.0",


    2. Import all the semantic-ui-modal components needed, the focus trap  and react moduls in the new component:

      Code Block
      languagejs
      themeDJango
      import React from 'react';
      import { Button, Icon, Modal, Container, Segment, Menu,Label,Input,Divider} from 'semantic-ui-react';
      import FocusTrap from 'focus-trap-react';


  1. Create the modal class, extending from React.Component as usual:

    Code Block
    languagejs
    themeDJango
    class AccesibleModal extends React.Component{
    ...
    }


  2. Prepare the constructor with the initial state, which will control if the modal will be displaying or not, and also, if the focus will be trapped to the modal or not, from the begining. For example, in this example the modal will be opened after pressing a button which is located in the same component.

    Code Block
    languagejs
    themeDJango
    class AccesibleModal extends React.Component{
        constructor(props) {
            super(props);
    
            this.state = {
                openModal: false,           
                activeTrap: false
            };
    
        }
    }


  3. Create the methods to open and close the modal. Also, the method needed to unmount the trap focus component. 

    1. The method which opens the modal should change the state of the modal component and also set the aria-hidden label of the application to true. In fact, the element #app is the div element which contains all the SlideWiki page. It is defined in the 'components/DefaultHTMLLayout.js' component, at line 26:

      Code Block
      languagexml
      themeDJango
      titleDeafultHTMLLayout.js
      ...
      <body>
      	<div id="app" aria-hidden = "false" dangerouslySetInnerHTML={{__html: this.props.markup}}></div>
      ....



      Thus, the following method will be enough to open the modal:

      Code Block
      languagejs
      themeDJango
      handleOpen(){
              $('#app').attr('aria-hidden','true');
              this.setState({
                  modalOpen:true,
                  activeTrap:true
              });
      }

      We can bind it at the constructor also:

      Code Block
      languagejs
      themeDJango
       constructor(props) {
              super(props);
      
              this.state = {
                  openModal: false,
                  activeTrap: false
              };
              this.handleOpen = this.handleOpen.bind(this);
              
        }


    2. The method which close the modal should change the state of the modal component and also set the aria-hidden label of the application to false. 


      Code Block
      languagejs
      themeDJango
       handleClose(){
      
              $('#app').attr('aria-hidden','false');
      
              this.setState({
                  modalOpen:false,
                  activeTrap: false
              });
       }



    3. The method which unmounts the focus trap compoenent should also change the state of the component. If we don't use it, if the user exits pressing outside the modal, the state of the component could be inconsistent. 

      Code Block
      languagejs
      themeDJango
      unmountTrap(){
              if(this.state.activeTrap){
                  this.setState({ activeTrap: false });
                  $('#app').attr('aria-hidden','false');
              }
      }


    4. Final constructor method: 

      Code Block
      languagejs
      themeDJango
      constructor(props) {
              super(props);
      
              this.state = {
                  openModal: false,
                  activeItem: 'MyDecks',
                  activeTrap: false
              };
      
              this.handleOpen = this.handleOpen.bind(this);
              this.handleClose = this.handleClose.bind(this);
              this.unmountTrap = this.unmountTrap.bind(this);
         }


  4. Add the modal to the render method, adding all the aria-labels required

    Code Block
    languagejs
    themeDJango
    <Modal trigger={
                        <Button as="button" 
                          type="button" aria-label="Open Button" data-tooltip="Open Button" aria-hidden={this.state.modalOpen}
                          basic  onClick={this.handleOpen} > Open Button
                        </Button>
                       }
                    open={this.state.modalOpen}
                    onClose={this.handleClose}
                    size="large"
                    role="dialog"
                    id="exampleModal"
                    aria-labelledby="exampleModalHeader"
                    aria-describedby="exampleModalDescription"              
                    tabIndex="0">
    </Modal>


    • Trigger: Defines the button which will open the modal.You can see different attributes needed for making it fully accesible:
      • as="button", because if not use it, semantic-ui-react render it as a div, and it is problematic
      • aria-label="Open Button",  it is required. Should contain the name of the button displayed, or it its explanation in case of a icon button.
      • data-tooltip="Open Button", extra information displayed when you pass over the button
      • aria-hidden={this.state.modalOpen}, hides the button for reader when modal is open
      • onClick={this.handleOpen}, associaction to the previously handleOpen method (which is binded in the constructor)
    • open={this.state.modalOpen}, it constrols if the modal is displayed (true) or not (false). Notice that in this case there is a difference with recpect semantic-ui. when the modal is not open, also is not rendered in the final DOM, so the components it has inside are not available.

  5. Add the focus-trap component. Modals from semantic-ui and semantic-ui-react do not trap the focus, so when users taps over the last element of the modal, the focus goes outside the modal. This make them no accesibles. In order to avoid that, we should use the focus-trap-react component. The only matter is that component destroys a little the final layout. After many tests, we found a good way to introduce it with less impact: adding it under <Modal>, and surrounding the rest of the elements of the modal:


    Code Block
    languagejs
    themeDJango
    <Modal trigger={
                        <Button as="button" 
                          type="button" aria-label="Open Button" data-tooltip="Open Button" aria-hidden={this.state.modalOpen}
                          basic  onClick={this.handleOpen} > Open Button
                        </Button>
                       }
                    open={this.state.modalOpen}
                    onClose={this.handleClose}
                    size="large"
                    role="dialog"
                    id="exampleModal"
                    aria-labelledby="exampleModalHeader"
                    aria-describedby="exampleModalDescription"              
                    tabIndex="0">
    
    
      		<FocusTrap
                            id='focus-trap-exampleModal'
                            className = "header"
                            active={this.state.activeTrap}
                            focusTrapOptions={{
                                onDeactivate: this.unmountTrap,
                                clickOutsideDeactivates: true,
                                initialFocus: '#firstElement',
                            }}>
    
    
    		{/*elements of the modal here*/}
    
    
    	</FocusTrap>
    </Modal>


      • className="header": using header as extra class, the impact in the final layout is reduced
      • active={this.state.activeTrap}, controls if the focus is trap or not
      • aria-label="Open Button",  it is required. Should contain the name of the button displayed, or it its explanation in case of a icon button.
      • focusTrapOptions.onDeactivate:this.unmountTrap, In this property we add the call to the method which ensures the trap component is unmount, even the user exits the modal pressing outside 
      • focusTrapOptions.clickOutsidesDeactivates:true, also, it is the default value..
      • initialFocus:'#firsElement', the id of the element which will receive the focus. I notice that f you don't indicate it, the modal does not receive the focus well
         
  6. Add Modal Elements. Modals from semantic-ui and semantic-ui-react do not trap the focus, so when users taps over the last element of the modal, the focus goes outside the modal. This make them no accesibles. In order to avoid that, we should use the focus-trap-react component. The only matter is that component destroys a little the final layout. After many tests, we found a good way to introduce it with less impact: adding it under <Modal>, and surrounding the rest of the elements of the modal:


Code Block
languagejs
themeDJango
<Modal trigger={
                    <Button as="button" 
                      type="button" aria-label="Open Button" data-tooltip="Open Button" aria-hidden={this.state.modalOpen}
                      basic  onClick={this.handleOpen} > Open Button
                    </Button>
                   }
                open={this.state.modalOpen}
                onClose={this.handleClose}
                size="large"
                role="dialog"
                id="exampleModal"
                aria-labelledby="exampleModalHeader"
                aria-describedby="exampleModalDescription"              
                tabIndex="0">


  		<FocusTrap
                        id='focus-trap-exampleModal'
                        className = "header"
                        active={this.state.activeTrap}
                        focusTrapOptions={{
                            onDeactivate: this.unmountTrap,
                            clickOutsideDeactivates: true,
                            initialFocus: '#firstElement',
                        }}>


		{/*elements of the modal here*/}


	</FocusTrap>
</Modal>


    • className="header": using header as extra class, the impact in the final layout is reduced
    • active={this.state.activeTrap}, controls if the focus is trap or not
    • aria-label="Open Button",  it is required. Should contain the name of the button displayed, or it its explanation in case of a icon button.
    • focusTrapOptions.onDeactivate:this.unmountTrap, In this property we add the call to the method which ensures the trap component is unmount, even the user exits the modal pressing outside 
    • focusTrapOptions.clickOutsidesDeactivates:true, also, it is the default value..
    • initialFocus:'#firsElement', the id of the element which will receive the focus. I notice that f you don't indicate it, the modal does not receive the focus well


Add the modal to the render method, adding all the aria-labels requiredhh

...