Light Leak
Light Leak

Navbar

A responsive and customizable navigation bar component with optional animations, logo, and mobile drawer support.

Introduction

The ZyfloNavbar component is a versatile and responsive navigation bar that can be easily integrated into your Next.js project. It offers a range of customization options, including logo display, navigation items, animations, and mobile drawer support.

Add The Component

Add the following component to your project in the /components/zyflo directory:

"use client"
import Link from "next/link"
import React from "react"
import { buttonVariants } from "../ui/button"
import {
  ZyfloDrawer,
  ZyfloDrawerTrigger,
  ZyfloDrawerContent,
  ZyfloDrawerHeader,
  ZyfloDrawerDescription
} from "@/components/zyflo/drawer"
import { RxHamburgerMenu } from "react-icons/rx"
import { cn } from "@/lib/utils"
import {
  zyfloBlurInFromBottomVariants,
  zyfloBlurInFromLeftVariants,
  zyfloBlurInFromRightVariants
} from "@/zyflo.config"
import { motion, Variants } from "framer-motion"

interface ZyfloNavbarItem {
  title: string
  href: string
  label?: string
  icon?: React.ReactNode
}

type ZyfloNavbarLogoComponent =
  | { src: string; alt: string; width: number; height: number }
  | { text: string; as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" }

interface ZyfloNavbarProps extends React.ComponentPropsWithRef<"header"> {
  items: ZyfloNavbarItem[]
  srOnly?: boolean
  mobileNavFooter?: React.ReactNode
  logo?: ZyfloNavbarLogoComponent
  disableAnimations?: boolean
  mobileNavbarCentered?: boolean
  sticky?: boolean
}

export default function ZyfloNavbar({
  logo,
  items,
  srOnly = false,
  mobileNavFooter = null,
  disableAnimations = false,
  mobileNavbarCentered = false,
  sticky = true,
  className,
  ...props
}: ZyfloNavbarProps) {
  const justify = !logo ? "center" : "between"
  const headingAs = logo && "text" in logo ? logo.as ?? "h1" : undefined
  const imageClassName = "h-12 w-auto select-none"

  const renderLogo = (isMobile = false) => {
    if (!logo) return null
    const content =
      "src" in logo ? (
        <div
          className={`flex w-full justify-${
            isMobile && mobileNavbarCentered ? "center" : "start"
          }`}
        >
          <img
            draggable={false}
            src={logo.src}
            alt={logo.alt}
            width={300}
            height={100}
            className={cn(imageClassName, "select-none zyflo-transition")}
          />
        </div>
      ) : (
        React.createElement(
          headingAs ?? "h3",
          {
            className:
              isMobile && mobileNavbarCentered
                ? "text-center w-full"
                : undefined
          },
          logo.text
        )
      )

    return disableAnimations ? (
      content
    ) : (
      <motion.div
        variants={zyfloBlurInFromLeftVariants as unknown as Variants}
        initial="initial"
        custom={0}
        whileInView="animate"
        viewport={{ once: true }}
        className={`w-fit ${isMobile && mobileNavbarCentered ? "w-full" : ""}`}
      >
        {content}
      </motion.div>
    )
  }

  const renderNavItems = (isMobile = false) => {
    const itemClass = isMobile
      ? `w-fit text-foreground/80 zyflo-transition hover:text-primary text-${
          mobileNavbarCentered ? "center" : "left"
        }`
      : "w-full text-base tracking-tight"
    const listClass = isMobile
      ? `flex list-none flex-col gap-2 justify-${
          mobileNavbarCentered ? "center" : "start"
        } items-${mobileNavbarCentered ? "center" : "start"}`
      : "flex list-none flex-row gap-6 !my-0"

    return (
      <ul className={listClass}>
        {items.map((item, index) => {
          const linkContent = (
            <Link
              key={index}
              href={item.href}
              className={
                isMobile
                  ? "inline-flex w-fit items-center gap-2"
                  : "inline-flex items-center gap-2 text-base"
              }
              aria-label={item.label ?? item.title}
            >
              {item.icon}
              {item.title}
              {srOnly && <span className="sr-only">{item.label}</span>}
            </Link>
          )

          return disableAnimations ? (
            <li key={item.title + index} className={itemClass}>
              {linkContent}
            </li>
          ) : (
            <motion.li
              key={item.title + index}
              variants={
                isMobile
                  ? (zyfloBlurInFromLeftVariants as unknown as Variants)
                  : (zyfloBlurInFromBottomVariants as unknown as Variants)
              }
              initial="initial"
              custom={index}
              whileInView="animate"
              viewport={{ once: true }}
              className={itemClass}
            >
              {linkContent}
            </motion.li>
          )
        })}
      </ul>
    )
  }

  return (
    <header
      className={cn(
        `${
          sticky ? "sticky top-0" : "relative"
        } z-50 flex items-center justify-${justify} border-b border-b-muted bg-background/90 px-4 py-5 backdrop-blur lg:px-8`,
        className
      )}
      {...props}
    >
      <div className="mx-auto w-full max-w-7xl">
        <div className="flex w-full items-center justify-between space-x-16">
          {renderLogo()}
          <nav className="hidden lg:flex">{renderNavItems()}</nav>
        </div>
      </div>

      <ZyfloDrawer direction="top">
        <ZyfloDrawerTrigger className="block lg:hidden" asChild={true}>
          {!disableAnimations ? (
            <motion.div
              variants={zyfloBlurInFromRightVariants as unknown as Variants}
              initial="initial"
              custom={0}
              whileInView="animate"
              viewport={{ once: true }}
              className={cn(
                buttonVariants({ variant: "ghost", size: "icon" }),
                "flex cursor-pointer"
              )}
            >
              <RxHamburgerMenu className="size-5" />
            </motion.div>
          ) : (
            <RxHamburgerMenu className="size-5" />
          )}
        </ZyfloDrawerTrigger>
        <ZyfloDrawerContent>
          <ZyfloDrawerHeader
            className={`mt-4 !text-${mobileNavbarCentered ? "center" : "left"}`}
          >
            {renderLogo(true)}
          </ZyfloDrawerHeader>
          <ZyfloDrawerDescription className="p-4">
            <nav>{renderNavItems(true)}</nav>
            {mobileNavFooter && (
              <footer
                className={`mt-8 justify-${
                  mobileNavbarCentered ? "center" : "start"
                } flex w-full`}
              >
                {!disableAnimations ? (
                  <motion.div
                    variants={{
                      initial: { opacity: 0, x: -45 },
                      animate: { opacity: 1, x: 0 }
                    }}
                    initial="initial"
                    whileInView="animate"
                    viewport={{ once: true }}
                    transition={{ delay: items.length * 0.3, duration: 0.4 }}
                    className="w-fit"
                  >
                    {mobileNavFooter}
                  </motion.div>
                ) : (
                  mobileNavFooter
                )}
              </footer>
            )}
          </ZyfloDrawerDescription>
        </ZyfloDrawerContent>
      </ZyfloDrawer>
    </header>
  )
}

Usage

Here's a basic example of how to use the Navbar component:

import ZyfloNavbar as Navbar from "@/components/zyflo/navbar"

const navItems = [
    { title: "Home", href: "/" },
    { title: "About", href: "/about" },
    { title: "Contact", href: "/contact" },
]

export default function MyPage() {
    return (
        <Navbar
            items={navItems}
            logo={{ text: "My Website" }}
        />
    )
}

Examples

Here are some examples of how to use the ZyfloNavbar component:

Zyflo Window Preview

My Awesome Website

And here is my awesome website's content! This is a paragraph of text that describes my awesome website. Like, this is so cool, right? I am getting tired of writing this now. Can't think of anything else to write so that is why I am writing this. Can't believe I need to write more of this. Like seriously, this is not fun, I do not know why you are laughing. More importatnly, why are you even reading this in the first place? You need to go touch grass bro.

With Text Logo and Custom Heading

Zyflo Window Preview

My Awesome Website

And here is my awesome website's content! This is a paragraph of text that describes my awesome website. Like, this is so cool, right? I am getting tired of writing this now. Can't think of anything else to write so that is why I am writing this. Can't believe I need to write more of this. Like seriously, this is not fun, I do not know why you are laughing. More importatnly, why are you even reading this in the first place? You need to go touch grass bro.

With Icons in Navigation Items

Zyflo Window Preview

My Awesome Website

And here is my awesome website's content! This is a paragraph of text that describes my awesome website. Like, this is so cool, right? I am getting tired of writing this now. Can't think of anything else to write so that is why I am writing this. Can't believe I need to write more of this. Like seriously, this is not fun, I do not know why you are laughing. More importatnly, why are you even reading this in the first place? You need to go touch grass bro.

Zyflo Window Preview

My Awesome Website

And here is my awesome website's content! This is a paragraph of text that describes my awesome website. Like, this is so cool, right? I am getting tired of writing this now. Can't think of anything else to write so that is why I am writing this. Can't believe I need to write more of this. Like seriously, this is not fun, I do not know why you are laughing. More importatnly, why are you even reading this in the first place? You need to go touch grass bro.

Centered Mobile Navigation

Zyflo Window Preview
E-Commerce

My Awesome Website

And here is my awesome website's content! This is a paragraph of text that describes my awesome website. Like, this is so cool, right? I am getting tired of writing this now. Can't think of anything else to write so that is why I am writing this. Can't believe I need to write more of this. Like seriously, this is not fun, I do not know why you are laughing. More importatnly, why are you even reading this in the first place? You need to go touch grass bro.

Props

The ZyfloNavbar component accepts the following props:

Quick Props Overview

ZyfloNavbar Component Props
PropDescription
itemsAn array of navigation items to be displayed in the navbar
logoThe logo to be displayed in the navbar
srOnlyIf true, adds screen reader-only labels to navigation items
mobileNavFooterAdditional content to be displayed at the bottom of the mobile navigation drawer
disableAnimationsIf true, disables all animations in the navbar
mobileNavbarCenteredIf true, centers the content in the mobile navigation drawer
stickyIf true, makes the navbar sticky at the top of the page
classNameAdditional CSS classes to be applied to the navbar

Detailed Props Overview

items

  • Type: ZyfloNavbarItem[]
  • Required: Yes

An array of navigation items to be displayed in the navbar. Each item should have the following structure:

interface ZyfloNavbarItem {
  title: string
  href: string
  label?: string
  icon?: React.ReactNode
}

The logo to be displayed in the navbar

  • Type: ZyfloNavbarLogoComponent
  • Required: No

It can be either an image or text:

type ZyfloNavbarLogoComponent =
  | { src: string; alt: string; width: number; height: number }
  | { text: string; as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" }

srOnly

  • Type: boolean
  • Default: false

If set to true, adds screen reader-only labels to navigation items.

mobileNavFooter

  • Type: React.ReactNode
  • Default: null

Additional content to be displayed at the bottom of the mobile navigation drawer.

disableAnimations

  • Type: boolean
  • Default: false

If set to true, disables all animations in the navbar.

mobileNavbarCentered

  • Type: boolean
  • Default: false

If set to true, centers the content in the mobile navigation drawer.

sticky

  • Type: boolean
  • Default: true

If set to true, makes the navbar sticky at the top of the page.

className

  • Type: string
  • Required: No

Additional CSS classes to be applied to the navbar container.

Customization

The component uses Tailwind CSS classes for styling. You can customize its appearance by modifying the CSS classes in the component's source code or by passing additional classes through the className prop.

Accessibility

The component is designed with accessibility in mind:

  • It uses semantic HTML elements (header, nav, ul, li) for proper structure.
  • The mobile navigation drawer is implemented using the shadcnUI Drawer component, which handles proper focus management and keyboard navigation.
  • The srOnly prop can be used to add screen reader-only labels to navigation items for improved clarity.

Notes

  • The component uses Framer Motion for animations. Make sure you have Framer Motion installed in your project if you plan to use animations.
  • The mobile navigation drawer is implemented using the shadcnUI Drawer component. Ensure that this component is available in your project.

Contributing

If you find any issues or have any sort of feedback or suggestions for improvements, please feel free to open an issue or submit a pull request on our GitHub repository. We appreciate your contributions and are always open to collaboration.

Thank you for considering contributing to Zyflo!