| import React from "react"; |
| import ReactMarkdown from "react-markdown"; |
| import remarkGfm from "remark-gfm"; |
| import { Minimize2, Type, BookOpen } from "lucide-react"; |
| import { Document } from "../../types"; |
|
|
| interface ReadingModeViewProps { |
| activeDoc: Document; |
| charCount: number; |
| wordCount: number; |
| readingTheme: "dark" | "sepia" | "light"; |
| setReadingTheme: (theme: "dark" | "sepia" | "light") => void; |
| readingFontFamily: "sans" | "serif" | "mono"; |
| setReadingFontFamily: (ff: "sans" | "serif" | "mono") => void; |
| readingFontSize: "sm" | "base" | "lg" | "xl"; |
| setReadingFontSize: (sz: "sm" | "base" | "lg" | "xl") => void; |
| readingLineHeight: "normal" | "relaxed" | "loose"; |
| setReadingLineHeight: (lh: "normal" | "relaxed" | "loose") => void; |
| setIsReadingMode: (isReading: boolean) => void; |
| } |
|
|
| export default function ReadingModeView({ |
| activeDoc, |
| charCount, |
| wordCount, |
| readingTheme, |
| setReadingTheme, |
| readingFontFamily, |
| setReadingFontFamily, |
| readingFontSize, |
| setReadingFontSize, |
| readingLineHeight, |
| setReadingLineHeight, |
| setIsReadingMode |
| }: ReadingModeViewProps) { |
| return ( |
| <div |
| id="reading-mode-canvas" |
| className={`min-h-screen flex flex-col transition-colors duration-250 ${ |
| readingTheme === "dark" ? "bg-[#090B0E] text-slate-200" : |
| readingTheme === "sepia" ? "bg-[#FAF4EB] text-amber-955" : |
| "bg-[#FCFBFA] text-slate-900" |
| }`} |
| > |
| <header className={`sticky top-0 z-30 shadow-sm border-b transition-colors duration-200 ${ |
| readingTheme === "dark" ? "bg-[#0F1216] border-slate-800" : |
| readingTheme === "sepia" ? "bg-[#F3EADF] border-amber-200" : |
| "bg-white border-slate-200" |
| }`}> |
| <div className="max-w-4xl mx-auto px-6 h-14 flex items-center justify-between"> |
| <div className="flex items-center gap-2 animate-pulse"> |
| <span className="text-[10px] font-mono tracking-wider uppercase text-slate-500"> |
| Reading: <span className="font-semibold text-blue-505 font-mono text-xs">{activeDoc.title}</span> |
| </span> |
| </div> |
| |
| <div className="flex items-center gap-3"> |
| <div className="flex items-center gap-1 p-0.5 rounded-lg border border-slate-700/20 bg-black/5 text-[10px]"> |
| <button |
| type="button" |
| onClick={() => setReadingTheme("dark")} |
| className={`px-2 py-1 font-mono rounded transition cursor-pointer ${ |
| readingTheme === "dark" ? "bg-slate-800 text-white font-medium" : "text-slate-500 hover:text-slate-800" |
| }`} |
| > |
| Dark |
| </button> |
| <button |
| type="button" |
| onClick={() => setReadingTheme("sepia")} |
| className={`px-2 py-1 font-mono rounded transition cursor-pointer ${ |
| readingTheme === "sepia" ? "bg-amber-800/10 text-amber-800 font-bold" : "text-slate-500 hover:text-slate-800" |
| }`} |
| > |
| Sepia |
| </button> |
| <button |
| type="button" |
| onClick={() => setReadingTheme("light")} |
| className={`px-2 py-1 font-mono rounded transition cursor-pointer ${ |
| readingTheme === "light" ? "bg-slate-200 text-slate-900 font-medium" : "text-slate-505 hover:text-slate-800" |
| }`} |
| > |
| Light |
| </button> |
| </div> |
| |
| <div className={`h-6 w-px ${readingTheme === "dark" ? "bg-slate-800" : "bg-slate-200"}`} /> |
| |
| <div className="flex items-center gap-1.5"> |
| <Type className="h-3.5 w-3.5 text-slate-500" /> |
| <div className="flex items-center gap-1"> |
| {(["sm", "base", "lg", "xl"] as const).map((sz) => ( |
| <button |
| key={sz} |
| type="button" |
| onClick={() => setReadingFontSize(sz)} |
| className={`h-6 w-6 rounded text-[10px] font-mono font-bold transition cursor-pointer flex items-center justify-center uppercase ${ |
| readingFontSize === sz |
| ? "bg-blue-600 text-white shadow-sm" |
| : readingTheme === "dark" ? "text-slate-500 hover:bg-slate-800" : "text-slate-600 hover:bg-slate-202" |
| }`} |
| > |
| {sz} |
| </button> |
| ))} |
| </div> |
| </div> |
| |
| <div className={`h-6 w-px ${readingTheme === "dark" ? "bg-slate-800" : "bg-slate-200"}`} /> |
| <div className="flex items-center gap-1 bg-black/5 p-0.5 rounded-lg border border-slate-700/30 text-[10px]"> |
| {(["sans", "serif", "mono"] as const).map((ff) => ( |
| <button |
| key={ff} |
| type="button" |
| onClick={() => setReadingFontFamily(ff)} |
| className={`px-1.5 py-0.5 rounded text-[9px] font-mono transition cursor-pointer font-bold uppercase ${ |
| readingFontFamily === ff |
| ? "bg-slate-800 text-white shadow-sm font-semibold" |
| : "text-slate-500 hover:text-slate-800" |
| }`} |
| > |
| {ff} |
| </button> |
| ))} |
| </div> |
| |
| <div className={`h-6 w-px ${readingTheme === "dark" ? "bg-slate-800" : "bg-slate-200"}`} /> |
| <button |
| type="button" |
| onClick={() => { |
| const nextHeight: Record<typeof readingLineHeight, typeof readingLineHeight> = { |
| normal: "relaxed", |
| relaxed: "loose", |
| loose: "normal" |
| }; |
| setReadingLineHeight(nextHeight[readingLineHeight]); |
| }} |
| className={`px-2 py-0.5 rounded text-[10px] font-mono border transition cursor-pointer ${ |
| readingTheme === "dark" ? "border-slate-800 bg-[#15181E] text-slate-400 hover:text-white" : "border-slate-200 bg-slate-100 text-slate-700" |
| }`} |
| title="Line Spacing" |
| > |
| Spacing: <span className="capitalize">{readingLineHeight}</span> |
| </button> |
| |
| <div className={`h-6 w-px ${readingTheme === "dark" ? "bg-slate-800" : "bg-slate-200"}`} /> |
| |
| <button |
| type="button" |
| onClick={() => setIsReadingMode(false)} |
| className="px-2.5 py-1 text-xs bg-red-600 hover:bg-red-500 text-white font-medium rounded transition flex items-center gap-1 cursor-pointer font-sans" |
| > |
| <Minimize2 className="h-3.5 w-3.5" /> Close Reader |
| </button> |
| </div> |
| </div> |
| </header> |
| |
| <main className="flex-1 w-full max-w-2xl mx-auto px-6 py-12 overflow-y-auto"> |
| <div className="text-left select-text"> |
| <h1 className={`text-4xl font-sans tracking-tight font-extrabold mb-3 ${ |
| readingTheme === "dark" ? "text-white" : |
| readingTheme === "sepia" ? "text-amber-950" : "text-slate-900" |
| }`}> |
| {activeDoc.title.replace(/\.[^/.]+$/, "")} |
| </h1> |
| <div className={`mb-8 pb-3 border-b text-[10px] font-mono ${ |
| readingTheme === "dark" ? "border-slate-800 text-slate-500" : |
| readingTheme === "sepia" ? "border-amber-200 text-amber-800/60" : "border-slate-200 text-slate-400" |
| }`}> |
| Last saved {new Date(activeDoc.lastSaved).toLocaleString()} • {charCount} characters • {wordCount} words |
| </div> |
| |
| <div className={`prose ${ |
| readingTheme === "dark" ? "prose-invert text-slate-300" : |
| readingTheme === "sepia" ? "text-amber-900" : "text-slate-800" |
| } ${ |
| readingFontFamily === "sans" ? "font-sans font-normal" : |
| readingFontFamily === "serif" ? "font-serif tracking-wide" : "font-mono text-xs tracking-tight" |
| } ${ |
| readingFontSize === "sm" ? "text-sm" : |
| readingFontSize === "base" ? "text-base" : |
| readingFontSize === "lg" ? "text-lg" : "text-xl" |
| } ${ |
| readingLineHeight === "normal" ? "leading-normal" : |
| readingLineHeight === "relaxed" ? "leading-relaxed" : "leading-loose" |
| } markdown-body transition-all duration-150`}> |
| <ReactMarkdown remarkPlugins={[remarkGfm]}>{activeDoc.content}</ReactMarkdown> |
| </div> |
| </div> |
| </main> |
| |
| <footer className={`h-12 border-t text-center flex items-center justify-center text-[11px] font-mono ${ |
| readingTheme === "dark" ? "border-slate-900 bg-[#0A0C10] text-slate-600" : |
| readingTheme === "sepia" ? "border-amber-100 bg-[#F3EADF] text-amber-800/40" : "bg-white border-slate-150 text-slate-500" |
| }`}> |
| Press <kbd className="px-1.5 py-0.5 rounded bg-black/5 border border-slate-400/20 text-[10px] mx-1">ESC</kbd> to exit readers view |
| </footer> |
| </div> |
| ); |
| } |
| |