/** * DocsPage Component * * Main layout for the documentation route (#/docs). * Full-page layout with a sticky header, collapsible sidebar on the left, * and scrollable content area on the right. * * Mobile-responsive: sidebar collapses behind a hamburger menu that * opens as an overlay. */ import { useState, useEffect, useCallback } from 'react' import { ArrowLeft, Menu, X, Moon, Sun } from 'lucide-react' import { useHashRoute } from '../../hooks/useHashRoute' import { useTheme } from '../../hooks/useTheme' import { ThemeSelector } from '../ThemeSelector' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { DocsSidebar } from './DocsSidebar' import { DocsSearch } from './DocsSearch' import { DocsContent } from './DocsContent' export function DocsPage() { const [activeSectionId, setActiveSectionId] = useState(null) const [searchQuery, setSearchQuery] = useState('') const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false) const { section: initialSection } = useHashRoute() const { theme, setTheme, darkMode, toggleDarkMode, themes } = useTheme() // On mount, if the hash includes a section id (e.g. #/docs/getting-started), // scroll to it and set it as active useEffect(() => { if (initialSection) { setActiveSectionId(initialSection) // Delay scroll slightly so the DOM is rendered requestAnimationFrame(() => { const element = document.getElementById(initialSection) if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }) } }) } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) // Run only on mount // When a sidebar item is clicked, scroll the corresponding element into view const handleSectionClick = useCallback((id: string) => { setActiveSectionId(id) // Update hash for linkability (without triggering a route change) history.replaceState(null, '', `#/docs/${id}`) const element = document.getElementById(id) if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }) } }, []) // Called by DocsContent's IntersectionObserver when a heading scrolls into view const handleSectionVisible = useCallback((id: string) => { setActiveSectionId(id) }, []) // Close mobile sidebar when pressing Escape useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && mobileSidebarOpen) { setMobileSidebarOpen(false) } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [mobileSidebarOpen]) // Prevent body scroll when mobile sidebar overlay is open useEffect(() => { if (mobileSidebarOpen) { document.body.style.overflow = 'hidden' } else { document.body.style.overflow = '' } return () => { document.body.style.overflow = '' } }, [mobileSidebarOpen]) return (
{/* Sticky header */}
{/* Left side: hamburger (mobile) + title + badge */}
{/* Mobile hamburger button -- only visible below lg breakpoint */} AutoForge Documentation
{/* Right side: theme controls + back button */}
{/* Body: sidebar + content */}
{/* ---------------------------------------------------------------- Desktop sidebar -- visible at lg and above Fixed width, sticky below the header, independently scrollable ---------------------------------------------------------------- */} {/* ---------------------------------------------------------------- Mobile sidebar overlay -- visible below lg breakpoint ---------------------------------------------------------------- */} {mobileSidebarOpen && ( <> {/* Backdrop */}
setMobileSidebarOpen(false)} aria-hidden="true" /> {/* Sidebar panel */} )} {/* ---------------------------------------------------------------- Content area -- fills remaining space, scrollable ---------------------------------------------------------------- */}
) }