summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-08 09:03:09 -0800
committerFuwn <[email protected]>2026-02-08 09:03:09 -0800
commit06dfa122f395c64852ed33472b6fbc166293d627 (patch)
tree3e8f844c6551cdfa510d8ed263b09feb56432272 /apps
parentfeat: add toolbar position setting (top or bottom) (diff)
downloadasa.news-06dfa122f395c64852ed33472b6fbc166293d627.tar.xz
asa.news-06dfa122f395c64852ed33472b6fbc166293d627.zip
fix: mobile scroll preservation, bottom toolbar for detail panel
Diffstat (limited to 'apps')
-rw-r--r--apps/web/app/reader/_components/entry-detail-panel.tsx122
-rw-r--r--apps/web/app/reader/_components/reader-shell.tsx28
2 files changed, 84 insertions, 66 deletions
diff --git a/apps/web/app/reader/_components/entry-detail-panel.tsx b/apps/web/app/reader/_components/entry-detail-panel.tsx
index b772ea1..fa74d6f 100644
--- a/apps/web/app/reader/_components/entry-detail-panel.tsx
+++ b/apps/web/app/reader/_components/entry-detail-panel.tsx
@@ -27,6 +27,7 @@ import {
import { HighlightSelectionToolbar } from "./highlight-selection-toolbar"
import { HighlightPopover } from "./highlight-popover"
import { formatDistanceToNow, format } from "date-fns"
+import { useIsMobile } from "@/lib/hooks/use-is-mobile"
import { notify } from "@/lib/notify"
import type { Highlight } from "@/lib/types/highlight"
@@ -68,6 +69,10 @@ export function EntryDetailPanel({
const showReadingTime = useUserInterfaceStore(
(state) => state.showReadingTime
)
+ const toolbarPosition = useUserInterfaceStore(
+ (state) => state.toolbarPosition
+ )
+ const isMobile = useIsMobile()
const proseContainerReference = useRef<HTMLDivElement>(null)
const [selectionToolbarState, setSelectionToolbarState] = useState<{
selectionRect: DOMRect
@@ -436,72 +441,78 @@ export function EntryDetailPanel({
const isRead = currentEntry?.isRead ?? false
const isSaved = currentEntry?.isSaved ?? false
- return (
- <div data-detail-panel className="flex h-full flex-col">
- <div className="flex items-center gap-2 overflow-x-auto border-b border-border px-4 py-2">
- <button
- type="button"
- onClick={() =>
- toggleReadState.mutate({
- entryIdentifier,
- isRead: !isRead,
- })
- }
+ const actionBarAtBottom = isMobile && toolbarPosition === "bottom"
+
+ const actionBar = (
+ <div className={`flex items-center gap-2 overflow-x-auto border-border px-4 py-2 ${actionBarAtBottom ? "border-t" : "border-b"}`}>
+ <button
+ type="button"
+ onClick={() =>
+ toggleReadState.mutate({
+ entryIdentifier,
+ isRead: !isRead,
+ })
+ }
+ className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
+ >
+ {isRead ? "mark unread" : "mark read"}
+ </button>
+ <button
+ type="button"
+ onClick={() =>
+ toggleSavedState.mutate({
+ entryIdentifier,
+ isSaved: !isSaved,
+ })
+ }
+ className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
+ >
+ {isSaved ? "unsave" : "save"}
+ </button>
+ {entryDetail.url && (
+ <a
+ href={entryDetail.url}
+ target="_blank"
+ rel="noopener noreferrer"
className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
>
- {isRead ? "mark unread" : "mark read"}
- </button>
+ open original
+ </a>
+ )}
+ {shareData?.isShared ? (
<button
type="button"
- onClick={() =>
- toggleSavedState.mutate({
- entryIdentifier,
- isSaved: !isSaved,
- })
- }
+ onClick={() => unshareMutation.mutate(shareData.shareToken!)}
className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
>
- {isSaved ? "unsave" : "save"}
+ unshare
</button>
- {entryDetail.url && (
- <a
- href={entryDetail.url}
- target="_blank"
- rel="noopener noreferrer"
- className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
- >
- open original
- </a>
- )}
- {shareData?.isShared ? (
- <button
- type="button"
- onClick={() => unshareMutation.mutate(shareData.shareToken!)}
- className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
- >
- unshare
- </button>
- ) : (
- <button
- type="button"
- onClick={() => {
- setShareNoteText("")
- setIsShareNoteDialogOpen(true)
- }}
- className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
- >
- share
- </button>
- )}
- <div className="flex-1" />
+ ) : (
<button
type="button"
- onClick={() => setSelectedEntryIdentifier(null)}
- className="hidden px-2 py-1 text-text-dim transition-colors hover:text-text-secondary md:block"
+ onClick={() => {
+ setShareNoteText("")
+ setIsShareNoteDialogOpen(true)
+ }}
+ className="shrink-0 whitespace-nowrap border border-border px-2 py-1 text-text-secondary transition-colors hover:bg-background-tertiary hover:text-text-primary"
>
- close
+ share
</button>
- </div>
+ )}
+ <div className="flex-1" />
+ <button
+ type="button"
+ onClick={() => setSelectedEntryIdentifier(null)}
+ className="hidden px-2 py-1 text-text-dim transition-colors hover:text-text-secondary md:block"
+ >
+ close
+ </button>
+ </div>
+ )
+
+ return (
+ <div data-detail-panel className="flex h-full flex-col">
+ {!actionBarAtBottom && actionBar}
<article data-detail-article className="min-h-0 flex-1 overflow-y-scroll px-6 py-4">
<h2 className="mb-1 text-base text-text-primary">
{entryDetail.title}
@@ -637,6 +648,7 @@ export function EntryDetailPanel({
</div>
</div>
)}
+ {actionBarAtBottom && actionBar}
</div>
)
}
diff --git a/apps/web/app/reader/_components/reader-shell.tsx b/apps/web/app/reader/_components/reader-shell.tsx
index eb63f63..8a5a044 100644
--- a/apps/web/app/reader/_components/reader-shell.tsx
+++ b/apps/web/app/reader/_components/reader-shell.tsx
@@ -287,18 +287,14 @@ export function ReaderShell({
fontSize === "small" ? "text-sm" : fontSize === "large" ? "text-lg" : "text-base"
)}>
{toolbarPosition === "top" && toolbar}
+ <div className="relative min-h-0 flex-1">
<ErrorBoundary>
{isMobile ? (
- selectedEntryIdentifier ? (
- <div className="flex-1 overflow-hidden">
- <ErrorBoundary>
- <EntryDetailPanel
- entryIdentifier={selectedEntryIdentifier}
- />
- </ErrorBoundary>
- </div>
- ) : (
- <div className="flex-1 overflow-hidden">
+ <>
+ <div className={classNames(
+ "absolute inset-0 overflow-x-hidden overflow-y-hidden",
+ selectedEntryIdentifier ? "invisible" : "visible"
+ )}>
<ErrorBoundary>
<EntryList
feedFilter={feedFilter}
@@ -308,7 +304,16 @@ export function ReaderShell({
/>
</ErrorBoundary>
</div>
- )
+ {selectedEntryIdentifier && (
+ <div className="absolute inset-0 overflow-hidden bg-background-primary">
+ <ErrorBoundary>
+ <EntryDetailPanel
+ entryIdentifier={selectedEntryIdentifier}
+ />
+ </ErrorBoundary>
+ </div>
+ )}
+ </>
) : (
<Group
orientation="horizontal"
@@ -352,6 +357,7 @@ export function ReaderShell({
</Group>
)}
</ErrorBoundary>
+ </div>
{toolbarPosition === "bottom" && toolbar}
</div>
)