開発コンポーネント
Reader Viewerコンポーネント仕様書
作品読み込みビューアのコンポーネント仕様書
Reader Viewerコンポーネント仕様書
最終更新: 2025-11-26
バージョン: 1.2.0(Phase 6対応)
概要
公開された作品を読むための没入型の読書体験を提供するコンポーネント群です。グローバルUIから独立した専用の空間として設計されており、読書進捗の追跡、現在位置の視覚的フィードバック、Markdownレンダリング、小説に最適化されたタイポグラフィを提供します。
コンポーネント一覧
ReaderHeader
Reader専用の固定ヘッダーコンポーネントです。
責務
- 戻るナビゲーション:
router.back()で前のページに戻る(履歴スタックを1つ戻る) - 閉じるボタン:
ArrowLeftからXアイコンに変更し、Readerを閉じる意図を明確化 - 作品タイトル表示: 中央に作品タイトルを表示(長い場合はtruncate)
- 現在の話タイトル表示 (v1.1.0): デスクトップで「作品タイトル | 現在の話タイトル」を表示
- 目次トリガー: 目次ドロワー(
TableOfContents)を開くボタンを提供 - すりガラス効果:
bg-background/80+backdrop-blur-mdで本文スクロール時に透過感を演出
主要なProps
interface ReaderHeaderProps {
title: string; // 作品タイトル
structuredPlot: StructuredPlot; // プロット構造(目次生成に使用)
}スタイル特性
fixed top-0 left-0 right-0: 画面上部に固定z-index: 50: 本文より上に配置h-14: 高さ56pxborder-b border-border/40: 極めて薄い下線で読書の邪魔にならないデザイン
レスポンシブ対応 (v1.1.0)
- モバイル: 作品タイトルのみ表示
- デスクトップ: 「作品タイトル | 現在の話タイトル」を表示
使用しているUIコンポーネント
Button(variant="ghost", size="icon")ArrowLeft,Menuアイコン (lucide-react)
関連コンポーネント
TableOfContents
実装の詳細 (v1.1.0)
// 現在の話タイトルを取得
const params = useParams();
const projectId = params.id as string;
const readingProgress = useAtomValue(readingProgressAtom);
const currentStoryId = readingProgress[projectId];
// structuredPlotから現在の話タイトルを検索
const currentStoryTitle = useMemo(() => {
// ... 検索ロジック
}, [structuredPlot, currentStoryId]);
// レスポンシブ表示
<div className="hidden md:block">
{title} | {currentStoryTitle}
</div>
<div className="md:hidden">
{title}
</div>ReaderContent
本文を階層的にレンダリングするメインコンポーネントです。
責務
- 階層的レンダリング: Part > Arc > Chapter > Storyの構造を解釈し、現在の話を表示
- ページネーション: 前の話・次の話へのナビゲーションボタンを表示(
replace={true}で履歴を置換) - Markdownレンダリング (v1.1.0):
MarkdownRendererを使用してリッチなコンテンツを表示 - 原稿用フォント適用 (v1.1.0): ユーザー設定のフォントファミリー、サイズ、ウェイトを適用
- 読書進捗管理:
useReadingProgressフックを使用して、現在の読書位置を自動保存 - 後方互換性: 新構造(parts/arcs)と旧構造(chapters直接)の両方に対応
主要なProps
interface ReaderContentProps {
projectContent: ProjectContent; // プロジェクトの全コンテンツ
storyId: string; // 表示する話のID
}
interface ProjectContent {
structuredPlot: StructuredPlot; // プロット構造
manuscripts: Manuscripts; // 原稿データ
}タイポグラフィ設計
- コンテナ:
max-w-2xl mx-auto px-6 py-24(最適な読書幅) - Part見出し:
text-sm font-bold tracking-widest uppercase text-muted-foreground mb-12 mt-24 - Chapter見出し:
text-2xl font-bold mb-8 mt-16 border-b pb-2 text-center - Story見出し:
text-xl font-semibold mb-6 mt-12 text-center - 本文 (v1.1.0): 原稿用フォント設定を適用
原稿用フォント設定の適用 (v1.1.0)
<div
className="reader-prose leading-relaxed tracking-wide text-foreground/90"
style={{
fontFamily: 'var(--font-manuscript)',
fontSize: 'var(--font-manuscript-size, 16px)',
fontWeight: 'var(--font-manuscript-weight, 400)',
}}
>
<MarkdownRenderer markdownText={version.content} />
</div>レンダリングロジック
// 階層見出しの動的挿入
structuredPlot.parts.map((part) => (
<>
<div className="part-heading">{part.part_title}</div>
{part.arcs.map((arc) => (
arc.chapters.map((chapter) => (
<>
<h2 className="chapter-heading">{chapter.chapter_title}</h2>
{chapter.stories.map(renderStory)}
</>
))
))}
</>
))関連カスタムフック
useReadingProgress
関連Atoms
readingProgressAtom(間接的に使用)
TableOfContents
目次をドロワー形式で表示し、任意の位置へのジャンプを可能にするコンポーネントです。
責務
- 階層的な目次表示: Part > Arc > Chapter > Storyの構造をツリー形式で表示
- 現在位置のハイライト (v1.1.0): 現在読んでいる話を視覚的に強調
- インデント表現:
pl-4,pl-8などで階層の深さを視覚的に表現 - リンクナビゲーション (v1.3.0): Storyクリック時に
Next.js Linkを使用して該当ページへ遷移(replace={true}で履歴を置換) - ドロワー自動クローズ: ジャンプ後にドロワーを閉じる
主要なProps
interface TableOfContentsProps {
open: boolean; // ドロワーの開閉状態
onOpenChange: (open: boolean) => void; // ドロワーの開閉を制御するコールバック
structuredPlot: StructuredPlot; // プロット構造
onSelectStory: (storyId: string) => void; // Storyが選択されたときのコールバック
}現在位置のハイライト (v1.1.0)
// 現在の話を取得
const params = useParams();
const projectId = params.id as string;
const readingProgress = useAtomValue(readingProgressAtom);
const currentStoryId = readingProgress[projectId];
// ハイライトスタイル
const isCurrentStory = story.id === currentStoryId;
<Button
className={cn(
isCurrentStory && "bg-primary/10 border-l-4 border-primary text-primary"
)}
>
{isCurrentStory && <BookOpen className="h-4 w-4 mr-2" />}
{story.story_title}
</Button>使用しているUIコンポーネント
Sheet,SheetContent,SheetHeader,SheetTitle(shadcn/ui)ScrollArea(shadcn/ui)Button(shadcn/ui)BookOpenアイコン (lucide-react) (v1.1.0)
スタイル特性
w-[300px] sm:w-[400px]: レスポンシブな幅h-[calc(100vh-8rem)]: ヘッダーを除いた高さ
後方互換性
structuredPlot.partsが存在しない場合、structuredPlot.chaptersを直接表示
useReadingProgress (カスタムフック)
読書進捗の自動保存・復元を実現するカスタムフックです。
責務
- 進捗の自動検知: ページロード時にURLパラメータから現在のstoryIdを取得
- 進捗の保存: 検知したstoryIdを
readingProgressAtomに保存(自動的にlocalStorageに永続化)
引数
function useReadingProgress(projectId: string): void実装の詳細
// IntersectionObserver設定
{
rootMargin: "-40% 0px -40% 0px" // 画面中央付近を「現在位置」と判定
}
// 位置復元 (v1.1.0: block: "start"に変更)
setTimeout(() => {
document.getElementById(lastStoryId)?.scrollIntoView({
behavior: "auto",
block: "start" // 話の冒頭にスクロール
});
}, 100); // DOMレンダリング完了を待つパフォーマンス考慮
- Observer登録は
.story-containerクラスを持つ要素のみ - 進捗更新は自動的にデバウンスされる(atomWithStorageの特性)
関連Atoms
readingProgressAtom
スタイルシステム
markdown-reader.css (v1.1.0)
Reader専用のMarkdownスタイルを定義したCSSファイルです。小説の読書体験に最適化されたタイポグラフィを提供します。
主要なスタイル設定
.reader-prose {
/* 小説に最適な行間 */
line-height: 1.9;
/* 日本語の可読性を向上させる字間 */
letter-spacing: 0.05em;
/* テキストを両端揃えで美しく */
text-align: justify;
}
.reader-prose p {
/* 段落間に十分な余白を確保 */
margin-bottom: 2rem;
/* 段落の最初の行をインデント(小説らしさ) */
text-indent: 1em;
}
.reader-prose h1,
.reader-prose h2,
.reader-prose h3,
.reader-prose h4,
.reader-prose h5,
.reader-prose h6 {
/* 見出しは中央揃え */
text-align: center;
/* 見出しは字間を少し広めに */
letter-spacing: 0.1em;
}
.reader-prose blockquote {
/* ミニマリズムなスタイル */
border-left: 2px solid hsl(var(--border));
color: hsl(var(--foreground) / 0.7);
font-size: 0.95em;
}
.reader-prose code {
/* インラインコードの改行を保持 */
white-space: pre-wrap;
word-break: break-word;
}Reader Viewerの設計思想
1. 没入型体験
- グローバルヘッダー/フッターを完全に排除
- タイポグラフィと余白を重視した読みやすいレイアウト
- 原稿用フォント設定を完全に適用(v1.1.0)
- 小説に最適化された行間、字間、段落スペース(v1.1.0)
2. シームレスな読書再開
- ユーザーが意識せずとも、前回の位置から自動的に読書を再開できる
- IntersectionObserverによる自然な進捗検知
- 話の冒頭にスクロールして自然な読書フローを提供(v1.1.0)
3. 現在位置の明確化 (v1.1.0)
- 目次ドロワーで現在の話をハイライト表示
- ヘッダーに現在の話タイトルを表示(デスクトップ)
- ユーザーが常に「今どこを読んでいるか」を把握できる
4. リッチなコンテンツ表示 (v1.1.0)
- Markdownレンダリングによる表現力の向上
- コードブロック、引用、見出しなどの適切なスタイリング
- シンタックスハイライト対応
5. 階層構造の尊重
- 複雑なプロット構造(Part > Arc > Chapter > Story)を適切に表現
- 見出しのサイズと余白で階層の深さを視覚的に示す
6. パフォーマンス
- Server Componentでデータ取得(初期ロード高速化)
- IntersectionObserverで効率的なスクロール検知
- localStorageで高速な進捗永続化
変更履歴
v1.3.1 (2025-12-02) - History Management
- 履歴管理の最適化:
- Reader内での話移動(次へ/前へ/目次)を
replace遷移に変更 - Readerを「閉じる」ボタン(Xアイコン)で
router.back()を実行 - これにより、何話読んでも「閉じる」で即座に元の画面(詳細ページ等)に戻れる「モーダル的挙動」を実現
- Reader内での話移動(次へ/前へ/目次)を
v1.3.0 (2025-12-02) - Pagination Refactoring
- ページネーション化:
- 全話を縦積み表示から、1話ごとのページ遷移に変更
- URL構造を
/read/[id]/[storyId]に変更 - 前の話・次の話へのナビゲーションボタンを追加
- パフォーマンス改善:
- 1話分のみレンダリングすることで初期ロード時間を短縮
- ナビゲーション:
- 目次リンクをアンカーリンクからページリンクに変更
v1.2.0 (2025-11-26) - Phase 6対応
- 多言語対応:
- ヘッダーに言語切り替えドロップダウンを追加
- 選択した言語を
atomWithStorageで永続化 - 本文表示時に選択言語のバージョンを動的に適用
- データ構造とレンダリング:
manuscriptsデータの配列化(順序保証)に対応- 配列・オブジェクト両形式に対応する
manuscriptMapを導入し、O(1)ルックアップを実現 - 階層的レンダリング(
structuredPlot走査)を維持しつつ、データアクセスを最適化 - 旧データ形式(オブジェクト)への後方互換性を維持(オンザフライ変換)
- UI改善:
- セクション間に
***の区切り線を追加し、場面転換を視覚的に表現
- セクション間に
v1.1.0 (2025-11-25) - Phase 5-2対応
- 読書進捗: スクロール位置を話の冒頭(
block: "start")に変更 - 現在位置表示:
- 目次ドロワーで現在の話をハイライト
- ヘッダーに現在の話タイトルを表示(レスポンシブ対応)
- Markdownレンダリング:
MarkdownRendererを統合 - フォント設定: 原稿用フォントのサイズとウェイトを完全適用
- タイポグラフィ:
markdown-reader.cssで小説に最適化されたスタイルを実装
v1.0.0 (初期実装)
- 基本的なReader Viewer機能
- 読書進捗の自動保存・復元
- 階層的な目次表示
- すりガラス効果のヘッダー
関連ドキュメント
/doc/system/06_routing_architecture.md- ルーティング設計/doc/system/03_state_management.md- Jotaiによる状態管理/doc/log/reports/2025-11-25_report_reader-viewer-improvements.md- Phase 5-2作業報告書/doc/log/reports/2025-11-26_report_explore-phase6-cleanup.md- Phase 6作業報告書