fix: Help page scroll and sidebar positioning

- Remove internal overflow (use wp-admin page scroll)
- Sidebar sticky under topbar with correct positioning
- Standalone mode: top-16 (below 64px header)
- WP Admin mode: top-[calc(7rem+32px)] (header+topnav+wp-admin bar)
- Uses useApp() to detect mode
This commit is contained in:
Dwindi Ramadhana
2026-01-04 12:27:51 +07:00
parent f49dde9484
commit bfb961ccbe

View File

@@ -3,6 +3,7 @@ import { useSearchParams } from 'react-router-dom';
import { Book, ChevronRight, FileText, Settings, Layers, Puzzle, Menu, X } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { useApp } from '@/contexts/AppContext';
import DocContent from './DocContent';
import type { DocSection } from './types';
@@ -21,8 +22,22 @@ export default function Help() {
const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>({});
const [sidebarOpen, setSidebarOpen] = useState(false);
const { isStandalone } = useApp();
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'
: 'top-[calc(7rem+32px)]';
// Height calculation: full height minus header
const sidebarHeightClass = isStandalone
? 'h-[calc(100vh-64px)]'
: 'h-[calc(100vh-7rem-32px)]';
// Fetch documentation registry
useEffect(() => {
const fetchDocs = async () => {
@@ -64,32 +79,35 @@ export default function Help() {
const selectDoc = (slug: string) => {
setSearchParams({ doc: slug });
setSidebarOpen(false); // Close mobile sidebar
setSidebarOpen(false);
};
const isActive = (slug: string) => slug === currentSlug;
return (
<div className="flex h-[calc(100vh-64px)] bg-background">
<div className="flex min-h-0">
{/* Mobile menu button */}
<Button
variant="ghost"
size="icon"
className="lg:hidden fixed bottom-4 right-4 z-50 bg-primary text-primary-foreground shadow-lg rounded-full w-12 h-12"
className="lg:hidden fixed bottom-20 right-4 z-50 bg-primary text-primary-foreground shadow-lg rounded-full w-12 h-12"
onClick={() => setSidebarOpen(!sidebarOpen)}
>
{sidebarOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</Button>
{/* Sidebar */}
{/* Sidebar - sticky, positioned below header */}
<aside
className={cn(
"w-72 border-r bg-muted/30 flex-shrink-0 transition-transform duration-300",
"fixed lg:relative inset-y-0 left-0 z-40 lg:translate-x-0",
sidebarOpen ? "translate-x-0" : "-translate-x-full"
"w-72 border-r bg-muted/30 flex-shrink-0",
"fixed lg:sticky inset-y-0 left-0 z-40",
sidebarTopClass,
sidebarHeightClass,
"lg:block overflow-y-auto",
sidebarOpen ? "block" : "hidden lg:block"
)}
>
<div className="p-4 border-b">
<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
@@ -97,52 +115,50 @@ export default function Help() {
<p className="text-sm text-muted-foreground">Help & Guides</p>
</div>
<div className="h-[calc(100vh-180px)] overflow-y-auto">
<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>
<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>
</div>
{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 */}
@@ -153,9 +169,9 @@ export default function Help() {
/>
)}
{/* Main content */}
<main className="flex-1 overflow-y-auto">
<div className="max-w-4xl mx-auto p-6 lg:p-10">
{/* Main content - uses page scroll, not internal overflow */}
<main className="flex-1 min-w-0">
<div className="max-w-4xl mx-auto py-6 px-6 lg:px-10">
<DocContent slug={currentSlug} />
</div>
</main>