diff --git a/src/components/docs/DocsBanner.tsx b/src/components/docs/DocsBanner.tsx index b08a8e58..9e008010 100644 --- a/src/components/docs/DocsBanner.tsx +++ b/src/components/docs/DocsBanner.tsx @@ -22,7 +22,7 @@ export function DocsBanner() { const isDark = resolvedTheme === 'dark' return ( -
+
diff --git a/src/components/docs/DocsSidebar.tsx b/src/components/docs/DocsSidebar.tsx index 7ddf9929..9284e81d 100644 --- a/src/components/docs/DocsSidebar.tsx +++ b/src/components/docs/DocsSidebar.tsx @@ -4,9 +4,10 @@ import { useState, useEffect, useRef } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { useTheme } from 'next-themes'; -import { ChevronRight, ChevronDown, FileText } from 'lucide-react'; +import { ChevronRight, ChevronDown, FileText} from 'lucide-react'; import { RelatedProjects } from './RelatedProjects'; import { useDocsMenu } from './DocsProvider'; +import { SidebarFooter } from './SidebarFooter'; interface MenuItem { name: string; @@ -48,31 +49,47 @@ export function DocsSidebar({ pageMap, className }: DocsSidebarProps) { const textColor = isDark ? '#e5e7eb' : '#374151'; // gray-200 : gray-700 // Stable layout values - only recalculate on resize or banner change const [layoutValues, setLayoutValues] = useState({ top: '4rem', height: 'calc(100vh - 4rem)' }); - const layoutCalculatedRef = useRef(false); + + const calculateOffsets = () => { + const navbar = document.querySelector('.nextra-nav-container') as HTMLElement | null; + if (!navbar) return; + + const navbarBottomY = navbar.getBoundingClientRect().bottom; + + setLayoutValues({ + top: `${navbarBottomY}px`, + height: `calc(100vh - ${navbarBottomY}px)` + }); + }; + useEffect(() => { - const calculateOffsets = () => { - const navbar = document.querySelector('.nextra-nav-container'); - if (navbar) { - const navbarHeight = (navbar as HTMLElement).offsetHeight; - const newTop = `${navbarHeight}px`; - const newHeight = `calc(100vh - ${navbarHeight}px)`; - setLayoutValues({ top: newTop, height: newHeight }); - layoutCalculatedRef.current = true; + let ticking = false; + + const onScroll = () => { + if (!ticking) { + ticking = true; + requestAnimationFrame(() => { + calculateOffsets(); + ticking = false; + }); } }; - // Calculate on mount and when banner state changes - // Use setTimeout to allow DOM to update after banner dismiss - const timeoutId = setTimeout(calculateOffsets, 50); + const t = setTimeout(calculateOffsets, 300); window.addEventListener('resize', calculateOffsets); + window.addEventListener('scroll', onScroll, { passive: true }); + return () => { - clearTimeout(timeoutId); + clearTimeout(t); window.removeEventListener('resize', calculateOffsets); + window.removeEventListener('scroll', onScroll); }; }, [bannerDismissed]); + + // Store initial pathname for initialization const initialPathnameRef = useRef(pathname); @@ -128,7 +145,7 @@ export function DocsSidebar({ pageMap, className }: DocsSidebarProps) { findActivePath(pageMap); collapseAll(pageMap); setCollapsed(initialCollapsed); - }, [pageMap]); + }, [pageMap, navInitialized, setCollapsed]); const toggleCollapse = (itemKey: string) => { toggleNavCollapsed(itemKey); @@ -233,12 +250,9 @@ export function DocsSidebar({ pageMap, className }: DocsSidebarProps) { +
- - {/* Related Projects - fixed at bottom, shrink-0 prevents shrinking */} -
- -
+ ); @@ -249,7 +263,7 @@ export function DocsSidebar({ pageMap, className }: DocsSidebarProps) {
{/* Footer with icon buttons */} - +
); diff --git a/src/components/docs/RelatedProjects.tsx b/src/components/docs/RelatedProjects.tsx index 47bcf86c..90b220bf 100644 --- a/src/components/docs/RelatedProjects.tsx +++ b/src/components/docs/RelatedProjects.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'; import { usePathname } from 'next/navigation'; import { useTheme } from 'next-themes'; -import { ChevronRight, ChevronDown, Moon, Sun, PanelRightOpenIcon, PanelLeftOpen } from 'lucide-react'; +import { ChevronRight, ChevronDown, Moon, Sun, PanelLeftOpen } from 'lucide-react'; import { useSharedConfig } from '@/hooks/useSharedConfig'; // Production URL - all cross-project links go here @@ -25,7 +25,7 @@ interface RelatedProjectsProps { bannerActive?: boolean; } -export function RelatedProjects({ variant = 'full', onCollapse, isMobile = false, bannerActive = false }: RelatedProjectsProps) { +export function RelatedProjects({ variant = 'full', onCollapse, bannerActive = false }: RelatedProjectsProps) { const [isExpanded, setIsExpanded] = useState(true); const [mounted, setMounted] = useState(false); const pathname = usePathname(); @@ -180,51 +180,6 @@ export function RelatedProjects({ variant = 'full', onCollapse, isMobile = false ); })}
- - {/* Footer Controls */} - {mounted && ( -
- {/* Theme Toggle Button */} - - - {/* Collapse Sidebar Button - Hidden on mobile */} - {onCollapse && !isMobile && ( - - )} -
- )}
); }