Skip to content

Commit

Permalink
Feature/46 desktop-navigation with example (#50)
Browse files Browse the repository at this point in the history
* feat: Create Desktop navigation with example in theme

* chore: Add "use client" in nav-context.tsx
  • Loading branch information
natemate90 authored Jan 9, 2025
1 parent 363859a commit 69cad1c
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 38 deletions.
27 changes: 17 additions & 10 deletions nextjs/src/app/[locale]/theme/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Link from "@/components/link-button"
import Title from "@/components/title"
import Text from "@/components/text"
import {
navItems,
hero,
intro,
solutions,
Expand Down Expand Up @@ -44,6 +45,8 @@ import { type Locale } from "@/hooks/useLocale"
import SectionWhitepaper from "@/components/sections/section-whitepaper"
import SectionCalendly from "@/components/sections/section-calendly"
import ExternalScript from "@/components/external-script"
import Topbar from "@/components/topbar"
import { NavProvider } from "@/components/nav-bar/nav-context"

export default function Theme({
params: { locale },
Expand All @@ -52,16 +55,20 @@ export default function Theme({
}) {
return (
<main className="bg-white">
<Hero
color="white"
imageUrl="https://images.unsplash.com/photo-1682687220198-88e9bdea9931?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
>
<Title markdown={hero.title} />
<Text markdown={hero.text} />
<Link href="https://www.adfinis.com" size="large">
Learn how
</Link>
</Hero>
<NavProvider>
<Topbar navItems={navItems} />
<Hero
color="white"
imageUrl="https://images.unsplash.com/photo-1682687220198-88e9bdea9931?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
>
<Title markdown={hero.title} />
<Text markdown={hero.text} />
<Link href="https://www.adfinis.com" size="large">
Learn how
</Link>
</Hero>
</NavProvider>

<Intro>
<Title markdown={intro.title} align="center" />
<Text markdown={intro.text} className="grid gap-8" />
Expand Down
56 changes: 56 additions & 0 deletions nextjs/src/app/[locale]/theme/texts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,62 @@
import { type Card } from "@/components/cards/card"
import { NavItem } from "@/components/nav-bar/nav"
import { CTA } from "@/lib/cta"

export const navItems: NavItem[] = [
{
title: "Solutions",
items: [
{
title: "HashiCorp",
items: [
{
title: "Vault",
url: "/solutions/vault",
},
{
title: "Terraform",
url: "/solutions/terraform",
},
{
title: "Consul",
url: "/solutions/consul",
},
],
},
{
title: "Red Hat",
items: [
{
title: "OpenShift",
url: "/solutions/openshift",
},
{
title: "Enterprise Linux & SAP Workloads",
url: "/solutions/enterprise-linux-sap-workloads",
},
{
title: "Ansible Automation Platform",
url: "/solutions/ansible-automation-platform",
},
{
title: "Red Hat Satellite",
url: "/solutions/red-hat-satellite",
},
],
},
],
},
{
title: "Partners & Products",
items: [
{
title: "Github",
url: "/partners/github",
},
],
},
]

export const hero = {
title: `## Potential. **Unlocked.**`,
text: `Open Source is our key to innovation and sustainable digitalization!
Expand Down
26 changes: 23 additions & 3 deletions nextjs/src/components/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Triangle from "./triangle"
import clsx from "clsx"
import React from "react"
import { colors } from "@/lib/colors"
import { useNavContext } from "./nav-bar/nav-context"

type HeroProps = {
color: keyof typeof colors
Expand All @@ -12,6 +13,7 @@ type HeroProps = {
}

const Hero: React.FC<HeroProps> = ({ imageUrl, children, color }) => {
const { navActive } = useNavContext()
return (
<div
className={clsx([
Expand All @@ -29,15 +31,33 @@ const Hero: React.FC<HeroProps> = ({ imageUrl, children, color }) => {
alt="Hero Image"
width={1920}
height={1080}
className="absolute inset-0 object-cover object-center z-0 h-full w-full"
className={clsx([
"absolute inset-0 object-cover object-center z-0 h-full w-full",
"transition-all duration-75",
{
"blur-sm": navActive,
},
])}
/>
<div
className={clsx([
"z-0 absolute inset-0 bg-gradient-to-r from-stone/50 to-stone/0",
"transition-all duration-75",
{ "bg-stone/60": navActive },
])}
/>
<div className="z-0 absolute inset-0 bg-gradient-to-r from-stone/50 to-stone/0" />
<Triangle
color={color}
className="w-[50vw] h-auto absolute right-0 bottom-0"
/>
<section
className="relative container px-4 lg:px-0 mt-28 lg:mt-44"
className={clsx([
"relative container px-4 lg:px-0 mt-28 lg:mt-44",
"transition-all duration-75",
{
"blur-sm": navActive,
},
])}
data-scheme="dark"
>
<div className="w-full lg:w-1/2">
Expand Down
29 changes: 29 additions & 0 deletions nextjs/src/components/nav-bar/nav-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client"
import React, { createContext, useState, useContext, ReactNode } from "react"

type NavState = {
navActive: boolean
setNavActive: React.Dispatch<React.SetStateAction<boolean>>
}

const NavContext = createContext<NavState | undefined>(undefined)

/**
* @description NavProvider is a context provider that has a single shared state about whether any desktop nav item is active or not.
*/
export const NavProvider = ({ children }: { children: ReactNode }) => {
const [navActive, setNavActive] = useState<boolean>(false)
return (
<NavContext.Provider value={{ navActive, setNavActive }}>
{children}
</NavContext.Provider>
)
}

export const useNavContext = () => {
const context = useContext(NavContext)
if (!context) {
throw new Error("useAppContext must be used within a NavProvider")
}
return context
}
76 changes: 76 additions & 0 deletions nextjs/src/components/nav-bar/nav-desktop-items.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useState } from "react"
import { Transition } from "@headlessui/react"
import { useNavContext } from "./nav-context"

import type { NavItem } from "./nav"
import Link from "next/link"

type NavDesktopItemsProps = {
navItem: NavItem
}
const NavDesktopItems: React.FC<NavDesktopItemsProps> = ({ navItem }) => {
const [isShowing, setIsShowing] = useState(false)
const { navActive, setNavActive } = useNavContext()

const showDesktopItems = () => {
setIsShowing(true)
setNavActive(true)
}

const hideDesktopItems = () => {
setIsShowing(false)
setNavActive(false)
}

return (
<div className="isolate z-50 pr-8" onMouseLeave={hideDesktopItems}>
<div className="mx-auto max-w-7xl">
<div
onMouseEnter={showDesktopItems}
className="cursor-pointer inline-flex items-center gap-x-1 text-sm/6 font-semibold text-neutral py-4"
>
{navItem.title}
</div>
</div>

<Transition
show={isShowing}
enter="transition delay-300 duration-300 ease-out"
enterFrom="opacity-0"
enterTo="translate-y-0 opacity-100"
leave="transition duration-75 ease-in"
leaveFrom="translate-y-0 opacity-100"
leaveTo="opacity-0"
>
<div className="absolute inset-x-0 top-14 -z-10 py-10">
<div className="flex justify-start items-start gap-x-4 text-neutral">
{navItem.items?.map((item, index) => (
<div
className="grid grid-cols-1 content-start gap-4 pr-2 border-r min-h-72 border-neutral/30 w-1/6"
key={index}
>
<h3 className="text-16 leading-5 font-semibold">
{item.title}
</h3>
{item.items?.map(
(subItem, subIndex) =>
subItem.url && (
<Link
className="font-normal"
key={subIndex}
href={subItem.url}
>
{subItem.title}
</Link>
),
)}
</div>
))}
</div>
</div>
</Transition>
</div>
)
}

export default NavDesktopItems
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import React, { useEffect } from "react"
import React, { useEffect, useState } from "react"
import clsx from "clsx"
import useDetectScroll, { Axis } from "@smakss/react-scroll-direction"
import Logo from "./logo"
import TopbarActions from "./topbar-actions"
import Logo from "../logo"
import TopbarActions from "../topbar-actions"
import { NavItem } from "./nav"
import Link from "next/link"
import NavDesktopItems from "./nav-desktop-items"

const NavDesktop: React.FC = () => {
const [menuExpanded, setMenuExpanded] = React.useState(true)
type NavDesktopProps = {
navItems: NavItem[]
}

const NavDesktop: React.FC<NavDesktopProps> = ({ navItems }) => {
const [menuExpanded, setMenuExpanded] = useState(true)
const { scrollDir, scrollPosition } = useDetectScroll({
thr: 20,
axis: Axis.Y,
Expand All @@ -30,19 +37,21 @@ const NavDesktop: React.FC = () => {
/**
* @description only collapse the menu when the user has scrolled down
*/
const onMouseMenuLeave = () => {
const handleMouseMenuLeave = () => {
if (scrollPosition.top > 200) {
setMenuExpanded(false)
}
}

const handleMouseEnter = () => {
setMenuExpanded(true)
}

return (
<div
className="hidden lg:grid divide-y divide-jumbo/30 pr-[50px]"
onMouseEnter={() => setMenuExpanded(true)}
onMouseLeave={() => onMouseMenuLeave()}
>
<div className="hidden lg:grid divide-y divide-jumbo/30 pr-[50px]">
<section
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseMenuLeave}
className={clsx([
"min-h-16 h-16 py-4 transition-all duration-150 flex justify-between items-center",
{
Expand All @@ -55,7 +64,15 @@ const NavDesktop: React.FC = () => {
<TopbarActions />
</section>
{menuExpanded && (
<section className="hidden lg:block min-h-12" id="nav-items"></section>
<section className="hidden lg:block min-h-12" id="nav-items">
<div className="flex justify-start items-center h-full relative">
{navItems.map((item, index) => (
<div className="" key={index}>
{item && <NavDesktopItems navItem={item} />}
</div>
))}
</div>
</section>
)}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ import clsx from "clsx"
import { Transition } from "@headlessui/react"
import { useClickAway } from "@uidotdev/usehooks"

import IconHamburgerMenu from "./icons/icon-hamburger-menu"
import IconHamburgerMenu from "../icons/icon-hamburger-menu"
import Link from "next/link"
import ButtonLink from "./link-button"
import IconChevronRight from "./icons/icon-chevron-right"
import Logo from "./logo"
import TopbarActions from "./topbar-actions"
import ButtonLink from "../link-button"
import IconChevronRight from "../icons/icon-chevron-right"
import Logo from "../logo"
import TopbarActions from "../topbar-actions"
import type { NavItem } from "./nav"

const NavMobile: React.FC = () => {
type NavMobileProps = {
navItems: NavItem[]
}

const NavMobile: React.FC<NavMobileProps> = ({ navItems }) => {
const [isOpen, setIsOpen] = useState(false)
const ref = useClickAway(() => {
setIsOpen(false)
Expand Down
5 changes: 5 additions & 0 deletions nextjs/src/components/nav-bar/nav.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type NavItem = {
title: string
url?: string
items?: NavItem[]
}
2 changes: 0 additions & 2 deletions nextjs/src/components/topbar-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import LocaleSwitcher from "./locale-switcher"
import Search from "./search"

const TopbarActions: React.FC = () => {
// Add your component logic here

return (
<div className="flex justify-end items-center gap-4 lg:gap-6 text-neutral">
<Search />
Expand Down
Loading

0 comments on commit 69cad1c

Please sign in to comment.