Archived course lesson

Add MainNavbar

Part of Frontend Serverless with React and GraphQL

Objective: Create a navigation bar for the top of the webapp.

Now that we have a footer, let’s make our navbar which will be the primary way that users can switch between different pages. Our navbar will have to sections that we divide with a flexbox, the left side will have a logo, title, and description while the right side will have a set of menu buttons that will take us to different pages.

We import our <Header> component from the <Layout> component just like we did with the <Footer>. We created a styled version of our header that we call <StyledHeader>. We use flexbox so that the title and menu sections of our navbar will dynamically stretch with the browser size and we also set a bottom border to create a nice visual divide between the navbar and the content section of our page.

Our <TitleContainer> component contains the logo image, title and description. We use @media queries so that when the size gets too small, it will disappear to leave room for the menu bar to take up the entire space.

The <StyledMenu> component has a single menu item that goes to the home page when clicked. We can add most of our styles there, but due to some issues I had with getting the menu line-height property to work in the styled component, I was forced to add it as a style prop to the component itself.

For any links I use which are internal to the site, I use the next/link package instead of using an <a> tag. This ensures that we can get nice client-side navigation without any hard refreshes or page flashes.

import styled from 'styled-components';
import { Layout, Menu } from 'antd';
import Link from 'next/link';
const { Header } = Layout;

const TitleContainer = styled.div`
  ${({ theme }) => `
        background-color: ${theme['header-color']};
        width: 50%;
        display: flex;
        align-items: center;
        @media (max-width: 890px){
            visibility: hidden;
            width: 0;
        }
    `}
`;

const Title = styled.div`
  ${({ theme }) => `
            text-align: left;
            display: flex;
            line-height: 50px;
            div {
                width: 100%;
                padding-left: ${theme['padding-small']};
            }
            h2 {
                display: inline;
                color: inherit;
            }
            img {
                width: 64px;
            }
            p {
              line-height: 0;
            }
        `}
`;

const StyledHeader = styled(Header)`
  ${({ theme }) => `
        background-color: ${theme['header-color']};
        border-bottom-color: ${theme['header-border-color']};
        border-bottom-right: 1px;
        border-bottom-style: solid;
        text-align: right;
        display: flex;
        li {
            font-size: ${theme['font-size-md']};
        }
    `}
`;

const StyledMenu = styled(Menu)`
  border-bottom-width: 0px;
  width: 50%;
  @media (max-width: 890px) {
    width: 100%;
  }
`;

export const MainNavbar = () => (
  <StyledHeader>
    <TitleContainer>
      <Title>
        <img src="/logo.svg" alt="Next Chop Logo" />
        <div>
          <h2>The Next Chop</h2>
          <p>A recipe discovery app powered by Next.js</p>
        </div>
      </Title>
    </TitleContainer>

    <StyledMenu theme="light" mode="horizontal" style={{ lineHeight: '64px' }}>
      <Menu.Item key="/">
        <Link href="/">
          <a>Home</a>
        </Link>
      </Menu.Item>
    </StyledMenu>
  </StyledHeader>
);

Now that we’ve created the MainNavbar we need to import it in the MainLayout like so:

import { ThemeProvider } from 'styled-components';
import { Component, ReactNode } from 'react';
import { theme } from '../../utils/theme';
import { GlobalStyle } from '../../utils/globalStyle';
import Head from 'next/head';
import { Layout } from 'antd';
import { MainFooter } from './MainFooter';
import { MainNavbar } from './MainNavbar';

const { Content } = Layout;

const MainHead = ({ title }: { title: string }) => (
  <Head>
    <title>{title}</title>
    <meta charSet="utf-8" />
    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    <meta
      name="description"
      content="A recipe discovery app powered by Next.js."
    />

    <meta name="author" content="codemochi" />
    <meta name="keyword" content="next, react, typescript, js" />
    <meta property="og:type" content="website" />
    <meta property="og:title" content={title} />
    <meta property="og:url" content="https://next-chop.codemochi.com" />
    <meta property="og:image" content="/logo.svg" />
    <meta
      property="og:description"
      content="A recipe discovery app powered by Next.js."
    />

    <link
      rel="apple-touch-icon"
      sizes="180x180"
      href="/favicon/apple-touch-icon.png"
    />

    <link
      rel="icon"
      sizes="32x32"
      type="image/png"
      href="/favicon/favicon-32x32.png"
    />
    <link
      rel="icon"
      sizes="16x16"
      type="image/png"
      href="/favicon/favicon-16x16.png"
    />
    <link rel="manifest" href="/favicon/site.webmanifest" />
  </Head>
);

type Props = {
  children: ReactNode;
  title?: string;
};

export class MainLayout extends Component<Props> {
  render() {
    const { title, children } = this.props;
    return (
      <ThemeProvider theme={theme}>
        <MainHead title={title} />
        <GlobalStyle />
        <Layout>
          <MainNavbar />
          <Content>{children}</Content>
          <MainFooter />
        </Layout>
      </ThemeProvider>
    );
  }
}