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 { Book, ChevronRight, FileText, Settings, Layers, Puzzle, Menu, X } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { useApp } from '@/contexts/AppContext';
import DocContent from './DocContent'; import DocContent from './DocContent';
import type { DocSection } from './types'; import type { DocSection } from './types';
@@ -21,8 +22,22 @@ export default function Help() {
const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>({}); const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>({});
const [sidebarOpen, setSidebarOpen] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(false);
const { isStandalone } = useApp();
const currentSlug = searchParams.get('doc') || 'getting-started'; 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 // Fetch documentation registry
useEffect(() => { useEffect(() => {
const fetchDocs = async () => { const fetchDocs = async () => {
@@ -64,32 +79,35 @@ export default function Help() {
const selectDoc = (slug: string) => { const selectDoc = (slug: string) => {
setSearchParams({ doc: slug }); setSearchParams({ doc: slug });
setSidebarOpen(false); // Close mobile sidebar setSidebarOpen(false);
}; };
const isActive = (slug: string) => slug === currentSlug; const isActive = (slug: string) => slug === currentSlug;
return ( return (
<div className="flex h-[calc(100vh-64px)] bg-background"> <div className="flex min-h-0">
{/* Mobile menu button */} {/* Mobile menu button */}
<Button <Button
variant="ghost" variant="ghost"
size="icon" 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)} onClick={() => setSidebarOpen(!sidebarOpen)}
> >
{sidebarOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />} {sidebarOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</Button> </Button>
{/* Sidebar */} {/* Sidebar - sticky, positioned below header */}
<aside <aside
className={cn( className={cn(
"w-72 border-r bg-muted/30 flex-shrink-0 transition-transform duration-300", "w-72 border-r bg-muted/30 flex-shrink-0",
"fixed lg:relative inset-y-0 left-0 z-40 lg:translate-x-0", "fixed lg:sticky inset-y-0 left-0 z-40",
sidebarOpen ? "translate-x-0" : "-translate-x-full" 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"> <h2 className="text-lg font-semibold flex items-center gap-2">
<Book className="w-5 h-5" /> <Book className="w-5 h-5" />
Documentation Documentation
@@ -97,52 +115,50 @@ export default function Help() {
<p className="text-sm text-muted-foreground">Help & Guides</p> <p className="text-sm text-muted-foreground">Help & Guides</p>
</div> </div>
<div className="h-[calc(100vh-180px)] overflow-y-auto"> <nav className="p-2">
<nav className="p-2"> {loading ? (
{loading ? ( <div className="p-4 text-sm text-muted-foreground">Loading...</div>
<div className="p-4 text-sm text-muted-foreground">Loading...</div> ) : sections.length === 0 ? (
) : sections.length === 0 ? ( <div className="p-4 text-sm text-muted-foreground">No documentation available</div>
<div className="p-4 text-sm text-muted-foreground">No documentation available</div> ) : (
) : ( sections.map((section) => (
sections.map((section) => ( <div key={section.key} className="mb-2">
<div key={section.key} className="mb-2"> <button
<button onClick={() => toggleSection(section.key)}
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"
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" />}
{iconMap[section.icon] || <FileText className="w-4 h-4" />} <span className="flex-1 text-left">{section.label}</span>
<span className="flex-1 text-left">{section.label}</span> <ChevronRight
<ChevronRight className={cn(
className={cn( "w-4 h-4 transition-transform",
"w-4 h-4 transition-transform", expandedSections[section.key] && "rotate-90"
expandedSections[section.key] && "rotate-90" )}
)} />
/> </button>
</button>
{expandedSections[section.key] && ( {expandedSections[section.key] && (
<div className="ml-4 mt-1 space-y-1"> <div className="ml-4 mt-1 space-y-1">
{section.items.map((item) => ( {section.items.map((item) => (
<button <button
key={item.slug} key={item.slug}
onClick={() => selectDoc(item.slug)} onClick={() => selectDoc(item.slug)}
className={cn( className={cn(
"w-full text-left px-3 py-1.5 text-sm rounded-md transition-colors", "w-full text-left px-3 py-1.5 text-sm rounded-md transition-colors",
isActive(item.slug) isActive(item.slug)
? "bg-primary text-primary-foreground" ? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
)} )}
> >
{item.title} {item.title}
</button> </button>
))} ))}
</div> </div>
)} )}
</div> </div>
)) ))
)} )}
</nav> </nav>
</div>
</aside> </aside>
{/* Backdrop for mobile */} {/* Backdrop for mobile */}
@@ -153,9 +169,9 @@ export default function Help() {
/> />
)} )}
{/* Main content */} {/* Main content - uses page scroll, not internal overflow */}
<main className="flex-1 overflow-y-auto"> <main className="flex-1 min-w-0">
<div className="max-w-4xl mx-auto p-6 lg:p-10"> <div className="max-w-4xl mx-auto py-6 px-6 lg:px-10">
<DocContent slug={currentSlug} /> <DocContent slug={currentSlug} />
</div> </div>
</main> </main>