fix: one menu, at the left, holding whatever the page has
The previous attempt split it in two — a session button on the left, the site menu on the right — which is not what was asked for and is worse: a second menu an inch from the first is a choice nobody wants to make. One button. It sits at the left-hand end of the bar on a phone, where it always fits, and what is behind it changes with where you are: the site's sections always, plus the questions in this session, or the contents of the article you are reading. A control that changes what it holds must at least not change where it is. Reading registers its contents with the same mechanism a session uses for its questions, so the drawer grows the tab and nothing else has to know. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
This commit is contained in:
parent
9658ddb207
commit
658482c53a
3 changed files with 49 additions and 46 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||||
|
import useMediaQuery from '../hooks/useMediaQuery'
|
||||||
|
import { useSessionDrawer } from '../context/SessionDrawer'
|
||||||
import RichText from './RichText'
|
import RichText from './RichText'
|
||||||
/**
|
/**
|
||||||
* Article prose goes through the same renderer as everything else.
|
* Article prose goes through the same renderer as everything else.
|
||||||
|
|
@ -111,6 +113,16 @@ export default function ArticleReader({
|
||||||
// you consult, and a wall of prose hides the one heading you came for.
|
// you consult, and a wall of prose hides the one heading you came for.
|
||||||
const [openIds, setOpenIds] = useState({})
|
const [openIds, setOpenIds] = useState({})
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false)
|
const [drawerOpen, setDrawerOpen] = useState(false)
|
||||||
|
// On a phone the contents live behind the same button as the site menu, the
|
||||||
|
// way a session's questions do: one control at the top left whose contents
|
||||||
|
// change with where you are, rather than a second menu to hunt for. The rail
|
||||||
|
// is on screen above 1150px, so there is nothing to open there.
|
||||||
|
const narrow = useMediaQuery('(max-width: 1150px)')
|
||||||
|
const { register: registerDrawer } = useSessionDrawer()
|
||||||
|
useEffect(() => {
|
||||||
|
if (!narrow || bare) return undefined
|
||||||
|
return registerDrawer(() => setDrawerOpen(true))
|
||||||
|
}, [narrow, bare, registerDrawer])
|
||||||
// Collapsing the contents rail hands its width to the prose.
|
// Collapsing the contents rail hands its width to the prose.
|
||||||
const [railOpen, setRailOpen] = useState(() => stored(RAIL_KEY, 'open') !== 'closed')
|
const [railOpen, setRailOpen] = useState(() => stored(RAIL_KEY, 'open') !== 'closed')
|
||||||
const [size, setSize] = useState(() => (SIZES.includes(stored(SIZE_KEY, 'm')) ? stored(SIZE_KEY, 'm') : 'm'))
|
const [size, setSize] = useState(() => (SIZES.includes(stored(SIZE_KEY, 'm')) ? stored(SIZE_KEY, 'm') : 'm'))
|
||||||
|
|
|
||||||
|
|
@ -281,17 +281,37 @@ export default function Navbar({ onSignIn, onRegister, onSearch }) {
|
||||||
for stays put; where you are going gets out of the way while you read. */}
|
for stays put; where you are going gets out of the way while you read. */}
|
||||||
<div className="navbar-primary">
|
<div className="navbar-primary">
|
||||||
<div className="container navbar-inner">
|
<div className="container navbar-inner">
|
||||||
{/* While a session is open on a phone, the questions get their own
|
{/* One menu, at the left-hand end on a phone. What is inside it
|
||||||
control on the left and the site menu keeps the right. One burger
|
changes with where you are — the site's sections always, plus the
|
||||||
that changed meaning depending on where you were meant the two
|
questions in this session, or the contents of what you are
|
||||||
menus took turns, and the one you wanted was the other one. */}
|
reading — but it is always the same button in the same place. Two
|
||||||
{sessionDrawer.opener && (
|
buttons was the previous attempt and it was wrong: a second menu
|
||||||
<button type="button" className="nav-session-burger"
|
an inch from the first is a choice nobody wants to make.
|
||||||
aria-label="Questions in this session" onClick={sessionDrawer.opener}>
|
|
||||||
<span aria-hidden="true">☰</span>
|
It opens the page's own drawer where there is one, because the
|
||||||
</button>
|
site menu is a tab inside that; otherwise the site menu itself. */}
|
||||||
)}
|
<button
|
||||||
<Link to="/" className="logo" onClick={() => setMenuOpen(false)}
|
className="nav-burger"
|
||||||
|
onClick={() => (sessionDrawer.opener ? sessionDrawer.opener() : setMenuOpen(v => !v))}
|
||||||
|
aria-label={sessionDrawer.opener ? 'Menu and contents' : 'Menu'}
|
||||||
|
aria-expanded={sessionDrawer.opener ? undefined : menuOpen}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
display: 'block', width: 22, height: 2, background: 'currentColor',
|
||||||
|
borderRadius: 2, transition: 'transform 0.2s, opacity 0.2s',
|
||||||
|
transform: menuOpen ? 'translateY(7px) rotate(45deg)' : 'none',
|
||||||
|
}} />
|
||||||
|
<span style={{
|
||||||
|
display: 'block', width: 22, height: 2, background: 'currentColor',
|
||||||
|
borderRadius: 2, opacity: menuOpen ? 0 : 1, transition: 'opacity 0.2s',
|
||||||
|
}} />
|
||||||
|
<span style={{
|
||||||
|
display: 'block', width: 22, height: 2, background: 'currentColor',
|
||||||
|
borderRadius: 2, transition: 'transform 0.2s, opacity 0.2s',
|
||||||
|
transform: menuOpen ? 'translateY(-7px) rotate(-45deg)' : 'none',
|
||||||
|
}} />
|
||||||
|
</button>
|
||||||
|
<Link to="/" className="logo" onClick={() => setMenuOpen(false)}
|
||||||
onMouseEnter={() => setPeek(true)} onFocus={() => setPeek(true)}>🏥 PedsHub</Link>
|
onMouseEnter={() => setPeek(true)} onFocus={() => setPeek(true)}>🏥 PedsHub</Link>
|
||||||
{user && <GlobalSearch onOpenOverlay={onSearch} />}
|
{user && <GlobalSearch onOpenOverlay={onSearch} />}
|
||||||
|
|
||||||
|
|
@ -300,31 +320,7 @@ export default function Navbar({ onSignIn, onRegister, onSearch }) {
|
||||||
<FeedbackBadge />
|
<FeedbackBadge />
|
||||||
<JobsBadge jobs={jobs} />
|
<JobsBadge jobs={jobs} />
|
||||||
<AccountMenu user={user} onLogout={logout} />
|
<AccountMenu user={user} onLogout={logout} />
|
||||||
{/* Always the site menu. It used to become the session's list of
|
|
||||||
questions while a session was open, which put both menus on
|
|
||||||
the same button and neither where the thumb expected it; the
|
|
||||||
session now has its own control at the other end of the bar. */}
|
|
||||||
<button
|
|
||||||
className="nav-burger"
|
|
||||||
onClick={() => setMenuOpen(v => !v)}
|
|
||||||
aria-label="Menu"
|
|
||||||
aria-expanded={menuOpen}
|
|
||||||
>
|
|
||||||
<span style={{
|
|
||||||
display: 'block', width: 22, height: 2, background: 'currentColor',
|
|
||||||
borderRadius: 2, transition: 'transform 0.2s, opacity 0.2s',
|
|
||||||
transform: menuOpen ? 'translateY(7px) rotate(45deg)' : 'none',
|
|
||||||
}} />
|
|
||||||
<span style={{
|
|
||||||
display: 'block', width: 22, height: 2, background: 'currentColor',
|
|
||||||
borderRadius: 2, opacity: menuOpen ? 0 : 1, transition: 'opacity 0.2s',
|
|
||||||
}} />
|
|
||||||
<span style={{
|
|
||||||
display: 'block', width: 22, height: 2, background: 'currentColor',
|
|
||||||
borderRadius: 2, transition: 'transform 0.2s, opacity 0.2s',
|
|
||||||
transform: menuOpen ? 'translateY(-7px) rotate(-45deg)' : 'none',
|
|
||||||
}} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
/* Logged-out: Sign In + Register */
|
/* Logged-out: Sign In + Register */
|
||||||
|
|
|
||||||
|
|
@ -218,15 +218,6 @@ html, body { overflow-x: hidden; }
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) { .navbar-sections { transition: none; } }
|
@media (prefers-reduced-motion: reduce) { .navbar-sections { transition: none; } }
|
||||||
[data-theme="markdown"] .navbar .logo { color: #d4a96a; }
|
[data-theme="markdown"] .navbar .logo { color: #d4a96a; }
|
||||||
/* The session's own control, at the left-hand end of the bar. Off the desktop
|
|
||||||
layout entirely: there the list of questions is a rail beside the question,
|
|
||||||
and a button for it would open a drawer over something already on screen. */
|
|
||||||
.nav-session-burger {
|
|
||||||
display: none; margin-right: 2px; padding: 6px 8px; cursor: pointer;
|
|
||||||
border: 1px solid rgba(255, 255, 255, .22); border-radius: 8px;
|
|
||||||
background: none; color: var(--navbar-fg); font-size: 1rem; line-height: 1;
|
|
||||||
}
|
|
||||||
.nav-session-burger:hover { border-color: rgba(255, 255, 255, .45); }
|
|
||||||
.nav-burger { display: none; background: none !important; border: 0 !important; cursor: pointer; color: var(--navbar-fg); padding: 6px; flex-direction: column; gap: 5px; opacity: 0.85; }
|
.nav-burger { display: none; background: none !important; border: 0 !important; cursor: pointer; color: var(--navbar-fg); padding: 6px; flex-direction: column; gap: 5px; opacity: 0.85; }
|
||||||
/* Scoped to the dark bar. These were `.navbar a` and `.navbar button`, which
|
/* Scoped to the dark bar. These were `.navbar a` and `.navbar button`, which
|
||||||
also caught everything in the light section bar beneath it — near-white text
|
also caught everything in the light section bar beneath it — near-white text
|
||||||
|
|
@ -810,8 +801,12 @@ html, body { overflow-x: hidden; }
|
||||||
/* The burger carries the sections on a phone; the strip would be a second
|
/* The burger carries the sections on a phone; the strip would be a second
|
||||||
copy of the same links competing for the same thumb. */
|
copy of the same links competing for the same thumb. */
|
||||||
.nav-burger { display: flex; }
|
.nav-burger { display: flex; }
|
||||||
.nav-session-burger { display: inline-flex; align-items: center; }
|
|
||||||
.nav-logout { display: none; }
|
.nav-logout { display: none; }
|
||||||
|
/* At the left-hand end, before the logo — it is the first child of the bar
|
||||||
|
now, so nothing here has to reorder it. A control that changes what it
|
||||||
|
holds must at least not change where it is: reaching for the top right and
|
||||||
|
finding the account chip is how a menu gets a reputation. */
|
||||||
|
.navbar-inner > .nav-burger { margin-right: 2px; }
|
||||||
/* Hide the whole strip, wrapper and arrows, not just the link row. */
|
/* Hide the whole strip, wrapper and arrows, not just the link row. */
|
||||||
.navbar-sections .ss-wrap { display: none; }
|
.navbar-sections .ss-wrap { display: none; }
|
||||||
.navbar-sections, .navbar-sections-inner, .navbar-sections:focus-within { height: 42px; }
|
.navbar-sections, .navbar-sections-inner, .navbar-sections:focus-within { height: 42px; }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue