feat: scroll to post bubble from recommended card + back-to-top button
Some checks failed
Deploy to Frontend Servers / deploy (push) Failing after 14s
Some checks failed
Deploy to Frontend Servers / deploy (push) Failing after 14s
Recommended cards already routed to /browse#post-<id>, but the stream had no logic to scroll to the target bubble — and the post might not be paged in yet. MessageStream now resolves the #post-<id> hash, auto-loads more pages until the bubble renders, scrolls to it, and gives it a brief gold highlight. Bubbles get scroll-mt so they clear the sticky header. Also adds a global floating back-to-top button (BackToTop) mounted in PublicLayout, shown after scrolling past 400px. Bundles related staging UI work already present in the working tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
import { ChevronDown, Menu, Search as SearchIcon, X } from "lucide-react";
|
||||
import { AnimatePresence, m } from "framer-motion";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Link,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useOutlet,
|
||||
} from "react-router-dom";
|
||||
import { pageTransition } from "../motion";
|
||||
import { ArkLogoMark } from "../components/ArkLogoMark";
|
||||
import { BackToTop } from "../components/BackToTop";
|
||||
import { DocumentMeta } from "../components/DocumentMeta";
|
||||
import { SearchPanel } from "../components/SearchPanel";
|
||||
import { useI18n, type Lang } from "../i18n";
|
||||
@@ -53,7 +61,9 @@ function navIsActive(
|
||||
|
||||
function navClassName(active: boolean) {
|
||||
return [
|
||||
"shrink-0 rounded-sm px-2 py-2 text-[13px] font-medium leading-none whitespace-nowrap no-underline outline-none transition-colors",
|
||||
"relative shrink-0 rounded-sm px-2 py-2 text-[13px] font-medium leading-none whitespace-nowrap no-underline outline-none transition-colors",
|
||||
// Hover-only gold underline that slides in (resting/active look unchanged).
|
||||
"after:pointer-events-none after:absolute after:inset-x-2 after:bottom-1 after:h-[2px] after:origin-left after:scale-x-0 after:rounded-full after:bg-ark-gold after:transition-transform after:duration-300 hover:after:scale-x-100 motion-reduce:after:transition-none",
|
||||
"focus-visible:ring-2 focus-visible:ring-ark-gold/90 focus-visible:ring-offset-2 focus-visible:ring-offset-ark-bg",
|
||||
active
|
||||
? "text-ark-gold visited:text-ark-gold"
|
||||
@@ -274,6 +284,7 @@ function MobileLanguageButton({
|
||||
export function PublicLayout() {
|
||||
const { t, lang, setLang } = useI18n();
|
||||
const { pathname, search, hash } = useLocation();
|
||||
const outlet = useOutlet();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
||||
const [q, setQ] = useState("");
|
||||
@@ -606,9 +617,17 @@ export function PublicLayout() {
|
||||
: "flex-1 px-4 pb-6 pt-6 min-[440px]:px-5 sm:px-6 md:px-9 md:pb-10 md:pt-10 xl:px-0"
|
||||
}`}
|
||||
>
|
||||
<div key={`${pathname}${search}`} className="ark-page-fade-in">
|
||||
<Outlet />
|
||||
</div>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<m.div
|
||||
key={`${pathname}${search}`}
|
||||
variants={pageTransition}
|
||||
initial="initial"
|
||||
animate="enter"
|
||||
exit="exit"
|
||||
>
|
||||
{outlet}
|
||||
</m.div>
|
||||
</AnimatePresence>
|
||||
</main>
|
||||
|
||||
<footer className="mt-auto bg-transparent md:border-t md:border-ark-line md:bg-ark-nav/90">
|
||||
@@ -654,6 +673,8 @@ export function PublicLayout() {
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<BackToTop />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user