DEV Community

Takane Ichinose
Takane Ichinose

Posted on

#CodepenChallenge Navigation: Responsive Glassy Navbar

Mobile-first responsive navigation menu design concept.

The structure of the links is just exactly the same as how I forked it. It is like, I just added some classes, and some elements, example for icons, or the text itself.

The menu and sub-menu was created with recursive function.


How I did it

Styling

First, using the default layout in HTML, I tried to style the navigation menu. There is no crucial part in styling, it is just how you want the navigation to look.

Rendering

Honestly, because I really want to use the GSAP, I brute-forced this one. So if there is anyone who has better code for this, please comment down below.

// This is the recursive function that will create the elements
createMenuJSX(menu = this.props.menu) {
  // The array that will be rendered
  let link = [];

  for (let i in menu) {
    let m  = menu[i];
    let ic = <i className="cpc-icon cpc-hidden fas fa-caret-down"></i>;

    if (typeof m.icon !== 'undefined') {
      ic = <i className={'cpc-icon ' + m.icon}></i>;
    }

    // if 'menu' object is undefined (if it doesn't have a sub menu),
    // just show content.
    if (typeof m.menu === 'undefined') {
      // Note the useless elements here, this is to maintain balance
      // between the texts and icons. I think there is a better way
      // here, but I ran out of ideas, so I kept it like this.
      link.push(
        <li>
          <a href={m.link}>
            {ic}
            <span>{i}</span>
            <i className="cpc-caret cpc-hidden fas fa-caret-down"></i>
          </a>
        </li>
      );
    } else if (typeof m.menu === 'object') {
      let tmpSubmenu = this.state.submenu;
      let tmpLength  = tmpSubmenu.length;

      // Create a temporary array. This will be used later for
      // rendering, as well as the 'ref' for GSAP animation.
      tmpSubmenu.push({
        'id': m.link,
        'active': false,
        'caret': React.createRef(),
        'sub': React.createRef()
      });

      // The click event handler is here.
      // The caret and sub menu ref is set here. As mentioned earlier
      // I need this to use the GSAP as animation. If I would not use
      // it, I can easily set the class, and will not use brute force
      // in rendering these elements. I can directly put this method
      // as rendering method in render() method.
      link.push(
        <li>
          <a
            href={m.link}
            onClick={this.menuClickEvent.bind(this, tmpLength)}
          >
            {ic}
            <span>{i}</span>
            <i
              className="cpc-caret fas fa-caret-down"
              ref={tmpSubmenu[tmpLength].caret}
            ></i>
          </a>
          <ul className="cpc-sub" ref={tmpSubmenu[tmpLength].sub}>
            {this.createMenuJSX(m.menu)}
          </ul>
        </li>
      );

      this.setState({submenu: tmpSubmenu});
    }
  }

  return link;
}

// I used the created jsxData state to display the elements.
render() {
  return (
    <nav className="cpc-menu">
      <ul className="cpc-main">
        {this.state.jsxData}
      </ul>
    </nav>
  );
}
Enter fullscreen mode Exit fullscreen mode

Changing the state

Now, we already rendered the elements, let's go to the click events, to show the sub menu of the menu with a caret.

menuClickEvent(i) {
  let submenu = this.state.submenu;
  let tmpmenu = submenu[i];
  // This is how you get the element that was set as a 'ref'
  let sub     = tmpmenu.sub.current;
  let caret   = tmpmenu.caret.current;

  if (tmpmenu.active === false) {
    tmpmenu.active = true;

    // GSAP animation for caret. Notice that I used the ref
    // for this one.
    TweenLite.to(caret, 1, {
      transform: 'rotate(180deg)',
      ease: Elastic.easeOut.config(1, 0.3)
    });

    // GSAP animation for sub menu. Notice that I used the ref
    // for this one.
    TweenLite.to(sub, 1, {
      height: sub.scrollHeight,
      visibility: 'visible',
      ease: Elastic.easeOut.config(1, 0.3)
    });
  } else {
    tmpmenu.active = false;

    // GSAP animation for caret. Notice that I used the ref
    // for this one.
    TweenLite.to(caret, 1, {
      transform: 'rotate(0deg)',
      ease: Elastic.easeOut.config(1, 0.3)
    });

    // GSAP animation for sub menu. Notice that I used the ref
    // for this one.
    TweenLite.to(sub, 0.5, {
      height: 0,
      ease: Bounce.easeOut
    }).eventCallback('onComplete', () => {
      // 'eventCallback' will be called after some events was called,
      // like, start, stop, complete, etc. In my case, I used complete.
      TweenLite.to(sub, 0, {
        visibility: 'hidden'
      })
    });
  }

  submenu[i] = tmpmenu;

  this.setState({submenu: submenu});
}
Enter fullscreen mode Exit fullscreen mode

So, this is how this menu and sub-menu is working. If you have better suggestion for this one, please comment down below.

You'll notice that I coded it crappy. It is because I am not yet good in ReactJS. I'm still practicing it.

Conclusion

To conclude this challenge, it was fun, that I learned many things about react. While I'm coding, of course, I am reading the documentation. This is my first time to do the recursion in ReactJS. Well, it is just the same as usual recursion actually. I also want to know more about GSAP animation, because I've been coding the animation at CSS only.

Resources

Resources/Libraries/Frameworks:
Google Fonts: Montserrat
Font Awesome: Icons
ReactJS: Functionality and rendering
GSAP: Animation

Top comments (0)