feat(school-detail): move Back to a standalone link, dock "Back to top" in the nav
Replicate the Autotrader pattern: pull the page-back control out of the sticky section-nav bar and place a standalone "← Back" text link above the header card, on the page background, where it scrolls away with the page. It keeps the context-aware behaviour (router.back → /search fallback); the secondary view gains the same fallback for parity (it previously used a bare router.back() that dead-ends on deep links). The pinned slot the Back button vacated now holds a "↑ Top" control that smooth-scrolls to the top, so the sticky bar keeps a useful affordance. Applies to both primary and secondary detail views. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,29 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Standalone back link, sits above the header card on the page background. */
|
||||||
|
.topBack {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
font-size: 1.0625rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent-coral, #e07256);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1.2;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBack:hover {
|
||||||
|
color: var(--accent-coral-dark, #c85a3e);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Header Section */
|
/* Header Section */
|
||||||
.header {
|
.header {
|
||||||
background: var(--bg-card, white);
|
background: var(--bg-card, white);
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ export function SchoolDetailView({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scrollToTop = () => {
|
||||||
|
if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = sectionLinksRef.current;
|
const el = sectionLinksRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -306,6 +310,13 @@ export function SchoolDetailView({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
{/* Standalone back link, above the header — returns to wherever the
|
||||||
|
user came from. Scrolls away with the page (the sticky bar keeps a
|
||||||
|
"back to top" control in its place). */}
|
||||||
|
<button type="button" onClick={handleBack} className={styles.topBack}>
|
||||||
|
<span aria-hidden="true">←</span> Back
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<div className={styles.headerContent}>
|
<div className={styles.headerContent}>
|
||||||
@@ -439,9 +450,9 @@ export function SchoolDetailView({
|
|||||||
|
|
||||||
{/* Sticky Section Navigation — docks under the global header */}
|
{/* Sticky Section Navigation — docks under the global header */}
|
||||||
<nav className={styles.sectionNav} aria-label="Page sections">
|
<nav className={styles.sectionNav} aria-label="Page sections">
|
||||||
<button onClick={handleBack} className={styles.sectionNavBack} aria-label="Back">
|
<button onClick={scrollToTop} className={styles.sectionNavBack} aria-label="Back to top">
|
||||||
<span aria-hidden="true">←</span>
|
<span aria-hidden="true">↑</span>
|
||||||
<span className={styles.sectionNavBackLabel}>Back</span>
|
<span className={styles.sectionNavBackLabel}>Top</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Desktop: scrolling section links */}
|
{/* Desktop: scrolling section links */}
|
||||||
|
|||||||
@@ -4,6 +4,29 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Standalone back link, sits above the header card on the page background. */
|
||||||
|
.topBack {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
font-size: 1.0625rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent-coral, #e07256);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1.2;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBack:hover {
|
||||||
|
color: var(--accent-coral-dark, #c85a3e);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Header ──────────────────────────────────────────── */
|
/* ── Header ──────────────────────────────────────────── */
|
||||||
.header {
|
.header {
|
||||||
background: var(--bg-card, white);
|
background: var(--bg-card, white);
|
||||||
|
|||||||
@@ -124,6 +124,20 @@ export function SecondarySchoolDetailView({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Back returns wherever the user came from; deep-links fall back to search
|
||||||
|
// so the button never dead-ends or leaves the site.
|
||||||
|
const handleBack = () => {
|
||||||
|
if (typeof window !== 'undefined' && window.history.length > 1) {
|
||||||
|
router.back();
|
||||||
|
} else {
|
||||||
|
router.push('/search');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToTop = () => {
|
||||||
|
if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
track('school_viewed', {
|
track('school_viewed', {
|
||||||
urn: schoolInfo.urn,
|
urn: schoolInfo.urn,
|
||||||
@@ -200,6 +214,12 @@ export function SecondarySchoolDetailView({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
{/* Standalone back link, above the header — returns wherever the user
|
||||||
|
came from. Scrolls away; the sticky bar keeps a "back to top" control. */}
|
||||||
|
<button type="button" onClick={handleBack} className={styles.topBack}>
|
||||||
|
<span aria-hidden="true">←</span> Back
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* ── Header ─────────────────────────────────────── */}
|
{/* ── Header ─────────────────────────────────────── */}
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<div className={styles.headerContent}>
|
<div className={styles.headerContent}>
|
||||||
@@ -324,7 +344,7 @@ export function SecondarySchoolDetailView({
|
|||||||
{/* ── Sticky section navigation ─────────────────────── */}
|
{/* ── Sticky section navigation ─────────────────────── */}
|
||||||
<nav className={styles.tabNav} aria-label="Page sections">
|
<nav className={styles.tabNav} aria-label="Page sections">
|
||||||
<div className={styles.tabNavInner}>
|
<div className={styles.tabNavInner}>
|
||||||
<button onClick={() => router.back()} className={styles.backBtn}>← Back</button>
|
<button onClick={scrollToTop} className={styles.backBtn} aria-label="Back to top">↑ Top</button>
|
||||||
{navItems.length > 0 && <div className={styles.tabNavDivider} />}
|
{navItems.length > 0 && <div className={styles.tabNavDivider} />}
|
||||||
{navItems.map(({ id, label }) => (
|
{navItems.map(({ id, label }) => (
|
||||||
<a
|
<a
|
||||||
|
|||||||
Reference in New Issue
Block a user