Archived
Resolve merge conflicts for PR #25: customizable mobile reading experience
- Backend: Replace PR's Book-only models with main's full models (EBook, BookChapter, DownloadRecord, ReadingProgress, ReadingSettings) - Backend: Add reader app (ReadingSettings model, serializer, view, URL) - Frontend: Port reader feature files from web/ to frontend/ (api, hooks, components/reader/, pages/ReadingPage, types/reader, reader.css) - Frontend: Add /read/:id route to App.tsx for chapter-based reading view - All imports adjusted for frontend/src directory structure
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user