"use client"; /** * SiteSidebar — desktop-only thin rail that expands on hover. * * Layout (collapsed, w-16): * * ┌──┐ * │🏠│ ← top group (Home / Search / Kautilya / * │🔍│ Collections / Bookmarks / Mentions) * │✦ │ * │📁│ * │🔖│ * │@ │ * │ │ * │⋮ │ ← bottom group (Reading settings, Profile) * │👤│ * └──┘ * * Hovering anywhere on the rail expands it to w-56 and reveals labels. * The rail floats above content (fixed position, the main area is * padded by w-16) so the expansion never causes layout shift. * * Hidden below md — mobile uses SiteBottomNav. */ import { useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { AtSign, Bookmark, FolderClosed, Home, LogIn, Search, Settings2, User as UserIcon, } from "lucide-react"; import { useAuth } from "@/components/auth/auth-provider"; import { ReadingSettings } from "@/components/reading-settings"; import { KautilyaIcon } from "@/components/selection-toolbar/shared"; import { cn } from "@/lib/utils"; type NavItem = { href: string; label: string; icon: React.ReactNode; /** Optional: routes to mark active when the path starts with these. * Used for /books variants (lectures, letters, etc.) all flagging the * Knowledgebase entry. */ matchPrefixes?: string[]; }; const TOP_ITEMS: NavItem[] = [ { href: "/", label: "Home", icon: }, { href: "/search", label: "Search", icon: , // Search doubles as the knowledgebase browse surface — light it up // any time the user is deep inside the library so they have a clear // path back. matchPrefixes: [ "/search", "/books", "/letters", "/transcriptions", "/essays", "/glossary", "/journals", "/magazines", "/other-documents", "/disciples", ], }, { href: "/chat", label: "Kautilya", icon: , }, { href: "/u/me/collections", label: "Collections", icon: , }, { href: "/u/me/bookmarks", label: "Bookmarks", icon: , }, { href: "/u/me/mentions", label: "Mentions", icon: , }, ]; export function SiteSidebar() { const pathname = usePathname(); const { user } = useAuth(); // While the reading-settings popover is open we pin the rail to its // expanded width so the popover (which anchors to the trigger button) // doesn't visually drift when the user's mouse leaves the rail. const [readingOpen, setReadingOpen] = useState(false); function isActive(item: NavItem): boolean { if (item.matchPrefixes) { return item.matchPrefixes.some((p) => p === "/" ? pathname === "/" : pathname.startsWith(p), ); } if (item.href === "/") return pathname === "/"; return pathname === item.href || pathname.startsWith(`${item.href}/`); } return ( ); } function SidebarLink({ item, active, expandedOverride, }: { item: NavItem; active: boolean; /** Force labels visible when the rail is pinned open (e.g. reading * settings popover is up) and the mouse isn't actually hovering. */ expandedOverride?: boolean; }) { return ( {item.icon} {item.label} ); } function SidebarReadingSettings({ expanded, onOpenChange, }: { expanded: boolean; onOpenChange: (open: boolean) => void; }) { return (
( )} />
); } function SidebarProfile({ authed, handle, name, avatarURL, active, expandedOverride, }: { authed: boolean; handle: string | null; name: string | null; /** Uploaded avatar URL — when set, the rail glyph becomes a small * circular picture instead of the generic person icon so the user * recognises their seat in the nav at a glance. */ avatarURL?: string | null; active: boolean; expandedOverride?: boolean; }) { if (!authed) { return ( Sign in ); } return ( {avatarURL ? ( ) : ( )} {name || (handle ? `@${handle}` : "Profile")} ); }