Collapsible docs sidebar + full-width playground#607
Conversation
A chevron tab on the right edge of #sidebar-content toggles html.ls-nav-collapsed and the sidebar wipes closed with an ease-out-snappy curve. The choice persists in localStorage and the Global Accounts playground defaults collapsed (set pre-paint in docs.json head.raw to avoid a flash). Desktop only; adds mintlify/sidebar-toggle.js plus chevron icons rendered as theme-aware masks. The state class is ls-nav-collapsed (not "sidebar-collapsed") to avoid the existing [class*="sidebar-collapse"] hide rule matching <html>. Co-authored-by: Cursor <cursoragent@cursor.com>
Lower $breakpoint-layout-wide 1800px -> 1200px so the embedded playground stays in the 3-column (Configure | App | API) layout at typical docs widths now that the docs sidebar can collapse. Co-authored-by: Cursor <cursoragent@cursor.com>
Collapsed now shows a 48px left rail with a bare panel icon that reserves real layout space: the sidebar collapses to the rail width (not 0), so the playground iframe starts after it instead of being overlaid. Expanded shows the toggle only on hover at the edge (col-resize cursor); collapsed uses a pointer cursor. Swaps the chevron glyphs for IconSidebarLeftArrow/RightArrow panel icons (1.5px stroke, matching the nav icons). Co-authored-by: Cursor <cursoragent@cursor.com>
- Drag the rail to resize the sidebar (280-420px, session-only, resets to 280 on reload); dragging left past the threshold collapses live. Click still toggles collapse. - Edge hover reveals the toggle in its resting state; hovering the button itself is its own active state. - 2px edge highlight on hover/drag drawn as an overlay so it never shifts layout, and suppressed during the open/close animation so it doesn't flash. - col-resize cursor on the edge (matches the demo's ColumnResizeHandle), pointer when collapsed; the playground iframe ignores pointer events mid- drag so the resize keeps tracking over it. Co-authored-by: Cursor <cursoragent@cursor.com>
The empty (skeleton) API panel was only capped to one screenful at the mobile breakpoint; in the laptop stacked range it grew to the full intrinsic skeleton height — a tall empty void below the app. Apply the same one-screen cap across the whole stacked range (below $breakpoint-layout-wide). Co-authored-by: Cursor <cursoragent@cursor.com>
Custom-layout pages (frontmatter mode: "custom", e.g. the flow builder) keep #sidebar-content in the DOM but render it hidden, so the old "does #sidebar-content exist?" check still injected the rail. Gate injection on a sidebar that actually renders (getClientRects), and re-validate on each SPA mutation batch so the rail is dropped the moment a page turns out to have no sidebar. A :has(.is-custom) CSS rule hides it instantly as a no-flash guard during navigation. Co-authored-by: Cursor <cursoragent@cursor.com>
- Resize handle hover highlight is now a full-height line over the divider matching the docs sidebar edge (2px, gray-300/700), drawn above the sticky panel headers so it isn't clipped by the API calls header — replaces the floating grabber pill. - Min API column width is the configure width (475) so dragging never shrinks it below its default/snap width. - Stack app + API at 1440px (was 1200) so the phone isn't squeezed tiny in the 3-column layout before it stacks. Co-authored-by: Cursor <cursoragent@cursor.com>
The collapse transition was always on, so a collapsed sidebar mounting on a freshly-loaded page (e.g. arriving from the sidebar-less flow builder) faded its nav content in from expanded over 260ms — a flash of nav content inside the rail. Gate the width/opacity transitions behind .ls-nav-animating, which the click handler sets (with a reflow) only for a real toggle; navigation and first paint now render the final collapsed state instantly. sync() clears the flag so a leftover toggle never bleeds into a navigation. Co-authored-by: Cursor <cursoragent@cursor.com>
The playground (global-accounts/demo) needs the horizontal space, so it should be collapsed regardless of the saved preference — previously a stored "expanded" preference kept it open. shouldCollapse() now returns true for the demo before consulting the pref, and the pre-paint script in docs.json collapses on the demo path regardless of the stored value so it's collapsed on first paint too. Every other page still follows the saved preference. Co-authored-by: Cursor <cursoragent@cursor.com>
The rail kept an always-on transition (left/width/background-color), so arriving on a page with a different collapse state — e.g. the always-collapsed playground from a page with the sidebar open — slid the rail across instead of showing it already in place. Gate the rail transition behind .ls-nav-animating like the column, so it only animates on a deliberate toggle, never on navigation. Co-authored-by: Cursor <cursoragent@cursor.com>
- Respect prefers-reduced-motion: the collapse transitions are gated behind .ls-nav-animating, which out-specified the old reduced-motion override, so it still animated. Point the override at those selectors — reduced-motion users get an instant toggle again. - Snap the rail button (no opacity fade) during navigation so its icon doesn't ghost in/out between pages; the hover reveal keeps its transition. - MutationObserver only re-adds the rail when it's actually missing instead of reading layout every frame; custom-layout pages stay covered by sync() on navigation plus the :has(.is-custom) CSS rule. - Lint-clean reflow: getBoundingClientRect() instead of `void offsetWidth`. - Note that the demo path is mirrored in the docs.json pre-paint script (it must run inline before this file, so the two are kept in sync by hand). Co-authored-by: Cursor <cursoragent@cursor.com>
Local-only personal notes; keeps it out of git (and stops review tooling from trying to read it). Co-authored-by: Cursor <cursoragent@cursor.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Greptile SummaryThis PR adds a collapsible and resizable docs sidebar via a fixed-position "rail" element injected by a new
Confidence Score: 5/5Safe to merge — the changes are self-contained UI enhancements with no backend, auth, or data-path impact. The sidebar toggle is a pure client-side UI layer isolated in an IIFE, and all state mutations are CSS class toggles and a single localStorage key. The wallet-demo changes (breakpoint, minimum column width, resize handle z-index) are likewise contained to layout CSS and the resize hook. No regressions to existing functionality were identified in the diff. mintlify/sidebar-toggle.js — the drag interaction uses absolute clientX rather than a start-offset delta, producing a small snap on drag start; worth a follow-up polish pass.
|
| Filename | Overview |
|---|---|
| mintlify/sidebar-toggle.js | New file implementing collapsible/resizable sidebar rail. Logic is well-structured with SPA-aware re-validation, reduced-motion support, and demo-path auto-collapse. Minor: drag uses absolute clientX rather than a start-offset delta (unlike the wallet demo hook). |
| mintlify/style.css | Adds rail + collapse CSS with transition gating (ls-nav-animating/ls-nav-dragging), reduced-motion override, and custom-layout guard. The existing hardcoded top: 112px concern was noted in a prior review. |
| mintlify/docs.json | Adds a pre-paint inline script that reads localStorage and stamps ls-nav-collapsed on <html> before render, preventing a flash of the expanded sidebar. DEMO_PATHS sync concern noted in a prior review. |
| components/grid-wallet-demo/src/hooks/useColumnResize.ts | Changes MIN_API from 320 to CONFIGURE_WIDTH (475) so the API column can only be widened from its snap width, never shrunk below it — preventing a sliver-width API panel. |
| components/grid-wallet-demo/src/styles/breakpoints.scss | Lowers $breakpoint-layout-wide from 1800px to 1440px so the 3-column layout kicks in earlier; the comment explains the phone minimum-width math driving the 1440 choice. |
| components/grid-wallet-demo/src/components/ApiPanel/ApiPanelEmpty.module.scss | Expands the empty-panel height cap from mobile-only to all stacked layouts (up to $breakpoint-layout-wide − 1), aligning with the updated 1440px stacking breakpoint. |
| components/grid-wallet-demo/src/components/ColumnResizeHandle/ColumnResizeHandle.module.scss | Bumps z-index to 4 (above sticky panel headers at z-index 3) and replaces the floating grabber pill with a full-height 2px line matching the docs sidebar style. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant Rail as .ls-nav-rail (button)
participant JS as sidebar-toggle.js
participant DOM as document.documentElement
participant LS as localStorage
Note over DOM: Pre-paint (docs.json inline script)
LS->>DOM: read 'ls-nav-collapsed' → add class if '1' or demo path
Note over JS: DOMContentLoaded → sync()
JS->>DOM: shouldCollapse() → applyState(collapsed)
JS->>Rail: ensureRail() → inject button into body
User->>Rail: mousedown (left btn, expanded)
Rail->>JS: startX captured, mousemove/mouseup attached to document
User->>Rail: drag (mousemove)
JS->>DOM: applyWidth(clampWidth(e.clientX)) → --ls-sidebar-width
alt "e.clientX < SNAP_COLLAPSE (240px)"
JS->>DOM: add ls-nav-collapsed
else e.clientX ≥ 240px
JS->>DOM: update --ls-sidebar-width live
end
User->>Rail: mouseup
JS->>DOM: remove ls-nav-dragging (transitions restored)
JS->>LS: "setPref('1'|'0')"
User->>Rail: click (no drag)
JS->>DOM: add ls-nav-animating (arms transition)
JS->>DOM: toggle ls-nav-collapsed
JS->>LS: setPref(next ? '1' : '0')
Note over DOM: 320ms later
JS->>DOM: remove ls-nav-animating
Note over JS: SPA navigation (MutationObserver / popstate)
JS->>DOM: remove ls-nav-animating, add ls-nav-snap
JS->>DOM: applyState(shouldCollapse()) — instant
JS->>Rail: ensureRail() (re-inject if removed by Mintlify)
JS->>DOM: remove ls-nav-snap (next rAF)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant Rail as .ls-nav-rail (button)
participant JS as sidebar-toggle.js
participant DOM as document.documentElement
participant LS as localStorage
Note over DOM: Pre-paint (docs.json inline script)
LS->>DOM: read 'ls-nav-collapsed' → add class if '1' or demo path
Note over JS: DOMContentLoaded → sync()
JS->>DOM: shouldCollapse() → applyState(collapsed)
JS->>Rail: ensureRail() → inject button into body
User->>Rail: mousedown (left btn, expanded)
Rail->>JS: startX captured, mousemove/mouseup attached to document
User->>Rail: drag (mousemove)
JS->>DOM: applyWidth(clampWidth(e.clientX)) → --ls-sidebar-width
alt "e.clientX < SNAP_COLLAPSE (240px)"
JS->>DOM: add ls-nav-collapsed
else e.clientX ≥ 240px
JS->>DOM: update --ls-sidebar-width live
end
User->>Rail: mouseup
JS->>DOM: remove ls-nav-dragging (transitions restored)
JS->>LS: "setPref('1'|'0')"
User->>Rail: click (no drag)
JS->>DOM: add ls-nav-animating (arms transition)
JS->>DOM: toggle ls-nav-collapsed
JS->>LS: setPref(next ? '1' : '0')
Note over DOM: 320ms later
JS->>DOM: remove ls-nav-animating
Note over JS: SPA navigation (MutationObserver / popstate)
JS->>DOM: remove ls-nav-animating, add ls-nav-snap
JS->>DOM: applyState(shouldCollapse()) — instant
JS->>Rail: ensureRail() (re-inject if removed by Mintlify)
JS->>DOM: remove ls-nav-snap (next rAF)
Reviews (3): Last reviewed commit: "Keep navbar link text at medium weight o..." | Re-trigger Greptile
Leaving the playground (collapsed -> expanded) was snapping the sidebar open, which read as a hard "pop": the nav appeared before the page content settled, and the swapped-in nav items showed instantly (brand-new elements don't transition). Make that one navigation animate — wipe the column open (ls-nav-animating) and fade the whole content container in via a keyframe (ls-nav-revealing, which plays regardless of element freshness). Entering the playground and page-to-page navigation still snap instantly. Co-authored-by: Cursor <cursoragent@cursor.com>
This reverts commit ca2d841. The wipe-open + content fade flashed white on staging — the content container fading from opacity 0 reveals the page background behind it — and didn't read as smooth. Back to the instant snap; the brief "pop" when leaving the playground is acceptable. Co-authored-by: Cursor <cursoragent@cursor.com>
The hosted Mintlify build wraps each navbar link's text in a <span> and applies a body-weight rule to descendants, so the text rendered at 400 even though the anchor itself was 500 (the local CLI build, 4.2.284, doesn't add that rule — which is why this only showed on staging). Target the inner elements too so GitHub / Book a live demo / Dashboard stay medium (500). Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
prefers-reduced-motion./global-accounts/demoopens collapsed regardless of the saved preference to give the 3-column embed room; every other page follows the saved pref.mode: "custom", e.g. the flow builder) render#sidebar-contenthidden, so the rail is gated on an actually-visible sidebar (plus a:has(.is-custom)CSS guard).Changes
Docs (
mintlify/)sidebar-toggle.js(new) — injects the rail, handles drag-resize / click-collapse, persistence, and SPA-aware re-validation.style.css— rail + collapse styling, transition gating, reduced-motion handling.docs.json— pre-paint inline script so first paint matches the collapse state (no flash).images/icons/IconSidebarLeftArrow.svg,IconSidebarRightArrow.svg(new).Demo (
components/grid-wallet-demo/)ColumnResizeHandle.module.scss,hooks/useColumnResize.ts,styles/breakpoints.scss,components/ApiPanel/ApiPanelEmpty.module.scss.Test plan
/global-accounts/demoalways opens collapsed (even with an "expanded" saved pref); other pages follow the saved pref.mode: "custom"page) shows no rail, going to/from it via SPA nav.prefers-reduced-motion: reduce→ toggle is instant.Made with Cursor