1

I have read a lot of similar articles like the following items:

  1. Detect click outside the react component
  2. Click outside of clickable components in React
  3. etc.

And I have read many of their answers, but it doesn't help me to solve my problem.

I have a navbar component, which has a bars icon on it. whenever a user clicks on it, it should show the sidebar component and in case the user clicks outside the sidebar component, it should be on hidden state (like first).

Here is what I have implemented:

navbar.js

import React, {Component} from 'react';
import {Icon} from 'antd';

import Sidebar from '../sidebar/sidebar.js';
import '../../css/navbar.css';

class Navbar extends Component {
    constructor(props){
        super(props);
        this.state = {
            sidebarVisible: false
        }
        this.sidebarShow = this.sidebarShow.bind(this);
        this.sidebarHide = this.sidebarHide.bind(this);
    }

    sidebarShow(){
        this.setState({
            sidebarVisible: true
        })
        document.addEventListener('click', this.sidebarHide);
    }

    sidebarHide(){
        this.setState({
            sidebarVisible: false
        })

        document.removeEventListener('click', this.sidebarHide)
    }

    render (){
        return (
            <div className="nav-container">

                <div className="bar" onClick={() => this.sidebarShow()} >
                    <Icon type="bars"/>
                </div>

                <div className="nav-logo">
                    خفت کتاب
                </div>

                {this.state.sidebarVisible ? <Sidebar/> : null}

            </div>
        )
    }
}

export default Navbar

sidebar.js

import React, { Component } from 'react'
import {Icon} from 'antd';

import '../../css/sidebar.css'

class Sidebar extends Component {
    render(){
        return (
            <div className="sidebar">
                <div className="sidebar-user">
                    <div className="sidebar-profile">
                        <img src={require('../../images/personal.jpg')}/>
                    </div>
                    <div className="sidebar-welcome">
                        مصطفی قدیمی
                    </div>
                </div>
                <div className="sidebar-active sidebar-elements">
                    <div className="sidebar-icon">
                        <Icon type="home" />
                    </div>
                    <div className="sidebar-title">
                        خانه
                    </div>
                </div>

                <div className="sidebar-elements">
                    <div className="sidebar-icon">
                        <Icon type="book" />
                    </div>
                    <div className="sidebar-title">
                        ثبت کتاب
                    </div>
                </div>

                <div className="sidebar-elements">
                    <div className="sidebar-icon">
                        <Icon type="info-circle" />
                    </div>
                    <div className="sidebar-title">
                        درباره ما
                    </div>
                </div>

                <div className="sidebar-elements">
                    <div className="sidebar-icon">
                        <Icon type="mail" />
                    </div>
                    <div className="sidebar-title">
                        تماس با ما
                    </div>
                </div>

            </div>
        )
    }
}

export default Sidebar;

sidebar.css

.sidebar {
    position: fixed;
    width: 250px;
    height: 100vh;
    background-color: #001529;
    z-index: 1;
    top: 0;
    right: 0;
    color: white;
    animation-name: 'sidebar';
    animation-duration: 1s;
    animation-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
}

.sidebar > div {
    padding: 10px;
}


.sidebar-elements {
    background-color: #334454;
    opacity: 0.4;
    display: grid;
    grid-template-columns: 40px auto;
    margin-bottom: 5px;
}

.sidebar-elements:hover {
    opacity: 1;
    transition-duration: .5s;
}

.sidebar-icon {
    display: flex;
    align-content: center;
}

.sidebar-profile {
    margin: auto;
    background-color: #334454;
    width: 150px;
    height: 150px;
    box-sizing: border-box;
    padding: 0;
    border-radius: 50%;
    position: relative;
    overflow: hidden;
}

.sidebar-profile > img {
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: auto;
    transform: translate(-50%, -50%)
}

.sidebar-welcome {
    margin: auto;
    text-align: center;
    font-size: 14px;
    color: white;
}

.sidebar-active {
    background-color: #1890ff;
    opacity: 1;
}

@keyframes sidebar {
    from {transform: translateX(250px);}
    to {transform: translateX(0);}
}

@-webkit-keyframes sidebar { 
    from {transform: translateX(250px);}
    to {transform: translateX(0);}
}

The main problem of this implementation is whenever the user clicks on click on sidebar component, it becomes invisible. How to prevent it from hiding? Note: Is there any way to add animation when the user click on outside of the component?

Mostafa Ghadimi
  • 5,883
  • 8
  • 64
  • 102
  • it's normal when you show the sidebar you subscribe to event document click document.addEventListener('click', this.sidebarHide). In your sidebar component you add and event click with event stop propagation – drtapha Apr 11 '19 at 19:27
  • 1
    Apart from handling the close event, the main issue seems to be mounting/unmounting the `` component. Notice the `this.state.sidebarVisible ? : null` in `navbar.js` in which the Sidebar is unmounted/removed from the DOM immediately. I'd rather keep the `` and play with its HTML classes during the time to implement the animation. Another alternative might be using something like [Pose](https://popmotion.io/pose/). – Hashem Qolami Apr 17 '19 at 08:49
  • @HashemQolami I didn't get what you mean by `playing with its html classes`. Would you please give me more details of it? – Mostafa Ghadimi Apr 22 '19 at 08:56
  • 1
    @MostafaGhadimi In short, I meant adding or removing the classes. Say `.sidebar-in` and `sidebar-out` are responsible for the animation. At some points (e.g. when the visibility state changes), you should add/remove the classes from the `` to achieve the desired effect. – Hashem Qolami Apr 22 '19 at 14:11

3 Answers3

1

If you want to add animation, you can create a css class for the sidebar component .is-visible and instead of doing:

{this.state.sidebarVisible ? <Sidebar/> : null}

you can do this:

<Sidebar isVisible={this.state.sidebarVisible} />

Now depending on the value of the isVisible prop value, add or remove the is-visible class from the sidebar component's container i.e. <div className="sidebar">

You can do it like this

.sidebar {
  ...,
  width: 320px;
  transition: transform 0.2s linear;
  transform: translateX(-320px); // assuming the sidebar is on the left side
}

.sidebar.is-visible {
  transform: translateX(0);
}

Hope this helps!

Ajay Gupta
  • 1,944
  • 2
  • 21
  • 36
  • Would you please check the post again? I have just added the `sidebar.css`. – Mostafa Ghadimi Apr 11 '19 at 17:58
  • Oh okay, but as I said, instead of doing `{this.state.sidebarVisible ? : null}`, do this `` and depending on the prop value, add or remove the `visible` class – Ajay Gupta Apr 11 '19 at 17:59
  • I have tried, but unfortunately it doesn't work for me! please give me more detail to solve the problem if I made a mistake in your solution. thanks – Mostafa Ghadimi Apr 11 '19 at 18:06
0

The problem were in the place that I have added an eventListener to whole document!

I have changed the style of coding approach (as @AjayGupta said) to the following:

Solution of first problem: It was fixed by reading the trick in this post. To explain it briefly, I have added an ignore variable that whenever the user clicks on the sidebar Component, the function returns and doesn't do anything else.

Sidebar.js

import React, {Component} from 'react';
import {Icon} from 'antd';

import Sidebar from '../sidebar/sidebar.js';
import '../../css/navbar.css';

class Navbar extends Component {
    constructor(props){
        super(props);
        this.state = {
            sidebarVisible: false
        }
        this.sidebarShow = this.sidebarShow.bind(this);
        this.sidebarHide = this.sidebarHide.bind(this);
    }

    sidebarShow(){

        this.setState({
            sidebarVisible: true
        })
        document.querySelector('.sidebar').className = "sidebar sidebar-in"
        document.querySelector('.sidebar').style.display = "block"
        document.addEventListener('click', this.sidebarHide);

    }

    sidebarHide(e){
        // TODO: implement sidebar in a different way

        this.setState({
            sidebarVisible: false
        })

        var ignore = document.querySelector('.sidebar')
        var target = e.target
        if (target === ignore || ignore.contains(target)) {
            return
        }
        document.querySelector('.sidebar').className = "sidebar sidebar-out"
        setTimeout(
            () => document.querySelector('.sidebar').style.display = 'none'
        , 800)
        document.removeEventListener('click', this.sidebarHide)
    }

    render (){
        return (
            <div className="nav-container">

                <div className="bar" onClick={() => this.sidebarShow()} >
                    <Icon type="bars"/>
                </div>

                <div className="nav-logo">
                    خفت کتاب
                </div>

                <Sidebar/>

            </div>
        )
    }
}

export default Navbar

Solution of second problem: I have added two classes (sidebar-in and sidebar-out and add them to the sidebar class in appropriated situations:

sidebar.css:

.sidebar {
    position: fixed;
    width: 250px;
    height: 100vh;
    background-color: #001529;
    z-index: 1;
    top: 0;
    right: 0;
    color: white;
    display: none;
}

.sidebar-in {
    animation-name: 'sidebar-in';
    animation-duration: 1s;
    animation-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
}

.sidebar-out {
    animation-name: 'sidebar-out';
    animation-duration: 1s;
    animation-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
}

@keyframes sidebar-in {
    from {transform: translateX(250px);}
    to {transform: translateX(0);}
}

@keyframes sidebar-out {
    from {transform: translateX(0);}
    to {transform: translateX(250px);}
}
Mostafa Ghadimi
  • 5,883
  • 8
  • 64
  • 102
-1
import '../../css/sidebar.css'

class Sidebar extends Component {
  render(){
    return (
        <div className="sidebar" onClick={event => event.stopPropagation()}>
            <div className="sidebar-user">
                <div className="sidebar-profile">
                    <img src={require('../../images/personal.jpg')}/>
                </div>
                <div className="sidebar-welcome">
                    مصطفی قدیمی
                </div>
            </div>
            <div className="sidebar-active sidebar-elements">
                <div className="sidebar-icon">
                    <Icon type="home" />
                </div>
                <div className="sidebar-title">
                    خانه
                </div>
            </div>

            <div className="sidebar-elements">
                <div className="sidebar-icon">
                    <Icon type="book" />
                </div>
                <div className="sidebar-title">
                    ثبت کتاب
                </div>
            </div>

            <div className="sidebar-elements">
                <div className="sidebar-icon">
                    <Icon type="info-circle" />
                </div>
                <div className="sidebar-title">
                    درباره ما
                </div>
            </div>

            <div className="sidebar-elements">
                <div className="sidebar-icon">
                    <Icon type="mail" />
                </div>
                <div className="sidebar-title">
                    تماس با ما
                </div>
            </div>

        </div>
    )
}

}

export default Sidebar;

drtapha
  • 539
  • 1
  • 7
  • 15