import React, { useState, useEffect, useRef } from 'react' import { Menu, X, type LucideIcon } from 'lucide-react' export interface PageSidebarTab { id: string label: string icon: LucideIcon } interface PageSidebarProps { /** Uppercase label shown above the tab list, e.g. "SETTINGS". */ sidebarLabel: string tabs: PageSidebarTab[] activeTab: string onTabChange: (id: string) => void children: React.ReactNode /** Small text at the very bottom of the sidebar (e.g. "v3.0 ยท self-hosted"). */ footer?: React.ReactNode } /** * Left-sidebar + right-panel layout used by the Settings and Admin pages. * * Desktop (>=1024px): sidebar is always visible at 260px; panel fills rest. * Mobile: sidebar collapses behind a hamburger at the top of the panel; tap * the hamburger to slide the sidebar in as an overlay, tap a tab to close. */ export default function PageSidebar({ sidebarLabel, tabs, activeTab, onTabChange, children, footer, }: PageSidebarProps): React.ReactElement { const [mobileOpen, setMobileOpen] = useState(false) const activeLabel = tabs.find(t => t.id === activeTab)?.label ?? '' // Close the mobile drawer on Escape or on outside click. const drawerRef = useRef(null) useEffect(() => { if (!mobileOpen) return const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setMobileOpen(false) } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [mobileOpen]) return (
{/* Mobile top bar with hamburger */}
{activeLabel}
{/* Desktop sidebar (always visible on lg) */} {/* Mobile drawer */} {mobileOpen && ( <>
setMobileOpen(false)} /> )} {/* Panel */}
{children}
) } function SidebarInner({ sidebarLabel, tabs, activeTab, onTabChange, footer, }: { sidebarLabel: string | null tabs: PageSidebarTab[] activeTab: string onTabChange: (id: string) => void footer?: React.ReactNode }): React.ReactElement { return ( <> {sidebarLabel && (
{sidebarLabel}
)} {footer && (
{footer}
)} ) }