"use client"; import clsx from "clsx"; import { AnimatePresence, motion, useIsPresent } from "framer-motion"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { type ComponentPropsWithoutRef, type ReactNode, useRef } from "react"; import { remToPx } from "../lib/remToPx"; import { Button } from "./Button"; import { useIsInsideMobileNavigation } from "./MobileNavigation"; import { useSectionStore } from "./SectionProvider"; import { Tag } from "./Tag"; interface NavGroup { title: string; links: Array<{ title: string; href: string; }>; } function useInitialValue(value: T, condition = true) { const initialValue = useRef(value).current; return condition ? initialValue : value; } function TopLevelNavItem({ href, children, }: { href: string; children: ReactNode; }) { return (
  • {children}
  • ); } function NavLink({ href, children, tag, active = false, isAnchorLink = false, }: { href: string; children: ReactNode; tag?: string; active?: boolean; isAnchorLink?: boolean; }) { return ( {children} {tag && ( {tag} )} ); } function VisibleSectionHighlight({ group, pathname, }: { group: NavGroup; pathname: string; }) { const [sections, visibleSections] = useInitialValue( [ useSectionStore((s) => s.sections), useSectionStore((s) => s.visibleSections), ], useIsInsideMobileNavigation(), ); const isPresent = useIsPresent(); const firstVisibleSectionIndex = Math.max( 0, [{ id: "_top" }, ...sections].findIndex( (section) => section.id === visibleSections[0], ), ); const itemHeight = remToPx(2); const height = isPresent ? Math.max(1, visibleSections.length) * itemHeight : itemHeight; const top = group.links.findIndex((link) => link.href === pathname) * itemHeight + firstVisibleSectionIndex * itemHeight; return ( ); } function ActivePageMarker({ group, pathname, }: { group: NavGroup; pathname: string; }) { const itemHeight = remToPx(2); const offset = remToPx(0.25); const activePageIndex = group.links.findIndex( (link) => link.href === pathname, ); const top = offset + activePageIndex * itemHeight; return ( ); } function NavigationGroup({ group, className, }: { group: NavGroup; className?: string; }) { // If this is the mobile navigation then we always render the initial // state, so that the state does not change during the close animation. // The state will still update when we re-open (re-render) the navigation. const isInsideMobileNavigation = useIsInsideMobileNavigation(); const [pathname, sections] = useInitialValue( [usePathname(), useSectionStore((s) => s.sections)], isInsideMobileNavigation, ); const isActiveGroup = group.links.findIndex((link) => link.href === pathname) !== -1; return (
  • {group.title}
    {isActiveGroup && ( )} {isActiveGroup && ( )}
      {group.links.map((link) => ( {link.title} {link.href === pathname && sections.length > 0 && ( {sections.map((section) => (
    • {section.title}
    • ))}
      )}
      ))}
  • ); } export const navigation: NavGroup[] = [ { title: "Guides", links: [ { title: "Introduction", href: "/introduction" }, { title: "SDKs", href: "/sdks" }, { title: "Entities", href: "/entities" }, { title: "Signatures", href: "/signatures" }, { title: "Federation", href: "/federation" }, { title: "Extensions", href: "/extensions" }, ], }, { title: "Federation", links: [ { title: "HTTP", href: "/federation/http" }, { title: "Validation", href: "/federation/validation" }, { title: "Discovery", href: "/federation/discovery" }, { title: "Delegation", href: "/federation/delegation" }, ], }, { title: "Structures", links: [ { title: "ContentFormat", href: "/structures/content-format" }, { title: "Collection", href: "/structures/collection" }, ], }, { title: "Entities", links: [ { title: "Delete", href: "/entities/delete" }, { title: "Follow", href: "/entities/follow" }, { title: "FollowAccept", href: "/entities/follow-accept" }, { title: "FollowReject", href: "/entities/follow-reject" }, { title: "Group", href: "/entities/group" }, { title: "Notes", href: "/entities/note" }, { title: "InstanceMetadata", href: "/entities/instance-metadata" }, { title: "Unfollow", href: "/entities/unfollow" }, { title: "Users", href: "/entities/user" }, ], }, { title: "Extensions", links: [ { title: "Custom Emojis", href: "/extensions/custom-emojis" }, { title: "Likes", href: "/extensions/likes" }, { title: "Migration", href: "/extensions/migration" }, { title: "Polls", href: "/extensions/polls" }, { title: "Reactions", href: "/extensions/reactions" }, { title: "Reports", href: "/extensions/reports" }, { title: "Share", href: "/extensions/share" }, { title: "Vanity", href: "/extensions/vanity" }, { title: "WebSockets", href: "/extensions/websockets" }, ], }, ]; export function Navigation(props: ComponentPropsWithoutRef<"nav">) { return ( ); }