How I Made a Navbar Component in Gatsby (React) with TypeScript, and Twin Macro

I am a full-stack web developer that enjoys coding in Laravel and React.
Search for a command to run...

I am a full-stack web developer that enjoys coding in Laravel and React.
I love this article! Would you be open to freelancing opportunities for B2B SaaS companies? Let me know.
Hi Saquib, thank you. Please reach me with the contact button on my website https://lloydmiller.dev
In this series I'll discuss how I solved problems with frontend technologies like React, Vue, Next, Nuxt, Gatsby, etc. in the style of tutorials.
To make apps using Zoom's API, authentication is a must. You can either do this with JWT or OAuth. If you're building a third-party service or application, OAuth is the way to go. We'll not be using an SDK for this tutorial; everything will be done f...
Switching stacks

Many developers know that full-stack apps with a Vue frontend can be quickly spun up with Laravel Jetstream. What many don’t know is that recently, the Laravel team made it easy to make an Inertia app with Laravel Breeze. In this article, we'll make ...

Extending the Rule Object with a DateTime Trait

Using Custom Console Commands

If you don't know what Twin Macro is, check it out! It's a great library that blends Tailwind CSS and Styled Component systems, like Emotion. I love using Twin because it lets me use Tailwind while separating my styling from my markup and methods. If you're someone who likes using styled-components for your React projects, let me show you how I did it. By the way, even though I'm using Gatsby, just know that Twin Macro is compatible with create-react-app and Next.js.
Preliminaries:
$ npm install -g gatsby-cli. $ npx gatsby new {APP_NAME}. You could also do this by running $ npm init gatsby. Doing it this way will bring up some prompts that you could follow through to set your project up. Select None when it asks you for your preferred design system.$ npm install @emotion/react @emotion/styled gatsby-plugin-emotion.gatsby-plugin-emotion to your list of plugins in gatsby-config.js."babelMacros": {
"twin": {
"preset": "emotion"
}
},
$ tsc --init --jsx react to build the tsconfig.json file. You'll see a bunch of commented and uncommented lines.{
"include": ["./src/**/*"],
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"lib": ["dom", "es2017"],
"jsx": "react",
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"noEmit": true,
"skipLibCheck": true
}
}
$ npx tailwindcss-cli@latest init.First Steps:
styled module from Twin Macro, we're going to have to make some adjustments so that the compiler doesn't scream at us. Let's add some types for React by running $ npm install --save-dev @types/react.import 'twin.macro'
import styledImport from '@emotion/styled'
import { css as cssImport } from '@emotion/react'
declare module 'twin.macro' {
const styled: typeof styledImport
const css: typeof cssImport
}
"jsxImportSource": "@emotion/react", to "compilerOptions". "jsx": "react" to "jsx": "react-jsx".include line, like so: "include": ["./src/**/*", "twin.d.ts"],.The Work Begins:
import React from 'react'
import { GlobalStyles } from 'twin.macro'
const Layout = ({ children, ...rest }) => (
<div {...rest}>
<GlobalStyles />
{children}
</div>
)
export default Layout
import React from 'react
import Layout from '../components/layout'
const IndexPage = () => {
return (
<Layout>
<p>Hello World!</p>
</Layout>
)
}
export default IndexPage
tw and styled which will help us make styled-components with Tailwind.The Work Continues:
import tw, {styled} from 'twin.macro'
export const Nav = tw.nav`
relative flex flex-wrap items-center justify-between px-4 py-5
md:px-24 lg:px-8
`
export const InnerNav = tw.div`
container px-4 mx-auto flex flex-wrap items-center justify-between
`
export const InnerMost = tw.div`
w-full relative flex justify-between lg:w-auto lg:static lg:block
lg:justify-start
`
export const DropDown = styled.div((props: any) => [
tw`lg:flex flex-grow items-center`,
props.open ? tw`flex` : tw`hidden`
])
export const Logo = tw.a`
leading-relaxed inline-block mr-4 whitespace-nowrap
`
export const NavButton = tw.button`
text-black cursor-pointer text-xl leading-none px-3 py-1 border border-solid
border-transparent rounded bg-transparent block lg:hidden outline-none
focus:outline-none
`
export const DropMenu = tw.ul`
flex flex-col lg:flex-row list-none lg:ml-auto md:w-full md:items-center
lg:justify-end
`
export const ListItem = tw.li`
block
`
export const InnerItem = tw(ThisLink)`
px-3 py-2 flex items-center font-sans font-semibold text-lg tracking-wide
text-gray-700 hover:text-gray-900
`
export const ExtraButton = tw.a`
text-black bg-transparent border border-solid border-gray-900
hover:bg-black hover:text-white active:bg-gray-600 font-sans font-semibold
uppercase text-sm px-4 py-2 rounded outline-none focus:outline-none mr-1
mb-1 mt-4 lg:mt-0 lg:ml-24
`
export const Bars = tw.div`
mt-2
`
export const Bar = tw.div`
w-8 h-1 bg-black mb-1 align-middle
`
styled module. This enables us to use props, which we'll need to pass our open data to.StaticImage. This component is used when we're not loading dynamic images.import React from 'react'
import {Nav, InnerNav, InnerMost, DropDown, Logo, NavButton,
DropMenu, ListItem, InnerItem, ExtraButton, Bars, Bar
} from './styles'
import {StaticImage} from 'gatsby-plugin-image'
const Navbar = () => {
return (
<>
<Nav>
<InnerNav>
<InnerMost>
<Logo href="/">
<StaticImage
src="../../images/logo.png"
alt="logo"
width={180}
placeholder="none"
/>
</Logo>
<NavButton
type="button"
>
<Bars>
<Bar />
<Bar />
<Bar />
</Bars>
</NavButton>
</InnerMost>
<DropDown>
<DropMenu>
<ListItem>
<InnerItem
href="/products"
aria-label="Products"
>
Products
</InnerItem>
</ListItem>
<ListItem>
<InnerItem
href="/pricing"
aria-label="Product pricing"
>
Pricing
</InnerItem>
</ListItem>
<ListItem>
<InnerItem
href="/about"
aria-label="About us"
>
About Us
</InnerItem>
</ListItem>
<ListItem>
<InnerItem
href="/faq"
aria-label="FAQs"
>
FAQs
</InnerItem>
</ListItem>
<ExtraButton
href="/contact-us"
>
CONTACT US
</ExtraButton>
</DropMenu>
</DropDown>
</InnerNav>
</Nav>
</>
)
}
export default Navbar
onClick event listener which will fire off our event handler, handleClick(). handleClick() method will then be in charge of facilitating our state change.open. Our markup will now look like this:import React, {useState} from 'react'
import {Nav, InnerNav, InnerMost, DropDown, Logo, NavButton,
DropMenu, ListItem, InnerItem, ExtraButton, Bars, Bar
} from './styles'
import {StaticImage} from 'gatsby-plugin-image'
const Navbar = () => {
const [navbarOpen, setNavbarOpen] = useState(false)
function handleClick(){
setNavbarOpen(!navbarOpen)
}
return (
<>
<Nav>
<InnerNav>
<InnerMost>
<Logo href="/">
<StaticImage
src="../../images/logo.png"
alt="logo"
width={180}
placeholder="none"
/>
</Logo>
<NavButton
type="button"
onClick={handleClick}
>
<Bars>
<Bar />
<Bar />
<Bar />
</Bars>
</NavButton>
</InnerMost>
<DropDown open={navbarOpen}>
<DropMenu>
<ListItem>
<InnerItem
href="/products"
aria-label="Products"
>
Products
</InnerItem>
</ListItem>
<ListItem>
<InnerItem
href="/pricing"
aria-label="Product pricing"
>
Pricing
</InnerItem>
</ListItem>
<ListItem>
<InnerItem
href="/about"
aria-label="About us"
>
About Us
</InnerItem>
</ListItem>
<ListItem>
<InnerItem
href="/faq"
aria-label="FAQs"
>
FAQs
</InnerItem>
</ListItem>
<ExtraButton
href="/contact-us"
>
CONTACT US
</ExtraButton>
</DropMenu>
</DropDown>
</InnerNav>
</Nav>
</>
)
}
export default Navbar
The Work Ends:
FC (Function Component) generic type which can be used on arrow functions to define and extend arrow function components. FC allows us to pass props into our component and helps us define default types for it as well. It also includes the children prop type by default, but we will be explicitly defining this in our component.ButtonProps where we'll define the props that we know will be passed to our button component (type, children, onClick). For onClick, we'll define it with a function that uses React's default MouseEvent interface. TypeScript pros have a saying that you should never use the any type, but for brevity, we'll just give children an any type.FC, we can then pass in our defined props to define our extended component. import React, {FC, MouseEvent} from 'react'
import tw, {styled} from 'twin.macro'
type ButtonProps = {
type: string,
children?: any,
onClick?: (event?: MouseEvent<HTMLButtonElement>) => void,
}
const ThisButton: FC<ButtonProps> = ({onClick, type, children, ...rest}) =>
<button onClick={onClick} {...rest}>{children}</button>
...
export const NavButton = tw(ThisButton)`
text-black cursor-pointer text-xl leading-none px-3 py-1 border border-solid
border-transparent rounded bg-transparent block lg:hidden outline-none
focus:outline-none
`
Navbar component and place it right below the GlobalStyles component.import React from 'react'
import { GlobalStyles } from 'twin.macro'
import Navbar from './Navbar'
const Layout = ({ children, ...rest }) => (
<div {...rest}>
<GlobalStyles />
<Navbar />
{children}
</div>
)
export default Layout
Conclusion:
onClick event listener to each link component, which in our case is <InnerItem />.import React, {FC, MouseEvent} from 'react'
import tw, {styled} from 'twin.macro'
type LinkProps = {
href: string,
"aria-label"?: string,
onClick?: (event?: MouseEvent<HTMLAnchorElement>) => void,
}
const ThisLink: FC<LinkProps> = props => <a {...props} />
...
export const InnerItem = tw(ThisLink)`
px-3 py-2 flex items-center font-sans font-semibold text-lg tracking-wide
text-gray-700 hover:text-gray-900
`