feat: add a mobile layout to the paper theme - #178
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The paper theme had no small-screen branch at all — none of its four CSS modules contained a single media query. At 390px it rendered the full desktop layout: the 262px sidebar ate two thirds of the viewport, and `.content`'s `margin-left: calc(-1 * var(--paper-sidebar-width))` dragged the article underneath it, clipping headings to "e (3.0)", "st" and "ed". There was no header, no hamburger and no way to reach the navigation. Add a mobile layout mirroring the default theme's, at the same 768px breakpoint: - a sticky mobile header carrying the existing SidebarHeader, the theme switcher and a hamburger toggle - a full-screen menu holding the chapter nav, a divider, the configured links as inline items (SidebarLinks variant='list') and the version switcher - the desktop sidebar hidden and `.content`'s negative offset neutralised, so the article is full-width and unclipped - the menu closes on navigation The reading-progress rail is a fixed 200px column pinned to the right edge, so it is dropped below the breakpoint rather than left overlapping the article, and the page navbar parks below the header instead of colliding with it. Two narrow-viewport defects surfaced once the content became full-width: a long breadcrumb could not shrink past its intrinsic width and stretched the page 7px wider than the viewport, and a long site title wrapped out of the fixed-height header. Both are corrected inside the media query. The mobile chrome only renders when the sidebar does, so it composes with reader mode rather than fighting it. Desktop rendering is unchanged — screenshots at 1440x900 before and after are byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f6a3d04 to
afe7ea9
Compare
* feat: add configurable links to the sidebar footer Adds a top-level `links:` key to chronicle.yaml, surfaced from the sidebar footer in both the default and paper themes. On desktop the links sit behind a `?` icon button that opens a menu upward, matching the design. In the default theme's mobile hamburger menu they render inline as nav items instead, above the version switcher — no dropdown. Each destination is tagged with a `ref` query param carrying the full URL of the page the link was clicked from. External links open with `noopener` (not `noreferrer`) so the destination also receives a Referer header; under the default referrer policy that header carries only the origin, so `ref` is what identifies the specific page. Existing query strings on the href are preserved, and non-web schemes such as mailto: are left untouched. The sidebar footer previously rendered only when versions were configured; it now renders when either versions or links are present. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: show latest.label as a static footer label when unversioned `latest` is valid without `versions` — the schema only requires it in the other direction — but both VersionSwitchers returned null whenever no versions were configured, so a configured `latest.label` was silently dropped. It now renders as static text in the sidebar footer. Deliberately not a dropdown: with nothing to switch to, a menu holding a single option would imply a navigation that does not exist. The footer visibility check now also accounts for a latest-only config. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: add a mobile layout to the paper theme (#178) The paper theme had no small-screen branch at all — none of its four CSS modules contained a single media query. At 390px it rendered the full desktop layout: the 262px sidebar ate two thirds of the viewport, and `.content`'s `margin-left: calc(-1 * var(--paper-sidebar-width))` dragged the article underneath it, clipping headings to "e (3.0)", "st" and "ed". There was no header, no hamburger and no way to reach the navigation. Add a mobile layout mirroring the default theme's, at the same 768px breakpoint: - a sticky mobile header carrying the existing SidebarHeader, the theme switcher and a hamburger toggle - a full-screen menu holding the chapter nav, a divider, the configured links as inline items (SidebarLinks variant='list') and the version switcher - the desktop sidebar hidden and `.content`'s negative offset neutralised, so the article is full-width and unclipped - the menu closes on navigation The reading-progress rail is a fixed 200px column pinned to the right edge, so it is dropped below the breakpoint rather than left overlapping the article, and the page navbar parks below the header instead of colliding with it. Two narrow-viewport defects surfaced once the content became full-width: a long breadcrumb could not shrink past its intrinsic width and stretched the page 7px wider than the viewport, and a long site title wrapped out of the fixed-height header. Both are corrected inside the media query. The mobile chrome only renders when the sidebar does, so it composes with reader mode rather than fighting it. Desktop rendering is unchanged — screenshots at 1440x900 before and after are byte-identical. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stacked on #176 — review that one first; this PR's diff shows only the mobile layout.
Problem
The paper theme had no responsive layout whatsoever.
grep -c "@media"returned 0 across all four of its CSS modules — the small-screen branch was simply never written.At 390px the theme rendered its full desktop layout:
.sidebaris an unconditionalwidth: var(--paper-sidebar-width)(262px), so it consumed two thirds of the viewport..contentcarriesmargin-left: calc(-1 * var(--paper-sidebar-width))to slide under that rail. With the rail still present the article was dragged underneath it and clipped on the left — headings rendered as…e (3.0),…st,…ed.This was not horizontal overflow:
document.documentElement.scrollWidth === window.innerWidth === 390. The layout just consumed the viewport.Approach
Followed the default theme as the reference implementation, at the same
max-width: 768pxbreakpoint:SidebarHeader,ClientThemeSwitcher(the existing shared component) and a hamburger.<SidebarLinks variant='list' />→ version switcher..content's negativemargin-leftneutralised so the article is full-width and unclipped.useEffectonpathname.Desktop is untouched: every new rule lives either inside the media query or on a new class that is
display: noneuntil the query turns it on.Where
SidebarHeaderwent, and whyIn the header, not the menu.
SidebarHeaderis paper's brand slot: it renders the site title as plain text, and only becomes a content-dirSelectwhen a site declares more than one content directory. On desktop it occupies the sidebar's header position — the top-left of the page.Moving it into the menu would have left the mobile header with no identity at all, which is a worse trade than a slightly busier bar; it would also have nested a
Selectpopover inside a full-screen overlay, which is awkward to operate. Keeping it in the header preserves the desktop mental model — same control, same top-left position — and it stays compact enough to sit alongside two icon buttons at 390px.This does differ from the default theme, which puts its content-dir entries in the menu as nav items. That theme has a separate
SidebarLogoto carry identity in the header; paper has no such component, so the two roles are conflated in one control and it has to live where identity belongs.Composing with reader mode
Paper wraps its layout in
ReaderModeProvider, andreaderModealready hides the sidebar. The mobile header and menu render under the sameshowSidebarcondition, so reader mode unmounts them along with the sidebar rather than leaving a header floating over a distraction-free page. Verified: in reader mode at 390px the header and menu are both absent and the content is the full 390px.The reading-progress rail
ReadingProgressisposition: fixed,width: 200px, pinned to the right edge — over half the viewport at 390px, sitting on top of the article. It is hidden below the breakpoint rather than shrunk; the page navbar already carries prev/next.Two defects that surfaced once content went full-width
Both are fixed inside the media query, so desktop is unaffected:
min-width: auto, soDeveloper Guide > Developer Guide (1.0)refused to shrink and forced the layout column to 397px inside a 390px viewport. Fixed withmin-width: 0on the layout column and the navbar's left cluster, plus clipping on the breadcrumb.Verification
Measured with headless Chrome over the DevTools Protocol (
Emulation.setDeviceMetricsOverride), which gives a genuine 390px viewport. The harness was validated first against a 50/50 split probe.bun run build:clibun testbiome lint src/themes/paper/tsconthemes/paper<Text size={2} weight={500}>)At 390x844, after:
The rail and the closed menu both compute to
display: none, and nothing paints over the article's right edge.Breakpoint boundary: at 768px the sidebar is
noneand the hamburger shows; at 769px the sidebar isflexwithmargin-left: -262pxand the hamburger is hidden.Menu contents, read out of the DOM in order — chapter nav, then a footer with a
1pxtop border, then the links, then the switcher:Menu closes on navigation: opening sets
data-open="true"/display: block/aria-expanded="true"; clickingGuidemoves/docs→/docs/guideand returns the menu todata-open="false"/display: none.Links still attach the
refparam — withwindow.openstubbed, clickingGo to appcaptured:Desktop unchanged: screenshots at 1440x900 before and after are byte-identical (
sha256 430b2d40…), re-confirmed after every subsequent change.A note on specificity
The brief flagged that the default theme had
.sidebar { display: none }losing to a.layout aside.sidebarbase rule. Paper has no such compound selector —.sidebaris a plain single-class rule — so the media query wins on source order. Confirmed at runtime rather than assumed: theasidecomputes todisplay: nonewith a measured width of 0.🤖 Generated with Claude Code