From da94acc0ec6d53ade5ac2d8b4b1be9fcfdec2c2d Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 20 Jun 2026 13:02:30 -0400 Subject: [PATCH] feat(setup): book metadata and settings panel --- src/components/Settings.tsx | 87 +++++++++++++++++++++++++++++++++++++ src/store/useBook.ts | 8 +++- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/components/Settings.tsx diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx new file mode 100644 index 0000000..e0a7057 --- /dev/null +++ b/src/components/Settings.tsx @@ -0,0 +1,87 @@ +import type { ReactNode } from "react"; +import { useBook } from "../store/useBook"; +import type { TrimSize } from "../model/book"; +import { Icon } from "./Icon"; + +const TRIMS: { value: TrimSize; label: string }[] = [ + { value: "6x9", label: "6 × 9 in — trade paperback" }, + { value: "5.5x8.5", label: "5.5 × 8.5 in — digest" }, + { value: "5x8", label: "5 × 8 in — pocket" }, + { value: "a5", label: "A5 — 148 × 210 mm" }, +]; + +const LANGUAGES = [ + { value: "en", label: "English" }, + { value: "es", label: "Spanish" }, + { value: "fr", label: "French" }, + { value: "de", label: "German" }, + { value: "it", label: "Italian" }, + { value: "pt", label: "Portuguese" }, +]; + +function Field({ label, children }: { label: string; children: ReactNode }) { + return ( + + ); +} + +export function Settings({ onClose }: { onClose: () => void }) { + const metadata = useBook((s) => s.book.metadata); + const settings = useBook((s) => s.book.settings); + const setMetadata = useBook((s) => s.setMetadata); + const setSettings = useBook((s) => s.setSettings); + + return ( +
+
e.stopPropagation()}> +
+

Book setup

+ +
+
+ + setMetadata({ title: e.target.value })} /> + + + setMetadata({ subtitle: e.target.value })} /> + + + setMetadata({ author: e.target.value })} /> + + + setMetadata({ isbn: e.target.value })} /> + +
+ + + + + + +
+ +
+
+
+ ); +} diff --git a/src/store/useBook.ts b/src/store/useBook.ts index a8fbf2b..01a004e 100644 --- a/src/store/useBook.ts +++ b/src/store/useBook.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import type { JSONContent } from "@tiptap/core"; -import { type Book, createChapter, starterBook } from "../model/book"; +import { type Book, type BookMetadata, type BookSettings, createChapter, starterBook } from "../model/book"; interface BookState { book: Book; @@ -11,6 +11,8 @@ interface BookState { setChapterContent: (id: string, content: JSONContent) => void; setChapterTitle: (id: string, title: string) => void; addChapter: () => void; + setMetadata: (patch: Partial) => void; + setSettings: (patch: Partial) => void; replaceBook: (book: Book, path: string) => void; markSaved: (path: string) => void; } @@ -48,6 +50,10 @@ export const useBook = create((set) => { book: { ...s.book, chapters: [...s.book.chapters, chapter] }, }; }), + setMetadata: (patch) => + set((s) => ({ dirty: true, book: { ...s.book, metadata: { ...s.book.metadata, ...patch } } })), + setSettings: (patch) => + set((s) => ({ dirty: true, book: { ...s.book, settings: { ...s.book.settings, ...patch } } })), replaceBook: (book, path) => set({ book, activeChapterId: book.chapters[0]?.id ?? "", path, dirty: false }), markSaved: (path) => set({ path, dirty: false }),