fix: Separate mobile/desktop sidebar components

- Mobile: fixed overlay sidebar with proper z-index
- Desktop: sticky sidebar with correct top offset
- Extracted SidebarContent component to avoid duplication
- Matches App.tsx submenu bar positioning logic
This commit is contained in:
Dwindi Ramadhana
2026-01-04 12:33:46 +07:00
parent 3a8c436839
commit 54a1ec1c88

View File

@@ -27,14 +27,15 @@ export default function Help() {
const currentSlug = searchParams.get('doc') || 'getting-started';
// Calculate sticky top position based on mode
// Standalone/fullscreen mode: top-16 (below 64px header)
// WP Admin mode: top-[calc(7rem+32px)] (header + topnav + wp-admin bar)
const sidebarTopClass = isStandalone
? 'top-16'
// This matches the submenu bar logic in App.tsx:
// - Standalone/fullscreen: top-0 (header already handles offset)
// - WP Admin: top-[calc(7rem+32px)] = 144px (header 64px + topnav 48px + wp-admin bar 32px)
const sidebarStickyTop = isStandalone
? 'top-0'
: 'top-[calc(7rem+32px)]';
// Height calculation: full height minus header
const sidebarHeightClass = isStandalone
// Height calculation matches App.tsx Sidebar pattern
const sidebarHeight = isStandalone
? 'h-[calc(100vh-64px)]'
: 'h-[calc(100vh-7rem-32px)]';
@@ -53,7 +54,6 @@ export default function Help() {
if (data.success) {
setSections(data.sections);
// Expand all sections by default
const expanded: Record<string, boolean> = {};
data.sections.forEach((section: DocSection) => {
expanded[section.key] = true;
@@ -85,8 +85,8 @@ export default function Help() {
const isActive = (slug: string) => slug === currentSlug;
return (
<div className="flex min-h-0">
{/* Mobile menu button */}
<div className="flex">
{/* Mobile menu button - only show on small screens */}
<Button
variant="ghost"
size="icon"
@@ -96,74 +96,7 @@ export default function Help() {
{sidebarOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</Button>
{/* Sidebar - sticky on desktop, fixed on mobile */}
<aside
className={cn(
"w-72 border-r bg-muted/30 flex-shrink-0 overflow-y-auto",
// Mobile: fixed position covering from below header
"fixed left-0 z-40",
sidebarTopClass,
sidebarHeightClass,
// Desktop: sticky instead of fixed
"lg:sticky",
sidebarOpen ? "block" : "hidden lg:block"
)}
>
<div className="p-4 border-b bg-muted/30">
<h2 className="text-lg font-semibold flex items-center gap-2">
<Book className="w-5 h-5" />
Documentation
</h2>
<p className="text-sm text-muted-foreground">Help & Guides</p>
</div>
<nav className="p-2">
{loading ? (
<div className="p-4 text-sm text-muted-foreground">Loading...</div>
) : sections.length === 0 ? (
<div className="p-4 text-sm text-muted-foreground">No documentation available</div>
) : (
sections.map((section) => (
<div key={section.key} className="mb-2">
<button
onClick={() => toggleSection(section.key)}
className="w-full flex items-center gap-2 px-3 py-2 text-sm font-medium text-foreground hover:bg-muted rounded-md"
>
{iconMap[section.icon] || <FileText className="w-4 h-4" />}
<span className="flex-1 text-left">{section.label}</span>
<ChevronRight
className={cn(
"w-4 h-4 transition-transform",
expandedSections[section.key] && "rotate-90"
)}
/>
</button>
{expandedSections[section.key] && (
<div className="ml-4 mt-1 space-y-1">
{section.items.map((item) => (
<button
key={item.slug}
onClick={() => selectDoc(item.slug)}
className={cn(
"w-full text-left px-3 py-1.5 text-sm rounded-md transition-colors",
isActive(item.slug)
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground"
)}
>
{item.title}
</button>
))}
</div>
)}
</div>
))
)}
</nav>
</aside>
{/* Backdrop for mobile */}
{/* Backdrop for mobile sidebar */}
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-30 lg:hidden"
@@ -171,7 +104,44 @@ export default function Help() {
/>
)}
{/* Main content - uses page scroll, not internal overflow */}
{/* Mobile sidebar - fixed overlay */}
<aside
className={cn(
"lg:hidden fixed left-0 z-40 w-72 bg-background border-r overflow-y-auto",
sidebarStickyTop,
sidebarHeight,
sidebarOpen ? "block" : "hidden"
)}
>
<SidebarContent
loading={loading}
sections={sections}
expandedSections={expandedSections}
toggleSection={toggleSection}
selectDoc={selectDoc}
isActive={isActive}
/>
</aside>
{/* Desktop sidebar - sticky */}
<aside
className={cn(
"hidden lg:block w-72 flex-shrink-0 border-r bg-muted/30 overflow-y-auto sticky",
sidebarStickyTop,
sidebarHeight
)}
>
<SidebarContent
loading={loading}
sections={sections}
expandedSections={expandedSections}
toggleSection={toggleSection}
selectDoc={selectDoc}
isActive={isActive}
/>
</aside>
{/* Main content - uses page scroll */}
<main className="flex-1 min-w-0">
<div className="max-w-4xl mx-auto py-6 px-6 lg:px-10">
<DocContent slug={currentSlug} />
@@ -180,3 +150,77 @@ export default function Help() {
</div>
);
}
// Extracted sidebar content to avoid duplication
function SidebarContent({
loading,
sections,
expandedSections,
toggleSection,
selectDoc,
isActive,
}: {
loading: boolean;
sections: DocSection[];
expandedSections: Record<string, boolean>;
toggleSection: (key: string) => void;
selectDoc: (slug: string) => void;
isActive: (slug: string) => boolean;
}) {
return (
<>
<div className="p-4 border-b bg-muted/30 sticky top-0">
<h2 className="text-lg font-semibold flex items-center gap-2">
<Book className="w-5 h-5" />
Documentation
</h2>
<p className="text-sm text-muted-foreground">Help & Guides</p>
</div>
<nav className="p-2">
{loading ? (
<div className="p-4 text-sm text-muted-foreground">Loading...</div>
) : sections.length === 0 ? (
<div className="p-4 text-sm text-muted-foreground">No documentation available</div>
) : (
sections.map((section) => (
<div key={section.key} className="mb-2">
<button
onClick={() => toggleSection(section.key)}
className="w-full flex items-center gap-2 px-3 py-2 text-sm font-medium text-foreground hover:bg-muted rounded-md"
>
{iconMap[section.icon] || <FileText className="w-4 h-4" />}
<span className="flex-1 text-left">{section.label}</span>
<ChevronRight
className={cn(
"w-4 h-4 transition-transform",
expandedSections[section.key] && "rotate-90"
)}
/>
</button>
{expandedSections[section.key] && (
<div className="ml-4 mt-1 space-y-1">
{section.items.map((item) => (
<button
key={item.slug}
onClick={() => selectDoc(item.slug)}
className={cn(
"w-full text-left px-3 py-1.5 text-sm rounded-md transition-colors",
isActive(item.slug)
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground"
)}
>
{item.title}
</button>
))}
</div>
)}
</div>
))
)}
</nav>
</>
);
}