Files

98 lines
3.0 KiB
TypeScript

"use client";
/**
* PageHeader — per-page sub-header carrying the page title / breadcrumb.
*
* The global SiteHeader (mounted in app/layout.tsx) owns the wordmark,
* primary nav, search, reading settings, and user menu. PageHeader is now
* purely typographic — it renders whatever `children` the page passes and
* applies a subtle bordered band so the page's title area has visual weight
* separate from the body content.
*
* Behaviour preserved from the previous (busier) implementation:
* • If sessionStorage has `spsays_from_search`, the first <Link> child is
* hidden and replaced with a "← Back to search results" button. This
* keeps the cross-page-navigation polish that the search page relies on.
*
* Pages that don't want a sub-header (e.g. the redesigned homepage which
* paints its own editorial hero) can simply not render PageHeader.
*/
import { useState } from "react";
import { useRouter, usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
const SEARCH_RETURN_KEY = "spsays_from_search";
export function PageHeader({
children,
className,
bare = false,
sticky = false,
}: {
children: React.ReactNode;
/** Drop the bordered chrome — useful for pages that want flush title text. */
bare?: boolean;
/**
* Stick to the top below the global SiteHeader. Used by reading pages
* so the chapter title stays visible as the user scrolls long content.
* The offset is `top-12 sm:top-14` — SiteHeader is `h-12 sm:h-14`, so
* these values land the PageHeader flush with its bottom edge with no
* visible gap.
*/
sticky?: boolean;
className?: string;
}) {
const router = useRouter();
const pathname = usePathname();
// Read (and consume) the search-return flag once on first render.
const [fromSearch, setFromSearch] = useState(() => {
if (typeof window === "undefined") return false;
try {
if (
sessionStorage.getItem(SEARCH_RETURN_KEY) === "1" &&
!pathname.startsWith("/search")
) {
sessionStorage.removeItem(SEARCH_RETURN_KEY);
return true;
}
} catch {
// sessionStorage unavailable
}
return false;
});
return (
<header
className={cn(
"px-4 sm:px-6",
bare ? "py-6" : "border-b border-border bg-card/85 backdrop-blur py-5",
sticky && "sticky top-12 sm:top-14 z-20",
className,
)}
>
<div className="max-w-5xl mx-auto">
{fromSearch ? (
<>
<button
type="button"
onClick={() => {
setFromSearch(false);
router.back();
}}
className="block text-xs text-muted-foreground hover:text-foreground transition-colors mb-2"
>
&larr; Back to search results
</button>
{/* Hide the page's own breadcrumb link (always the first <a>) */}
<div className="[&>a:first-child]:hidden">{children}</div>
</>
) : (
children
)}
</div>
</header>
);
}