From 0beac0c2405ce979fc795cc4f491e686565a843b Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 20 Jun 2026 13:02:30 -0400 Subject: [PATCH] feat(ui): titlebar export menu and book-setup access --- src/App.tsx | 34 +++++++++- src/styles/app.css | 150 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 74437b1..c56e8b7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,14 +3,19 @@ import type { Editor as TiptapEditor } from "@tiptap/react"; import { Sidebar } from "./components/Sidebar"; import { Dock } from "./components/Dock"; import { Icon } from "./components/Icon"; +import { Settings } from "./components/Settings"; import { Editor } from "./editor/Editor"; import { FloatingToolbar } from "./editor/FloatingToolbar"; import { useBook } from "./store/useBook"; +import type { Book } from "./model/book"; import { chooseSavePath, openBook, writeBook } from "./project"; +import { exportEpub, exportPdf } from "./export/exporters"; function App() { const [dock, setDock] = useState(true); const [editor, setEditor] = useState(null); + const [settingsOpen, setSettingsOpen] = useState(false); + const [exportOpen, setExportOpen] = useState(false); const title = useBook((s) => s.book.metadata.title); const author = useBook((s) => s.book.metadata.author); const chapters = useBook((s) => s.book.chapters); @@ -53,14 +58,23 @@ function App() { return () => window.removeEventListener("keydown", onKey); }, [handleSave, handleOpen]); + const runExport = async (fn: (book: Book) => Promise) => { + setExportOpen(false); + try { + await fn(useBook.getState().book); + } catch (e) { + console.error(e); + } + }; + return (
- +
+
+ + {exportOpen && ( + <> +
setExportOpen(false)} /> +
+ + +
+ + )} +
@@ -99,6 +127,8 @@ function App() { {dock && }
+ + {settingsOpen && setSettingsOpen(false)} />}
); } diff --git a/src/styles/app.css b/src/styles/app.css index 9b82d16..cd88760 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -621,3 +621,153 @@ button { text-align: center; padding: 44px 0; } + +.overlay { + position: fixed; + inset: 0; + z-index: 20; + background: rgba(35, 32, 27, 0.28); + backdrop-filter: blur(2px); + display: grid; + place-items: center; + padding: 40px; +} + +.panel { + width: 480px; + max-width: 100%; + max-height: 100%; + display: flex; + flex-direction: column; + background: var(--paper); + border: 1px solid var(--line); + border-radius: var(--r-lg); + box-shadow: var(--shadow-pop); + overflow: hidden; +} + +.panel-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 14px 16px 22px; + border-bottom: 1px solid var(--line); +} + +.panel-head h2 { + margin: 0; + font-family: var(--font-book); + font-weight: 500; + font-size: 18px; +} + +.panel-body { + display: flex; + flex-direction: column; + gap: 15px; + padding: 20px 22px 24px; + overflow-y: auto; +} + +.field { + display: flex; + flex-direction: column; + gap: 6px; +} + +.field-label { + font-family: var(--font-ui); + font-size: var(--t-1); + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--ink-faint); +} + +.field input, +.field select { + font-family: var(--font-ui); + font-size: var(--t-4); + color: var(--ink); + background: var(--raised); + border: 1px solid var(--line-strong); + border-radius: var(--r-sm); + padding: 9px 11px; +} + +.field input:focus, +.field select:focus { + outline: none; + border-color: var(--accent); +} + +.field-row { + display: flex; + gap: 14px; +} + +.field-row .field { + flex: 1; +} + +.check { + display: flex; + align-items: center; + gap: 9px; + font-family: var(--font-ui); + font-size: var(--t-3); + color: var(--ink-soft); +} + +.check input { + width: 15px; + height: 15px; + accent-color: var(--accent); +} + +.doc-title { + cursor: pointer; +} + +.doc-title:hover { + color: var(--ink); +} + +.menu-wrap { + position: relative; +} + +.menu-backdrop { + position: fixed; + inset: 0; + z-index: 25; +} + +.menu { + position: absolute; + top: 36px; + right: 0; + z-index: 30; + min-width: 156px; + display: flex; + flex-direction: column; + padding: 5px; + background: var(--paper); + border: 1px solid var(--line); + border-radius: var(--r-md); + box-shadow: var(--shadow-pop); +} + +.menu button { + text-align: left; + padding: 8px 11px; + border-radius: var(--r-sm); + font-family: var(--font-ui); + font-size: var(--t-3); + color: var(--ink-soft); +} + +.menu button:hover { + background: var(--accent-wash); + color: var(--ink); +}