'use client'; import { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { FiList, FiX } from 'react-icons/fi'; import { PostToc } from './post-toc'; import { clsx, type ClassValue } from 'clsx'; import { twMerge } from 'tailwind-merge'; function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export function PostLayout({ children, hasToc = true, contentKey }: { children: React.ReactNode; hasToc?: boolean; contentKey?: string }) { const [isTocOpen, setIsTocOpen] = useState(false); // Default closed on mobile const [isDesktopTocOpen, setIsDesktopTocOpen] = useState(hasToc); // Separate state for desktop const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); // Lock body scroll when mobile TOC is open useEffect(() => { if (isTocOpen) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } return () => { document.body.style.overflow = ''; }; }, [isTocOpen]); const mobileToc = hasToc && mounted ? createPortal( <> {/* Backdrop */}
setIsTocOpen(false)} aria-hidden="true" /> {/* Drawer */}
{/* Handle / Header */}
setIsTocOpen(false)}>
目錄
{/* Content */}
setIsTocOpen(false)} showTitle={false} className="w-full" />
, document.body ) : null; const tocButton = hasToc && mounted ? ( ) : null; const desktopTocButton = hasToc && mounted ? ( ) : null; return (
{/* Main Content Area */}
{children}
{/* Desktop Sidebar (TOC) */}
{/* Mobile TOC Overlay */} {mobileToc} {/* Toggle Buttons - Rendered via Portal */} {mounted && createPortal( <> {tocButton} {desktopTocButton} , document.body )}
); }