Gatsby Navigation using Styled Components and useState hook

How to make a simple responsive menu bar utilising styled components and useState hook

Image for post
Image for post

Motivation

Requirements

node -v
npm install -g gatsby-cli

Installation

gatsby new gatsby-navigation https://github.com/gatsbyjs/gatsby-starter-default
gatsby-installation
gatsby-installation
cd gatsby-navigation

Styled components

npm install --save gatsby-plugin-styled-components styled-components babel-plugin-styled-components

Start Building

gatsby develop
source-folder
source-folder

Logo component

// Logo.js

import React from "react"
import Img from "gatsby-image"
import { useStaticQuery, graphql } from "gatsby"


const Logo = () => {
const data = useStaticQuery(graphql`
query {
file(name: { eq: "gatsby-icon" }, extension: { eq: "png" }) {
childImageSharp {
fluid(maxWidth: 50, pngQuality: 80) {
...GatsbyImageSharpFluid
}
}
}
}
`)
return (
<Img fluid={data.file.childImageSharp.fluid} alt="logo" />
)
}

export default Logo

Navbar Links

// NavbarLinks.js

import React from "react"
import { Link } from "gatsby"

const NavbarLinks = () => {
return (
<>
<Link to="/">About</Link>
<Link to="/404">Services</Link>
<Link to="/">Gallery</Link>
<Link to="/404">Contact</Link>
</>
)
}

export default NavbarLinks

Navigation Bar

// Navbar.js

import React, { useState } from "react"
import NavbarLinks from "./NavbarLinks"
import Logo from "./Logo"
import styled from 'styled-components'

const Navigation = styled.nav``
const Toggle = styled.div``
const Navbox = styled.div``
const Hamburger = styled.div``


const Navbar = () => {
const [navbarOpen, setNavbarOpen] = useState(false)

return (
<Navigation>
<Logo />
<Toggle
navbarOpen={navbarOpen}
onClick={() => setNavbarOpen(!navbarOpen)}
>
{navbarOpen ? <Hamburger open /> : <Hamburger />}
</Toggle>
{navbarOpen ? (
<Navbox>
<NavbarLinks />
</Navbox>
) : (
<Navbox open>
<NavbarLinks />
</Navbox>
)}
</Navigation>
)
}

export default Navbar

You’ve got it!

celebrate
celebrate

Let’s add some styling

Final Logo

// Logo.jsimport React from "react"
import styled from "styled-components"
import Img from "gatsby-image"
import { Link, useStaticQuery, graphql } from "gatsby"

const LogoWrap = styled.div`
margin: auto 0;
flex: 0 1 36px;

@media (max-width: 768px) and (orientation: landscape) {
flex: 0 1 25px;
}
`
const Logo = () => {
const data = useStaticQuery(graphql`
query {
file(name: { eq: "gatsby-icon" }, extension: { eq: "png" }) {
childImageSharp {
fluid(maxWidth: 50, pngQuality: 80) {
...GatsbyImageSharpFluid
}
}
}
}
`)

return (
<LogoWrap as={Link} to="/">
<Img fluid={data.file.childImageSharp.fluid} alt="logo" />
</LogoWrap>
)
}

export default Logo

Final Navbar Links

// NavbarLinks.js

import React from "react"
import styled from "styled-components"
import { Link } from "gatsby"

const NavItem = styled(Link)`
text-decoration: none;
color: #111;
display: inline-block;
white-space: nowrap;
margin: 0 1vw;
transition: all 200ms ease-in;
position: relative;

:after {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 0%;
content: ".";
color: transparent;
background: goldenrod;
height: 1px;
transition: all 0.4s ease-in;
}

:hover {
color: goldenrod;
::after {
width: 100%;
}
}

@media (max-width: 768px) {
padding: 20px 0;
font-size: 1.5rem;
z-index: 6;
}
`
const NavbarLinks = () => {
return (
<>
<NavItem to="/">About</NavItem>
<NavItem to="/404">Services</NavItem>
<NavItem to="/">Gallery</NavItem>
<NavItem to="/404">Contact</NavItem>
</>
)
}

export default NavbarLinks

Final Navigation Bar

// Navbar.js

import React, { useState } from "react"
import styled from "styled-components"
import NavbarLinks from "./NavbarLinks"
import Logo from "./Logo"

const Navigation = styled.nav`
height: 10vh;
display: flex;
background-color: #fff;
position: relative;
justify-content: space-between;
text-transform: uppercase;
border-bottom: 2px solid #33333320;
margin: 0 auto;
padding: 0 5vw;
z-index: 2;
align-self: center;

@media (max-width: 768px) {
position: sticky;
height: 8vh;
top: 0;
left: 0;
right: 0;
left: 0;
}
`

const Toggle = styled.div`
display: none;
height: 100%;
cursor: pointer;
padding: 0 10vw;

@media (max-width: 768px) {
display: flex;
}
`

const Navbox = styled.div`
display: flex;
height: 100%;
justify-content: flex-end;
align-items: center;

@media (max-width: 768px) {
flex-direction: column;
position: fixed;
width: 100%;
justify-content: flex-start;
padding-top: 10vh;
background-color: #fff;
transition: all 0.3s ease-in;
top: 8vh;
left: ${props => (props.open ? "-100%" : "0")};
}
`

const Hamburger = styled.div`
background-color: #111;
width: 30px;
height: 3px;
transition: all .3s linear;
align-self: center;
position: relative;
transform: ${props => (props.open ? "rotate(-45deg)" : "inherit")};

::before,
::after {
width: 30px;
height: 3px;
background-color: #111;
content: "";
position: absolute;
transition: all 0.3s linear;
}

::before {
transform: ${props =>
props.open ? "rotate(-90deg) translate(-10px, 0px)" : "rotate(0deg)"};
top: -10px;
}

::after {
opacity: ${props => (props.open ? "0" : "1")};
transform: ${props => (props.open ? "rotate(90deg) " : "rotate(0deg)")};
top: 10px;
}
`
const Navbar = () => {
const [navbarOpen, setNavbarOpen] = useState(false)

return (
<Navigation>
<Logo />
<Toggle
navbarOpen={navbarOpen}
onClick={() => setNavbarOpen(!navbarOpen)}
>
{navbarOpen ? <Hamburger open /> : <Hamburger />}
</Toggle>
{navbarOpen ? (
<Navbox>
<NavbarLinks />
</Navbox>
) : (
<Navbox open>
<NavbarLinks />
</Navbox>
)}
</Navigation>
)
}

export default Navbar

Wrapping up

Links

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store