feat(ui): connect shell to store with live preview dock

This commit is contained in:
pj committed 2026-06-20 12:28:44 -04:00
1 parent 3cf07d4ef6
commit 29c412849e
5 files changed
+197 -71

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
import { useMemo } from "react";
import { generateHTML } from "@tiptap/core";
import { useBook } from "../store/useBook";
import { editorExtensions } from "../editor/extensions";
const TRIM_LABEL: Record<string, string> = {
"6x9": "6 × 9 in",
"5.5x8.5": "5.5 × 8.5 in",
"5x8": "5 × 8 in",
a5: "A5",
};
export function Dock() {
const chapters = useBook((s) => s.book.chapters);
const trim = useBook((s) => s.book.settings.trim);
const activeChapterId = useBook((s) => s.activeChapterId);
const idx = chapters.findIndex((c) => c.id === activeChapterId);
const chapter = chapters[idx];
const html = useMemo(() => generateHTML(chapter.content, editorExtensions), [chapter.content]);
return (
<section className="dock">
<div className="dock-head">
<span className="label">Preview</span>
<span className="meta">
{TRIM_LABEL[trim]} · {idx + 1} of {chapters.length}
</span>
</div>
<div className="page">
<div className="p-opener">
<div className="p-num">Chapter {idx + 1}</div>
<div className="p-title">{chapter.title || "Untitled"}</div>
</div>
<div className="page-body" dangerouslySetInnerHTML={{ __html: html }} />
<div className="folio">{idx * 8 + 7}</div>
</div>
</section>
);
}
+21
View File
@@ -0,0 +1,21 @@
interface IconProps {
d: string;
size?: number;
}
export function Icon({ d, size = 16 }: IconProps) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d={d} />
</svg>
);
}
+36
View File
@@ -0,0 +1,36 @@
import { useBook } from "../store/useBook";
import { Icon } from "./Icon";
export function Sidebar() {
const chapters = useBook((s) => s.book.chapters);
const activeChapterId = useBook((s) => s.activeChapterId);
const setActiveChapter = useBook((s) => s.setActiveChapter);
const addChapter = useBook((s) => s.addChapter);
return (
<aside className="sidebar">
<div className="brand">
<span className="mark">margin</span>
<span className="dot" />
</div>
<div className="nav-label">Chapters</div>
<ul className="chapters">
{chapters.map((chapter, i) => (
<li
key={chapter.id}
className="chapter"
data-active={chapter.id === activeChapterId}
onClick={() => setActiveChapter(chapter.id)}
>
<span className="num">{i + 1}</span>
<span className="title">{chapter.title || "Untitled"}</span>
</li>
))}
</ul>
<button className="add-chapter" onClick={addChapter}>
<Icon d="M12 5v14M5 12h14" />
New chapter
</button>
</aside>
);
}