"use client"; import { useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { ChevronDown, List } from "lucide-react"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; import type { TocItem } from "@/lib/blog-utils"; interface TocGroup { heading: TocItem; children: TocItem[]; } function groupByLevel2(items: TocItem[]): TocGroup[] { const groups: TocGroup[] = []; let current: TocGroup | null = null; for (const item of items) { if (item.level === 2) { current = { heading: item, children: [] }; groups.push(current); } else if (current) { current.children.push(item); } } return groups; } interface BlogTocProps { items: TocItem[]; className?: string; } const tocLinkClass = ( level: number, isActive: boolean ) => cn( "block rounded-md border-r-2 rounded-s-none border-transparent py-1.5 text-sm transition-colors hover:text-foreground hover:bg-muted/60", level === 2 && "pr-3", level === 3 && "pr-5", level === 4 && "pr-7", isActive ? "font-medium text-foreground border-primary bg-muted/50" : "text-muted-foreground" ); export function BlogToc({ items, className }: BlogTocProps) { const [activeId, setActiveId] = useState(items[0]?.id ?? null); const [mobileOpen, setMobileOpen] = useState(false); const [openSections, setOpenSections] = useState>(() => new Set()); const groups = useMemo(() => groupByLevel2(items), [items]); useEffect(() => { setOpenSections(new Set(groups.map((g) => g.heading.id))); }, [groups]); useEffect(() => { if (items.length === 0) return; const observer = new IntersectionObserver( (entries) => { for (const entry of entries) { if (entry.isIntersecting) { const id = entry.target.getAttribute("id"); if (id) setActiveId(id); } } }, { rootMargin: "-80px 0% -70% 0%", threshold: 0 } ); items.forEach(({ id }) => { const el = document.getElementById(id); if (el) observer.observe(el); }); return () => observer.disconnect(); }, [items]); useEffect(() => { if (activeId && groups.length > 0) { const group = groups.find( (g) => g.heading.id === activeId || g.children.some((c) => c.id === activeId) ); if (group) { setOpenSections((prev) => new Set(prev).add(group.heading.id)); } } }, [activeId, groups]); if (items.length === 0) return null; const renderTocLink = (item: TocItem, onNav?: () => void) => ( { setMobileOpen(false); onNav?.(); }} className={tocLinkClass(item.level, activeId === item.id)} > {item.text} ); const tocList = ( ); return ( <> {/* Desktop: sticky sidebar left */} {/* Mobile: collapsible, sticky when scrolling */} فهرست مطالب
{tocList}
); }