diff options
| author | Fuwn <[email protected]> | 2026-02-08 08:50:10 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-08 08:50:10 -0800 |
| commit | 1157a51cd2e527d30d61583d0888494716d1dc88 (patch) | |
| tree | 42fdfac5f89c89c2616e16e1c6643ae8a6eb2ac2 /apps/web/app/reader | |
| parent | feat: enforce tier-based history retention (14d free, 90d pro/dev) (diff) | |
| download | asa.news-1157a51cd2e527d30d61583d0888494716d1dc88.tar.xz asa.news-1157a51cd2e527d30d61583d0888494716d1dc88.zip | |
feat: add toolbar position setting (top or bottom)
Diffstat (limited to 'apps/web/app/reader')
| -rw-r--r-- | apps/web/app/reader/_components/reader-shell.tsx | 175 | ||||
| -rw-r--r-- | apps/web/app/reader/settings/_components/appearance-settings.tsx | 22 |
2 files changed, 115 insertions, 82 deletions
diff --git a/apps/web/app/reader/_components/reader-shell.tsx b/apps/web/app/reader/_components/reader-shell.tsx index 0cad5cd..eb63f63 100644 --- a/apps/web/app/reader/_components/reader-shell.tsx +++ b/apps/web/app/reader/_components/reader-shell.tsx @@ -55,6 +55,9 @@ export function ReaderShell({ const toggleShortcutsDialog = useUserInterfaceStore( (state) => state.toggleShortcutsDialog ) + const toolbarPosition = useUserInterfaceStore( + (state) => state.toolbarPosition + ) const detailLayout = useDefaultLayout({ id: "asa-detail-layout", @@ -175,108 +178,115 @@ export function ReaderShell({ ) const allAreRead = totalUnreadCount === 0 - return ( - <div className={classNames( - "flex h-full flex-col", - fontSize === "small" ? "text-sm" : fontSize === "large" ? "text-lg" : "text-base" + const toolbar = ( + <header className={classNames( + "flex items-center justify-between border-border px-4 py-3", + toolbarPosition === "top" ? "border-b" : "border-t" )}> - <header className="flex items-center justify-between border-b border-border px-4 py-3"> - {isMobile && selectedEntryIdentifier ? ( + {isMobile && selectedEntryIdentifier ? ( + <button + type="button" + onClick={() => setSelectedEntryIdentifier(null)} + className="text-text-secondary transition-colors hover:text-text-primary" + > + ← back + </button> + ) : isRenamingTitle ? ( + <div className="flex items-center gap-2"> + <input + type="text" + value={renameValue} + onChange={(event) => setRenameValue(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") handleSaveRename() + if (event.key === "Escape") setIsRenamingTitle(false) + }} + className="min-w-0 border border-border bg-background-primary px-2 py-1 text-text-primary outline-none focus:border-text-dim" + autoFocus + /> + <button + type="button" + onClick={handleSaveRename} + className="text-text-secondary transition-colors hover:text-text-primary" + > + save + </button> <button type="button" - onClick={() => setSelectedEntryIdentifier(null)} + onClick={() => setIsRenamingTitle(false)} className="text-text-secondary transition-colors hover:text-text-primary" > - ← back + cancel </button> - ) : isRenamingTitle ? ( - <div className="flex items-center gap-2"> - <input - type="text" - value={renameValue} - onChange={(event) => setRenameValue(event.target.value)} - onKeyDown={(event) => { - if (event.key === "Enter") handleSaveRename() - if (event.key === "Escape") setIsRenamingTitle(false) - }} - className="min-w-0 border border-border bg-background-primary px-2 py-1 text-text-primary outline-none focus:border-text-dim" - autoFocus - /> + </div> + ) : ( + <div className="flex items-center gap-2"> + <h1 className="text-text-primary">{pageTitle}</h1> + {isRenameable && ( <button type="button" - onClick={handleSaveRename} - className="text-text-secondary transition-colors hover:text-text-primary" + onClick={handleStartRename} + className="text-text-dim transition-colors hover:text-text-secondary" > - save + rename </button> + )} + </div> + )} + <div className="flex items-center gap-3"> + {!(isMobile && selectedEntryIdentifier) && ( + <> <button type="button" - onClick={() => setIsRenamingTitle(false)} - className="text-text-secondary transition-colors hover:text-text-primary" + onClick={() => setSearchOpen(true)} + className="text-text-dim transition-colors hover:text-text-secondary" > - cancel + search </button> - </div> - ) : ( - <div className="flex items-center gap-2"> - <h1 className="text-text-primary">{pageTitle}</h1> - {isRenameable && ( + {feedFilter === "all" && ( <button type="button" - onClick={handleStartRename} - className="text-text-dim transition-colors hover:text-text-secondary" + onClick={() => + markAllAsRead.mutate({ readState: !allAreRead }) + } + disabled={markAllAsRead.isPending} + className="text-text-dim transition-colors hover:text-text-secondary disabled:opacity-50" > - rename + {allAreRead ? "mark all unread" : "mark all read"} </button> )} - </div> + <select + value={entryListViewMode} + onChange={(event) => + setEntryListViewMode( + event.target.value as "compact" | "comfortable" | "expanded" + ) + } + className="hidden border border-border bg-background-primary px-2 py-1 text-text-secondary outline-none sm:block" + > + <option value="compact">compact</option> + <option value="comfortable">comfortable</option> + <option value="expanded">expanded</option> + </select> + <button + type="button" + onClick={() => toggleShortcutsDialog()} + className="hidden text-text-dim transition-colors hover:text-text-secondary sm:block" + > + shortcuts + </button> + </> )} - <div className="flex items-center gap-3"> - {!(isMobile && selectedEntryIdentifier) && ( - <> - <button - type="button" - onClick={() => setSearchOpen(true)} - className="text-text-dim transition-colors hover:text-text-secondary" - > - search - </button> - {feedFilter === "all" && ( - <button - type="button" - onClick={() => - markAllAsRead.mutate({ readState: !allAreRead }) - } - disabled={markAllAsRead.isPending} - className="text-text-dim transition-colors hover:text-text-secondary disabled:opacity-50" - > - {allAreRead ? "mark all unread" : "mark all read"} - </button> - )} - <select - value={entryListViewMode} - onChange={(event) => - setEntryListViewMode( - event.target.value as "compact" | "comfortable" | "expanded" - ) - } - className="hidden border border-border bg-background-primary px-2 py-1 text-text-secondary outline-none sm:block" - > - <option value="compact">compact</option> - <option value="comfortable">comfortable</option> - <option value="expanded">expanded</option> - </select> - <button - type="button" - onClick={() => toggleShortcutsDialog()} - className="hidden text-text-dim transition-colors hover:text-text-secondary sm:block" - > - shortcuts - </button> - </> - )} - </div> - </header> + </div> + </header> + ) + + return ( + <div className={classNames( + "flex h-full flex-col", + fontSize === "small" ? "text-sm" : fontSize === "large" ? "text-lg" : "text-base" + )}> + {toolbarPosition === "top" && toolbar} <ErrorBoundary> {isMobile ? ( selectedEntryIdentifier ? ( @@ -342,6 +352,7 @@ export function ReaderShell({ </Group> )} </ErrorBoundary> + {toolbarPosition === "bottom" && toolbar} </div> ) } diff --git a/apps/web/app/reader/settings/_components/appearance-settings.tsx b/apps/web/app/reader/settings/_components/appearance-settings.tsx index 458d2b6..0f0d793 100644 --- a/apps/web/app/reader/settings/_components/appearance-settings.tsx +++ b/apps/web/app/reader/settings/_components/appearance-settings.tsx @@ -61,6 +61,12 @@ export function AppearanceSettings() { const setShowFoldersAboveFeeds = useUserInterfaceStore( (state) => state.setShowFoldersAboveFeeds ) + const toolbarPosition = useUserInterfaceStore( + (state) => state.toolbarPosition + ) + const setToolbarPosition = useUserInterfaceStore( + (state) => state.setToolbarPosition + ) return ( <div className="px-4 py-3"> <div className="mb-6"> @@ -79,6 +85,22 @@ export function AppearanceSettings() { </select> </div> <div className="mb-6"> + <h3 className="mb-2 text-text-primary">toolbar position</h3> + <p className="mb-3 text-text-dim"> + place the toolbar at the top or bottom of the reader + </p> + <select + value={toolbarPosition} + onChange={(event) => + setToolbarPosition(event.target.value as "top" | "bottom") + } + className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" + > + <option value="top">top</option> + <option value="bottom">bottom</option> + </select> + </div> + <div className="mb-6"> <h3 className="mb-2 text-text-primary">display density</h3> <p className="mb-3 text-text-dim"> controls the overall text size and spacing |