Once you have completed the right Setup following the first part of these tutorials:
Building a React Redux compatible hamburger menus - Setup(part 1)
You're ready to continue with the actual implementation.
Since we want to control when the burger menu is opened/closed using actions let's start with the actual component and containers related to that
Let's start by creating the src/components/
, styles
and src/containers/
folders:
mkdir src/components
mkdir src/containers
mkdir src/styles
Let's continue by creating the HamburgerContainer
in order to map the actions to be dispatched to component
and show/hide
the NavBarMenu
src/containers/HamburgerIconContainer.js
import { connect } from "react-redux";
import {
hideMobileNavigationMenu,
showMobileNavigationMenu
} from "../actions/BurgerAction";
import HamburgerIcon from "../components/HamburgerIcon/HamburgerIcon";
const mapStateToProps = (state, ownProps) => {
return {
...ownProps
}
};
const mapDispatchToProps = (dispatch) => {
return {
onHideMobileNavigationMenu: () => dispatch(hideMobileNavigationMenu()),
onShowMobileNavigationMenu: () => dispatch(showMobileNavigationMenu())
}
};
const HamburgerIconContainer = connect(
mapStateToProps,
mapDispatchToProps
)(HamburgerIcon);
export default HamburgerIconContainer;
Now, let's declare the component HamburgerIcon
src/components/HamburgerIcon/HamburgerIcon.js
import React, { Component } from "react";
import "./HamburgerIcon.scss";
class HamburgerIcon extends Component{
constructor(props) {
super(props);
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler() {
const {
showSideBarMenu,
onShowMobileNavigationMenu,
onHideMobileNavigationMenu
} = this.props;
showSideBarMenu ? onShowMobileNavigationMenu() : onHideMobileNavigationMenu();
}
render() {
return(
<div className="burger-container">
<a className="burger-icon-link" onClick={this.onClickHandler} href="#">
<span className="burger-span-element white-burger-icon-color"></span>
<span className="burger-span-element white-burger-icon-color"></span>
<span className="burger-span-element white-burger-icon-color"></span>
</a>
</div>
);
}
}
export default HamburgerIcon;
Now, we need to define our styles:
src/components/HamburgerIcon/HamburgerIcon.scss
@import "../../styles/responsiveness";
.burger-container {
display: inline-block;
height: 36px;
margin-left: 10px;
margin-top: 7px;
width: 33px;
@include mq-desktop {
display: none;
}
}
.burger-icon-link {
display: inline-block;
line-height: 8.5px;
text-decoration: none;
width: 33px;
}
.white-burger-icon-color {
background-color: black !important;
}
.active-slide-panel {
margin-left: 20px;
margin-top: 19px;
}
.burger-span-element {
display: inline-block;
height: 3px;
width: 30px;
}
We will use a couple of SASS files as utilities for media queries, mixin functions and variables:
For Media queries
src/styles/responsiveness.scss
@mixin mq-desktop($orientation: '') {
@if $orientation == '' {
@media (min-width: 1024px) { @content; }
} @else {
@media (min-width: 1024px) and (orientation: $orientation) { @content; }
}
}
@mixin mq($max-width, $orientation: '') {
@if $orientation == '' {
@media (max-width: $max-width) { @content; }
} @else {
@media (max-width: $max-width) and (orientation: $orientation) { @content; }
}
}
For variables
src/styles/variables.scss
$screen-xs-min: 480px; // Extra small screen / phone
$screen-sm-min: 768px; // Small screen / tablet
$screen-sm-max: 991px; // Small screens / tablets
$screen-md-min: 992px; // Medium screen / desktop
$screen-md-lg-min: 1100px; // Medium screen / desktop
$screen-lg-min: 1200px; // Large screen / wide desktop
For mixins
src/styles/mixins.scss
@mixin font-smoothing() {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
-ms-border-radius: $radius;
border-radius: $radius;
}
@mixin transition($transition...) {
-moz-transition: $transition;
-o-transition: $transition;
-webkit-transition: $transition;
transition: $transition;
}
@mixin transform($transforms) {
-moz-transform: $transforms;
-o-transform: $transforms;
-ms-transform: $transforms;
-webkit-transform: $transforms;
transform: $transforms;
}
@mixin translate3d($size, $x, $y) {
@include transform(translate3d($size, $x, $y));
}
@mixin Opacity($value){
$IEValue: $value*100;
opacity: $value;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity="+$IEValue+")";
filter: alpha(opacity=$IEValue);
}
and remember to update the main layout to include the hamburger icon
src/DefaultLayout.js
+ import HamburgerIconContainer from "./containers/HamburgerIconContainer.js";
<div className="default-layout">
+ <HamburgerIconContainer showSideBarMenu />
<div className="main-container">
If we followed every step correctly we should make the hamburger icon visible when you reduce the screen size
Now, it's time to implement the actual sidebar menu
Let's start by creating the container
src/containers/NavBarContainer.js
import { connect } from "react-redux";
import NavBar from "../components/NavBar/NavBar";
import {
hideMobileNavigationMenu
} from "../actions/BurgerAction";
const mapStateToProps = (state, ownProps) => {
return {
visibleMobileMenu: state.BurgerMenuReducer.visibleMobileMenu,
...ownProps
}
};
const mapDispatchToProps = (dispatch) => {
return {
onHideMobileNavigationMenu: () => dispatch(hideMobileNavigationMenu())
}
};
const NavBarContainer = connect(
mapStateToProps,
mapDispatchToProps
)(NavBar);
export default NavBarContainer;
Since we want to hide the sidebar menu whenever we navigate to a new section we need to map that action to our component.
Such component would look like this:
src/components/NavBar/NavBar.js
import React, { Component } from "react";
import "./NavBar.scss";
import NavBarItem from "./NavBarItem";
class NavBar extends Component {
constructor(props){
super(props);
this.handleMouseDown = this.handleMouseDown.bind(this);
}
handleMouseDown(e) {
this.props.onHideMobileNavigationMenu();
e.stopPropagation();
}
render() {
let {
visibleMobileMenu,
onHideMobileNavigationMenu
} = this.props;
const navBarClass = visibleMobileMenu ? "show-menu" : "hide-menu";
return (
<div>
<div onMouseDown={this.handleMouseDown} className={`overlay-container ${navBarClass}`}></div>
<div
className={`sidebar-container ${navBarClass}`}>
<aside className="row">
<ul className="sidebar">
<NavBarItem
path="/about"
onHideMobileNavigationMenu={onHideMobileNavigationMenu}
linkText="About us"
/>
<NavBarItem
path="/contact"
onHideMobileNavigationMenu={onHideMobileNavigationMenu}
linkText="Contact us"
/>
</ul>
</aside>
</div>
</div>
);
}
}
export default NavBar;
NOTE: one of the important parts in this component is:
<div onMouseDown={this.handleMouseDown} className={`overlay-container ${navBarClass}`}></div>
Since this element is responsible of hiding the Sidebar menu when it's clicked (outside the sidebar menu container elements), it's necessary to dispatch the same action to hide the menu.
The CSS for this component is:
src/components/NavBar/NavBar.scss
@import "../../styles/responsiveness";
@import "../../styles/variables";
@import "../../styles/mixins";
.sidebar{
list-style: none;
padding: 0;
border: none;
margin: 0;
clear: both;
@include mq($screen-sm-max) {
padding-left: 18px;
}
li {
padding: 20px 0 20px 0;
text-align: center;
margin: 0 auto;
color: #FFFFFF;
font-size: 10px;
@include mq($screen-sm-max) {
font-size: 16px;
padding-left: 12px;
padding: 0px;
}
&.disabled{
color: #1F1F1F;
}
&.active{
a{
color: #FFA800;
text-decoration: none;
}
}
&.disabled {
a {
color: black;
cursor: default;
&:hover {
color: black;
}
}
}
a {
margin: 0px;
padding: 0px;
text-decoration: none;
color: #FFFFFF;
&:hover{
color: #FFA800;
text-decoration: none;
}
}
}
}
.sidebar-container {
background: #555555;
z-index: 99;
.sidebar {
margin: 0;
list-style: none;
padding: 0;
li {
padding: 20px 0 20px 0;
text-align: center;
margin: 0 auto;
color: #555555;
font-size: 15px;
}
li a {
color: white;
}
li.disabled {
color: gray;
}
li a:hover {
color: white;
text-decoration: none;
}
}
}
.hamburger-container {
display: none;
padding: 0;
}
@include mq-desktop {
.sidebar-container {
height: auto;
.sidebar {
padding: 0 10px;
li {
color: #555555;
display: inline-block;
font-size: 15px;
margin-right: 22px;
}
}
}
}
@include mq($screen-sm-max) {
.hamburger-container {
display: block;
}
.overlay-container {
@include transition(opacity 0.3s);
@include Opacity(0);
background: rgba(0, 0, 0, 0.4);
bottom: 0;
font-family: Arial, Helvetica, sans-serif;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 999;
&.hide-menu {
@include translate3d(100%, 0, 0);
@include transition(opacity 0.3s, transform 0s 0.3s);
}
&.show-menu {
@include Opacity(1);
}
}
.logo-container {
margin: 0 auto;
width: 100%;
}
.sidebar-container {
@include transition(all 0.3s cubic-bezier(0, 0.52, 0, 1));
height: 100vh;
left: 0;
overflow: scroll;
position: fixed;
top: 0;
width: 250px;
z-index: 1000;
&.hide-menu {
@include translate3d(-300px, 0, 0);
}
&.show-menu {
@include translate3d(0px, 0, 0);
overflow: hidden;
}
}
}
Before moving on, I would like to explain the most important parts and elements of the CSS properties we just added above:
.overlay-container
That div is responsible of showing the dark overlay when the sidebar nav menu is open and is able to be closed by clicking on any area of the overlay container.
.sidebar-container
Here, we can find all the CSS effects to show the slide in and out effects using CSS3
If you were to look into the NavBar
component you would see that we've used another NavBarItem
component for the internal elements, that component looks like this:
src/components/NavBar/NavBarItem.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
class NavBarItem extends Component {
constructor(props) {
super(props);
this.onClickHandler = this.onClickHandler.bind(this);
}
getPath() {
return this.props.path;
}
onClickHandler() {
const { onHideMobileNavigationMenu } = this.props;
onHideMobileNavigationMenu && onHideMobileNavigationMenu();
}
render() {
const { className, linkText } = this.props;
return (
<li className={className}>
<Link to={this.getPath()} onClick={this.onClickHandler}>
{linkText}
</Link>
</li>
);
}
}
export default NavBarItem;
The final step is to include the NavBarContiner
into the main layout
src/DefaultLayout.js
import NavBarContainer from "./containers/NavBarContainer.js";
src/DefaultLayout.js
+ import NavBarContainer from "./containers/NavBarContainer.js";
<div className="default-layout">
<HamburgerIconContainer showSideBarMenu />
+ <NavBarContainer/>
<div className="main-container">
The full file looks like this:
import React from 'react';
import HamburgerIconContainer from "./containers/HamburgerIconContainer.js";
import NavBarContainer from "./containers/NavBarContainer.js";
import { Route } from "react-router-dom";
import "./DefaultLayout.scss";
const DefaultLayout = ({component: Component, ...rest}) => {
return (
<Route {...rest} render={matchProps => (
<div className="default-layout">
<HamburgerIconContainer showSideBarMenu />
<NavBarContainer />
<div className="main-container">
<Component {...matchProps} />
</div>
<div className="layout-footer">Footer</div>
</div>
)} />
)
};
export default DefaultLayout;
This is how it should look at this point:
If you want to see the full example please visit the following pull request
https://github.com/heridev/react-burger-menu-implementations/pull/1/files
Or the direct branch called approach-from-scratch
That's all I have for now, hope you find it useful, and stay tuned for the next part if you want to use an existent library to implement the same exact hamburger menu.
H.
Keep reading, coding and relaxing