Archived
- Backend: Chapter, ReadingProgress, ReadingSettings models - Backend: Chapter API (TOC + content), progress tracking, settings CRUD - Frontend: ReadingPage with chapter navigation - Frontend: TableOfContents drawer - Frontend: ReadingSettingsPanel (theme, font, size, orientation) - Frontend: Custom hooks for settings, chapters, progress tracking - CSS: Mobile-first reading view with sepia/dark/light/paper themes - Route: /reader/:bookId reading view from book detail page - Docs: 001-customizable-mobile-reading-experience.md
80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
/**
|
|
* TableOfContents — slide-in drawer listing all chapters.
|
|
* Tap a chapter to navigate. Current chapter is highlighted.
|
|
*/
|
|
|
|
import type { ChapterSummary } from "../types/reader";
|
|
|
|
interface TableOfContentsProps {
|
|
chapters: ChapterSummary[];
|
|
currentChapterNumber: number;
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onNavigate: (number: number) => void;
|
|
}
|
|
|
|
export default function TableOfContents({
|
|
chapters,
|
|
currentChapterNumber,
|
|
isOpen,
|
|
onClose,
|
|
onNavigate,
|
|
}: TableOfContentsProps) {
|
|
const handleChapterClick = (number: number) => {
|
|
onNavigate(number);
|
|
onClose();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{/* Overlay */}
|
|
{isOpen && (
|
|
<div
|
|
className="toc-overlay"
|
|
onClick={onClose}
|
|
onKeyDown={(e: React.KeyboardEvent) => {
|
|
if (e.key === "Escape") onClose();
|
|
}}
|
|
role="presentation"
|
|
/>
|
|
)}
|
|
|
|
{/* Drawer */}
|
|
<aside className={`toc-drawer ${isOpen ? "toc-drawer--open" : ""}`}>
|
|
<div className="toc-header">
|
|
<h2 className="toc-title">Contents</h2>
|
|
<button
|
|
type="button"
|
|
className="toc-close-btn"
|
|
onClick={onClose}
|
|
aria-label="Close table of contents"
|
|
>
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<line x1="18" y1="6" x2="6" y2="18" />
|
|
<line x1="6" y1="6" x2="18" y2="18" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<nav className="toc-list">
|
|
{chapters.length === 0 && (
|
|
<p className="toc-empty">No chapters available.</p>
|
|
)}
|
|
{chapters.map((chapter) => (
|
|
<button
|
|
key={chapter.number}
|
|
type="button"
|
|
className={`toc-item ${
|
|
chapter.number === currentChapterNumber ? "toc-item--active" : ""
|
|
}`}
|
|
onClick={() => handleChapterClick(chapter.number)}
|
|
>
|
|
<span className="toc-item-number">{chapter.number}</span>
|
|
<span className="toc-item-title">{chapter.title}</span>
|
|
</button>
|
|
))}
|
|
</nav>
|
|
</aside>
|
|
</>
|
|
);
|
|
} |