Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/docs/DocsBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function DocsBanner() {
const isDark = resolvedTheme === 'dark'

return (
<div className={`relative ${isDark ? 'bg-neutral-900' : 'bg-blue-50'} border-b ${isDark ? 'border-gray-800' : 'border-blue-100'}`}>
<div className={`docs-banner relative ${isDark ? 'bg-neutral-900' : 'bg-blue-50'} border-b ${isDark ? 'border-gray-800' : 'border-blue-100'}`}>
<div className="max-w-7xl mx-auto py-2 px-3 sm:px-6 lg:px-8">
<div className="flex items-center justify-between flex-wrap">
<div className="flex-1 flex items-center justify-center">
Expand Down
56 changes: 35 additions & 21 deletions src/components/docs/DocsSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -233,12 +250,9 @@ export function DocsSidebar({ pageMap, className }: DocsSidebarProps) {
<nav className="p-4 pb-6 w-full space-y-2">
{pageMap.map(item => renderMenuItem(item))}
</nav>
<RelatedProjects bannerActive={!bannerDismissed} />
</div>

{/* Related Projects - fixed at bottom, shrink-0 prevents shrinking */}
<div className="shrink-0">
<RelatedProjects onCollapse={toggleSidebar} isMobile={menuOpen} bannerActive={!bannerDismissed} />
</div>
<SidebarFooter onCollapse={toggleSidebar} isMobile={menuOpen} />
</>
);

Expand All @@ -249,7 +263,7 @@ export function DocsSidebar({ pageMap, className }: DocsSidebarProps) {
<div className="flex-1"></div>

{/* Footer with icon buttons */}
<RelatedProjects onCollapse={toggleSidebar} variant="slim" />
<SidebarFooter onCollapse={toggleSidebar} isMobile={menuOpen} variant="slim" />
</div>
);

Expand Down
49 changes: 2 additions & 47 deletions src/components/docs/RelatedProjects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
Expand Down Expand Up @@ -180,51 +180,6 @@ export function RelatedProjects({ variant = 'full', onCollapse, isMobile = false
);
})}
</div>

{/* Footer Controls */}
{mounted && (
<div
className={`flex items-center gap-2 border-t border-gray-200 dark:border-gray-700 ${bannerActive ? 'pt-2 mt-1' : 'pt-3 mt-2'}`}
suppressHydrationWarning
>
{/* Theme Toggle Button */}
<button
onClick={() => setTheme(isDark ? 'light' : 'dark')}
title="Change theme"
className="group cursor-pointer h-7 rounded-md px-2 text-sm font-thin transition-all hover:font-bold flex items-center gap-2 flex-1"
style={{ color: textColor }}
suppressHydrationWarning
>
<div className="relative w-5 h-5">
<Moon
className={`absolute inset-0 w-5 h-5 transition-all duration-300 group-hover:rotate-45 ${
isDark ? 'opacity-100 rotate-0 scale-100' : 'opacity-0 -rotate-90 scale-0'
}`}
/>
<Sun
className={`absolute inset-0 w-5 h-5 transition-all duration-300 group-hover:rotate-45 ${
!isDark ? 'opacity-100 rotate-0 scale-100' : 'opacity-0 rotate-90 scale-0'
}`}
/>
</div>
<span>{isDark ? 'Dark' : 'Light'}</span>
</button>

{/* Collapse Sidebar Button - Hidden on mobile */}
{onCollapse && !isMobile && (
<button
onClick={onCollapse}
className="transition-all cursor-pointer rounded-md p-2 hover:font-bold"
style={{ color: textColor }}
title="Collapse sidebar"
type="button"
suppressHydrationWarning
>
<PanelRightOpenIcon className="w-4 h-4" />
</button>
)}
</div>
)}
</div>
);
}