"use client"; import { navigation } from "@/lib/navigation"; import { remToPx } from "@/lib/remToPx"; import clsx from "clsx"; import { AnimatePresence, motion, useIsPresent } from "framer-motion"; import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useRef, useState } from "react"; import { Button } from "./Button"; import { useIsInsideMobileNavigation } from "./MobileNavigation"; import { useSectionStore } from "./SectionProvider"; export interface BaseLink { title: string; } export interface LinkWithHref extends BaseLink { href: string; children?: never; // Ensure that 'children' cannot coexist with 'href' } export interface LinkWithChildren extends BaseLink { href?: never; // Ensure that 'href' cannot coexist with 'children' children: Array<{ title: string; href: string; }>; } export interface NavGroup { title: string; links: Array; } const useInitialValue = (value: T, condition = true) => { let initialValue = useRef(value).current; return condition ? initialValue : value; }; const NavLink = ({ href, children, active = false, isAnchorLink = false, }: { href: string; children: React.ReactNode; active: boolean; isAnchorLink?: boolean; }) => { return ( {children} ); }; const VisibleSectionHighlight = ({ group, pathname }: { group: NavGroup; pathname: string }) => { let [sections, visibleSections] = useInitialValue( [useSectionStore((s) => s.sections), useSectionStore((s) => s.visibleSections)], useIsInsideMobileNavigation() ); let isPresent = useIsPresent(); let firstVisibleSectionIndex = Math.max( 0, [{ id: "_top" }, ...sections].findIndex((section) => section.id === visibleSections[0]) ); let itemHeight = remToPx(2); let activePageIndex = group.links.findIndex( (link) => (link.href && pathname.startsWith(link.href)) || (link.children && link.children.some((child) => pathname.startsWith(child.href))) ); let height = isPresent ? Math.max(1, visibleSections.length) * itemHeight : itemHeight; let top = activePageIndex * itemHeight + firstVisibleSectionIndex * itemHeight; if (activePageIndex === -1) return null; return ( ); }; const ActivePageMarker = ({ group, pathname }: { group: NavGroup; pathname: string }) => { let itemHeight = remToPx(2); let offset = remToPx(0.25); let activePageIndex = group.links.findIndex( (link) => (link.href && pathname.startsWith(link.href)) || (link.children && link.children.some((child) => pathname.startsWith(child.href))) ); if (activePageIndex === -1) return null; let top = offset + activePageIndex * itemHeight; return ( ); }; const NavigationGroup = ({ group, className, activeGroup, setActiveGroup, openGroups, setOpenGroups, }: { group: NavGroup; className?: string; activeGroup: NavGroup | null; setActiveGroup: (group: NavGroup | null) => void; openGroups: string[]; setOpenGroups: (groups: string[]) => void; }) => { const isInsideMobileNavigation = useIsInsideMobileNavigation(); const pathname = usePathname(); const isActiveGroup = activeGroup?.title === group.title; const toggleParentTitle = (title: string) => { if (openGroups.includes(title)) { setOpenGroups(openGroups.filter((t) => t !== title)); } else { setOpenGroups([...openGroups, title]); } setActiveGroup(group); }; const isParentOpen = (title: string) => openGroups.includes(title); return (
  • {group.title}
    {isActiveGroup && } {isActiveGroup && }
      {group.links.map((link) => ( {link.href ? ( {link.title} ) : (
      toggleParentTitle(link.title)}> pathname.startsWith(child.href)) ) }> {link.title} {isParentOpen(link.title) ? ( ) : ( )}
      )} {link.children && isParentOpen(link.title) && ( {link.children.map((child) => (
    • {child.title}
    • ))}
      )}
      ))}
  • ); }; export const Navigation = (props: React.ComponentPropsWithoutRef<"nav">) => { const [activeGroup, setActiveGroup] = useState(navigation[0]); const [openGroups, setOpenGroups] = useState([]); return ( ); };