BluePeriod Docs
開発コンポーネント

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: 高さ56px
  • border-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() を実行
    • これにより、何話読んでも「閉じる」で即座に元の画面(詳細ページ等)に戻れる「モーダル的挙動」を実現

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作業報告書

On this page