Skip to content

Collapsible docs sidebar + full-width playground#607

Open
patcapulong wants to merge 15 commits into
mainfrom
pat/playground-full-width
Open

Collapsible docs sidebar + full-width playground#607
patcapulong wants to merge 15 commits into
mainfrom
pat/playground-full-width

Conversation

@patcapulong

Copy link
Copy Markdown
Contributor

Summary

  • Collapsible + resizable docs sidebar via an edge "rail": hover the sidebar's right edge to reveal a toggle (col-resize cursor), drag to resize (280–420px), drag past the threshold or click to collapse, and click the slim rail to reopen. Collapse state persists across pages/sessions (localStorage); width is session-only and resets to 280px on refresh (no post-paint jump).
  • Animation only on deliberate toggles. The ease-out-snappy wipe (column width, nav opacity, and the rail slide) runs on a click toggle but is instant on navigation/first paint — so a collapsed sidebar never fades in or slides across when you move between pages. Respects prefers-reduced-motion.
  • Playground always starts collapsed. /global-accounts/demo opens collapsed regardless of the saved preference to give the 3-column embed room; every other page follows the saved pref.
  • No rail on sidebar-less pages. Custom-layout pages (mode: "custom", e.g. the flow builder) render #sidebar-content hidden, so the rail is gated on an actually-visible sidebar (plus a :has(.is-custom) CSS guard).
  • Wallet-demo layout polish: resize edge restyled to match the docs sidebar (full-height 2px line, not a floating grabber pill, drawn above the panel headers); API column never resizes below its snap width (475px); app + API stack at 1440px (was 1200) so the phone never shrinks tiny in 3-up; empty API panel is height-capped in the stacked layout.

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

  • Hover the sidebar's right edge → toggle appears (col-resize cursor); drag to resize 280–420px; drag past threshold or click to collapse; click the slim rail to reopen.
  • Collapse state persists across pages and a fresh session; refresh resets width to 280px with no jump.
  • /global-accounts/demo always opens collapsed (even with an "expanded" saved pref); other pages follow the saved pref.
  • Navigating while collapsed → no flash of nav content, no rail slide (instant); only deliberate clicks animate.
  • Flow builder (and any mode: "custom" page) shows no rail, going to/from it via SPA nav.
  • prefers-reduced-motion: reduce → toggle is instant.
  • Playground wide (≳1440px): 3 columns with a full-size phone; below that, app stacks over API; resize edge matches the docs sidebar; empty API panel isn't a tall void when stacked.
  • Mobile (<1024px): no rail; existing mobile layout unchanged.
  • Light + dark mode both look correct.

Made with Cursor

patcapulong and others added 12 commits June 21, 2026 02:13
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>
@mintlify

mintlify Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Grid 🟢 Ready View Preview Jun 21, 2026, 7:31 PM

@vercel

vercel Bot commented Jun 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Jun 21, 2026 9:50pm
grid-wallet-demo Ignored Ignored Preview Jun 21, 2026 9:50pm

Request Review

@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a collapsible and resizable docs sidebar via a fixed-position "rail" element injected by a new sidebar-toggle.js script, and polishes the wallet-demo playground layout with a full-height resize edge, a tighter stacking breakpoint (1800px → 1440px), and a minimum API column width equal to the configure column width.

  • Docs sidebar (mintlify/): sidebar-toggle.js injects the rail, handles drag-resize / click-collapse, persists collapse state in localStorage, and re-syncs on SPA navigation via MutationObserver + popstate. A pre-paint inline script in docs.json stamps ls-nav-collapsed on <html> before first render to avoid a flash. Animation is deliberately gated behind ls-nav-animating so navigation and first paint are always instant, and prefers-reduced-motion is respected.
  • Wallet demo (components/grid-wallet-demo/): $breakpoint-layout-wide drops from 1800px to 1440px so the 3-column layout appears earlier; MIN_API is raised to CONFIGURE_WIDTH (475px) to prevent the API column from shrinking below its snap width; the resize handle becomes a full-height 2px line (z-index 4) matching the docs sidebar; and the empty API panel height is capped for all stacked layouts, not just mobile.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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)
Loading
%%{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)
Loading

Reviews (3): Last reviewed commit: "Keep navbar link text at medium weight o..." | Re-trigger Greptile

Comment thread mintlify/sidebar-toggle.js
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant