From 05f5334929845e37cd5463446acbcb88583bf282 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Thu, 9 Oct 2025 21:08:58 -0400 Subject: [PATCH] scroll pages and comments into view properly --- .../[rkey]/Interactions/InteractionDrawer.tsx | 6 +- .../[rkey]/Interactions/Interactions.tsx | 21 ++----- .../[did]/[publication]/[rkey]/PostPages.tsx | 33 ++++++---- app/lish/[did]/[publication]/[rkey]/page.tsx | 1 + src/utils/scrollIntoView.ts | 62 +++++++++++++++++++ 5 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 src/utils/scrollIntoView.ts diff --git a/app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx b/app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx index b2c7d0c1..f83f5dd1 100644 --- a/app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx +++ b/app/lish/[did]/[publication]/[rkey]/Interactions/InteractionDrawer.tsx @@ -13,7 +13,7 @@ export const InteractionDrawer = (props: { comments: Comment[]; did: string; }) => { - let drawer = useDrawerOpen(); + let drawer = useDrawerOpen(props.document_uri); if (!drawer) return null; return ( <> @@ -36,10 +36,10 @@ export const InteractionDrawer = (props: { ); }; -export const useDrawerOpen = () => { +export const useDrawerOpen = (uri: string) => { let params = useSearchParams(); let interactionDrawerSearchParam = params.get("interactionDrawer"); - let { drawerOpen: open, drawer } = useInteractionState(); + let { drawerOpen: open, drawer } = useInteractionState(uri); if (open === false || (open === undefined && !interactionDrawerSearchParam)) return null; return drawer || interactionDrawerSearchParam; diff --git a/app/lish/[did]/[publication]/[rkey]/Interactions/Interactions.tsx b/app/lish/[did]/[publication]/[rkey]/Interactions/Interactions.tsx index 76a76e86..171037ed 100644 --- a/app/lish/[did]/[publication]/[rkey]/Interactions/Interactions.tsx +++ b/app/lish/[did]/[publication]/[rkey]/Interactions/Interactions.tsx @@ -8,6 +8,7 @@ import type { Comment } from "./Comments"; import { QuotePosition } from "../quotePosition"; import { useContext } from "react"; import { PostPageContext } from "../PostPageContext"; +import { scrollIntoView } from "src/utils/scrollIntoView"; type InteractionState = { drawerOpen: undefined | boolean; @@ -27,9 +28,9 @@ export let useInteractionStateStore = create<{ [document_uri: string]: InteractionState; }>(() => ({})); -export function useInteractionState(document_uri?: string) { +export function useInteractionState(document_uri: string) { return useInteractionStateStore((state) => { - if (!document_uri || !state[document_uri]) { + if (!state[document_uri]) { return defaultInteractionState; } return state[document_uri]; @@ -87,21 +88,7 @@ export function openInteractionDrawer( flushSync(() => { setInteractionState(document_uri, { drawerOpen: true, drawer }); }); - let el = document.getElementById("interaction-drawer"); - let isOffscreen = false; - if (el) { - const rect = el.getBoundingClientRect(); - const windowWidth = - window.innerWidth || document.documentElement.clientWidth; - isOffscreen = rect.right > windowWidth - 64; - } - - if (el && isOffscreen) - el.scrollIntoView({ - behavior: "smooth", - block: "center", - inline: "center", - }); + scrollIntoView("interaction-drawer"); } export const Interactions = (props: { diff --git a/app/lish/[did]/[publication]/[rkey]/PostPages.tsx b/app/lish/[did]/[publication]/[rkey]/PostPages.tsx index 380955b8..54e14c2f 100644 --- a/app/lish/[did]/[publication]/[rkey]/PostPages.tsx +++ b/app/lish/[did]/[publication]/[rkey]/PostPages.tsx @@ -9,7 +9,7 @@ import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/act import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; import { SubscribeWithBluesky } from "app/lish/Subscribe"; import { EditTiny } from "components/Icons/EditTiny"; -import { Interactions, useInteractionState } from "./Interactions/Interactions"; +import { Interactions } from "./Interactions/Interactions"; import { PostContent } from "./PostContent"; import { PostHeader } from "./PostHeader/PostHeader"; import { useIdentityData } from "components/IdentityProvider"; @@ -25,21 +25,28 @@ import { PageOptionButton } from "components/Pages/PageOptions"; import { CloseTiny } from "components/Icons/CloseTiny"; import { PageWrapper } from "components/Pages/Page"; import { Fragment } from "react"; +import { flushSync } from "react-dom"; +import { scrollIntoView } from "src/utils/scrollIntoView"; export const usePostPageUIState = create(() => ({ pages: [] as string[], })); -export const openPage = (parent: string | undefined, page: string) => - usePostPageUIState.setState((state) => { - let parentPosition = state.pages.findIndex((s) => s == parent); - return { - pages: - parentPosition === -1 - ? [page] - : [...state.pages.slice(0, parentPosition + 1), page], - }; +export const openPage = (parent: string | undefined, page: string) => { + flushSync(() => { + usePostPageUIState.setState((state) => { + let parentPosition = state.pages.findIndex((s) => s == parent); + return { + pages: + parentPosition === -1 + ? [page] + : [...state.pages.slice(0, parentPosition + 1), page], + }; + }); }); + scrollIntoView(`post-page-${page}`); +}; + export const closePage = (page: string) => usePostPageUIState.setState((state) => { let parentPosition = state.pages.findIndex((s) => s == page); @@ -58,7 +65,9 @@ export function PostPages({ pubRecord, prerenderedCodeBlocks, bskyPostData, + document_uri, }: { + document_uri: string; document: PostPageData; blocks: PubLeafletPagesLinearDocument.Block[]; name: string; @@ -70,7 +79,7 @@ export function PostPages({ preferences: { showComments?: boolean }; }) { let { identity } = useIdentityData(); - let drawerOpen = useDrawerOpen(); + let drawerOpen = useDrawerOpen(document_uri); let pages = usePostPageUIState((s) => s.pages); if (!document || !document.documents_in_publications[0].publications) return null; @@ -154,7 +163,7 @@ export function PostPages({ { + const entry = entries[0]; + + // If element is not sufficiently visible, scroll to it + if (!entry.isIntersecting || entry.intersectionRatio < threshold) { + const elementRect = element.getBoundingClientRect(); + const containerRect = scrollContainer.getBoundingClientRect(); + + // Calculate the target scroll position + // We want to center the element horizontally in the container + const targetScrollLeft = + scrollContainer.scrollLeft + + elementRect.left - + containerRect.left - + (containerRect.width - elementRect.width) / 2; + + scrollContainer.scrollTo({ + left: targetScrollLeft, + behavior: "smooth", + }); + } + + // Disconnect after checking once + observer.disconnect(); + }, + { + root: scrollContainer, + threshold: threshold, + }, + ); + + observer.observe(element); +} -- 2.54.0