"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")}
);
}