React: Create a Responsive Navbar from Scratch

React: Create a Responsive Navbar from Scratch

I wanted to create a responsive react navbar from scratch. I’m going to do this without using any CSS libraries just because I wanted to see if I can… I also wanted to refresh my memory on React. So here we go.

I hate wordy tutorials, so let’s jump right in. The final product will look like this:

Navbar

Install node (>= 6)

brew install node

Create a react app

npx create-react-app navbar
cd navbar
npm start

What does all this mean?

  • To use react, you need node hence brew install node
  • Use create-react-app to create react boilerplate code
  • cd into the directory
  • npm start the development server

Install fontawesome for react

npm i --save @fortawesome/fontawesome
npm i [email protected]/fontawesome-free-solid
npm i --save @fortawesome/react-fontawesome

Your package.json would simply look like this:

{
  "name": "navbar",
  "version": "0.1.0",
  "private": true,
  "homepage": "./",
  "dependencies": {
    "@fortawesome/fontawesome": "^1.1.8",
    "@fortawesome/fontawesome-free-solid": "^5.0.13",
    "@fortawesome/react-fontawesome": "0.0.19",
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "react-scripts": "1.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

Now let’s go ahead and create the correct structure and files under source. Namely topMenuitemlead, and their relative index.js and index.css. Our goal here is to be as general as possible to create contained reusable objects.

We’ll start with topMenu > index.js

import React, { Component } from 'react';
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import faBars from '@fortawesome/fontawesome-free-solid/faBars'

import Item from './item'
import Lead from './lead'

import './index.css'

class TopMenu extends Component {

  constructor(props) {
    super(props)
    this.state = {
      menu_class: '',
    }
  }

  setToggleTopMenuClass = () => {
    if (this.state.menu_class === '') {
      this.setState({
        menu_class: 'toggled',
      })
    } else {
      this.setState({
        menu_class: '',
      })
    }
  }


  render = () => {
    let top_menu_class = `top-menu ${this.state.menu_class}`
    return (
        <div>
          <div className={top_menu_class} >
            <Lead text="This Is Your Title!" />
            <div className='left'>
              <Item text='Left1'/>
              <Item text='Left2'/>
            </div>
            <div className='right'>
              <Item text='Right1' />
              <Item text='Right2' />
            </div>
            <FontAwesomeIcon icon={faBars} className='top-menu-icon' onClick={this.setToggleTopMenuClass}/>
            <div className='clear-fix' />
          </div>
        </div>
    )
  }
}

export default TopMenu;

So what’s happening?

  • Straightforward imports of fontawesomeitem and lead
  • constructor function that sets the state
  • setToggleTopMenuClass that sets the state to toggled or blank which then is used in index.css to toggle the menu
  • render function renders the component, obviously.

Now onto topMenu > index.css

.top-menu {
    background-color: grey;
    padding: 20px 50px 20px 50px;
    user-select: none;
}

.top-menu > .right {
    float: right;
}

.top-menu > .right > * {
    margin-left: 5px;
}

.top-menu > .left {
    float: left;
}

.top-menu > .left > * {
    margin-right: 5px;
}

.top-menu > .left > *, .top-menu > .right > * {
    padding: 11.4px;
    display: inline-block;
    text-align: center;
    min-width: 50px;
}

.top-menu *:hover{
    cursor: pointer;
}

.clear-fix {
    clear: both;
}

.top-menu .top-menu-icon {
    padding: 10px;
    position: absolute;
    top: 0;
    right: 0;
    display: none;
}

@media screen and (max-width: 600px) {
    .top-menu {
        padding: 40px 20px 20px 20px;
        max-height: 0;
        overflow: hidden;
    }

    .top-menu > .left, .top-menu > .right {
        display: none;
    }

    .top-menu-icon {
        display: block !important;
    }
}

@media screen and (max-width: 600px) {
    .top-menu.toggled {
        padding: 60px 0 0 0;
        max-height: 1500px;
        transition: max-height 1s;
    }

    .top-menu.toggled > .left {
        border-bottom: 1px solid black;
        margin: 15px 0 0 0 ;
    }

    .top-menu.toggled *:not(.top-menu-lead):not(.top-menu-icon) {
        float: none;
        display: block !important;
        text-align: left;
        margin: 0;
    }
}

What’s happening here:

  • We’re setting the topMenu and children’s  CSS properties
  • We’re setting properties for the toggled and un-toggled states when max-width is 600 pixels

item.js and item.css are super simple:

import React, { Component } from 'react';

import './index.css'

class Item extends Component {

    constructor(props) {
        super(props)
        this.text = props.text
    }

    render() {
        return (
            <div className='top-menu-item'>
                {this.text}
            </div>
        )
    }
}

export default Item;
.top-menu-item:hover {
    background-color: white;
}

The code:

  • Sets the item text that gets passed down from topMenu > index.js component namely Left1Left2, etc
  • Sets the background-color to white on hover

lead.js and lead.css are pretty simple too:

import './index.css'

class Lead extends Component {

    constructor(props) {
        super(props)
        this.text = props.text
    }

    render() {
        return (
            <div className='top-menu-lead'>
                {this.text}
            </div>
        )
    }
}

export default Lead;
.top-menu-lead {
    padding: 10px;
    background-color: #000000;
    color: white;
    display: inline-block;
    float: left;
    font-size: 18px;
}

@media screen and (max-width: 600px) {
    .top-menu-lead {
        position: absolute;
        top: 10px;
        left: 10px;
        float: none;
    }
}

The code:

  • Sets the lead text that gets passed down from topMenu > index.js component namely This Is Your Title!
  • tweaks the size and shape of the lead via CSS so that it would stand out from the rest of the menu

And… done!  Simple, eh?

We can add as many menu items as we want, by simply adding a new <Item text='whatever'/> to the TopMenu component under either left or right classes. The Item component is completely independent of TopMenu.