Categories
React Tutorial

React: Create a Responsive Navbar from Scratch

react

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.

If you’d like to learn React in depth, I’ve written a series of tutorials that will teach you React from scratch. Otherwise, carry on.

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

navigation bar
Navigation Bar
Collapsed on Small Screen
Toggled on Small Screen

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 lets go ahead and create the correct structure and files under source. Namely topMenuitem, lead, and their relative index.js and index.css. Our goal here is to be as general as possible to create contained reusable objects.

File Structure

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 whats 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;
    }
}

Whats 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 React, { Component } from 'react';

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.

You can see the navbar in action here