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
67 lines
2.7 KiB
TypeScript
67 lines
2.7 KiB
TypeScript
/**
|
|
* ReaderToolbar — fixed bottom toolbar for the reading view.
|
|
* Provides TOC toggle, settings toggle, and progress indicator.
|
|
*/
|
|
|
|
import type { ReadingProgress } from "../types/reader";
|
|
|
|
interface ReaderToolbarProps {
|
|
bookTitle: string;
|
|
chapterTitle: string;
|
|
progress: ReadingProgress | null;
|
|
onToggleToc: () => void;
|
|
onToggleSettings: () => void;
|
|
}
|
|
|
|
export default function ReaderToolbar({
|
|
bookTitle,
|
|
chapterTitle,
|
|
progress,
|
|
onToggleToc,
|
|
onToggleSettings,
|
|
}: ReaderToolbarProps) {
|
|
const percentage = progress?.percentage ?? 0;
|
|
|
|
return (
|
|
<>
|
|
{/* Top bar */}
|
|
<header className="reader-top-bar">
|
|
<button
|
|
type="button"
|
|
className="reader-bar-btn"
|
|
onClick={onToggleToc}
|
|
aria-label="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="3" y1="6" x2="21" y2="6" />
|
|
<line x1="3" y1="12" x2="21" y2="12" />
|
|
<line x1="3" y1="18" x2="21" y2="18" />
|
|
</svg>
|
|
</button>
|
|
<div className="reader-bar-title">
|
|
<span className="reader-bar-book">{bookTitle}</span>
|
|
<span className="reader-bar-chapter">{chapterTitle}</span>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="reader-bar-btn"
|
|
onClick={onToggleSettings}
|
|
aria-label="Reading settings"
|
|
>
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="12" cy="12" r="3" />
|
|
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
|
</svg>
|
|
</button>
|
|
</header>
|
|
|
|
{/* Bottom progress bar */}
|
|
<div className="reader-progress-bar">
|
|
<div
|
|
className="reader-progress-fill"
|
|
style={{ width: `${Math.min(percentage, 100)}%` }}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
} |