-
Notifications
You must be signed in to change notification settings - Fork 7
Collapsible docs sidebar + full-width playground #607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
patcapulong
wants to merge
15
commits into
main
Choose a base branch
from
pat/playground-full-width
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
bbc87f8
Add collapsible docs sidebar with an edge chevron tab
patcapulong 5650374
Keep wallet demo app + API side by side at docs widths
patcapulong f29af5b
Replace the sidebar chevron tab with a panel rail
patcapulong 8253bed
Make the sidebar rail drag-to-resize and refine its hover states
patcapulong fd363ff
Cap the empty API panel height in the laptop stacked layout
patcapulong 2c51b7f
Hide the sidebar rail on custom-layout pages
patcapulong 6dbaabe
Polish the playground resize edge, min width, and stacking
patcapulong e39fe54
Only animate the sidebar collapse on a deliberate toggle
patcapulong 00b8b2b
Always start the playground with the sidebar collapsed
patcapulong 83249b8
Snap the sidebar rail instantly on navigation
patcapulong 69492e8
Address review: reduced-motion, rail snap, lighter observer
patcapulong 61a4330
Ignore the pat-notes.md scratch file
patcapulong ca2d841
Animate the sidebar wipe-open when leaving the playground
patcapulong b4b94ea
Revert "Animate the sidebar wipe-open when leaving the playground"
patcapulong d4651f4
Keep navbar link text at medium weight on the hosted build
patcapulong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,243 @@ | ||
| // Collapsible + resizable docs sidebar (desktop). | ||
| // | ||
| // A rail on the right edge of #sidebar-content: | ||
| // - drag it to resize the sidebar (clamped MIN..MAX); release below the snap | ||
| // threshold to collapse. | ||
| // - click it (no drag) to toggle collapse. | ||
| // Collapsed shows a slim visible rail with a bare icon (click/pointer to | ||
| // reopen); expanded shows the toggle on hover at the edge (col-resize). Width + | ||
| // collapsed state persist in localStorage and are restored pre-paint by the | ||
| // inline script in docs.json head.raw, so there's no flash. | ||
|
|
||
| (function () { | ||
| var DESKTOP_MIN = 1024; | ||
| var KEY = 'ls-nav-collapsed'; | ||
| // NOTE: keep in sync with the demo-path check in docs.json head.raw — the | ||
| // pre-paint script collapses the playground before this runs (no flash). The | ||
| // path lives in both because the pre-paint must run inline, before this file. | ||
| var DEMO_PATHS = ['/global-accounts/demo', '/global-accounts/demo/']; | ||
| var MIN_WIDTH = 280; // the original sidebar width — only resizes wider | ||
| var MAX_WIDTH = 420; | ||
| var SNAP_COLLAPSE = 240; // drag left past this x -> collapse | ||
| var DRAG_THRESHOLD = 4; // px of movement before a press counts as a drag | ||
|
|
||
| var rail = null; | ||
|
|
||
| function isDesktop() { | ||
| return window.innerWidth >= DESKTOP_MIN; | ||
| } | ||
|
|
||
| function isDemo() { | ||
| return DEMO_PATHS.indexOf(location.pathname) !== -1; | ||
| } | ||
|
|
||
| // #sidebar-content is in the DOM on every docs page, but custom-layout pages | ||
| // (frontmatter mode: "custom", e.g. the flow builder) keep it and hide it | ||
| // (display:none). getClientRects() is empty for a non-rendered element, so this | ||
| // is true only when there's a real sidebar to toggle — no sidebar, no rail. | ||
| function hasVisibleSidebar() { | ||
| var sc = document.getElementById('sidebar-content'); | ||
| return !!sc && sc.getClientRects().length > 0; | ||
| } | ||
|
|
||
| function getPref() { | ||
| try { | ||
| return localStorage.getItem(KEY); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| function setPref(value) { | ||
| try { | ||
| localStorage.setItem(KEY, value); | ||
| } catch (e) { | ||
| /* private mode — toggle still works for the session */ | ||
| } | ||
| } | ||
|
|
||
| function clampWidth(w) { | ||
| return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, Math.round(w))); | ||
| } | ||
|
|
||
| // Width is session-only — not persisted, so a refresh resets to the 280px | ||
| // default (the CSS var fallback) with no post-paint resize jump. | ||
| function applyWidth(w) { | ||
| document.documentElement.style.setProperty('--ls-sidebar-width', w + 'px'); | ||
| } | ||
|
|
||
| // The playground (demo) always starts collapsed — it needs the horizontal | ||
| // space — regardless of the saved preference. Every other page follows the | ||
| // remembered preference (default expanded). | ||
| function shouldCollapse() { | ||
| if (isDemo()) return true; | ||
| return getPref() === '1'; | ||
| } | ||
|
|
||
| function isCollapsed() { | ||
| return document.documentElement.classList.contains('ls-nav-collapsed'); | ||
| } | ||
|
|
||
| function updateRail() { | ||
| if (!rail) return; | ||
| var collapsed = isCollapsed(); | ||
| rail.setAttribute('aria-label', collapsed ? 'Show navigation' : 'Hide navigation'); | ||
| rail.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); | ||
| } | ||
|
|
||
| function applyState(collapsed) { | ||
| document.documentElement.classList.toggle('ls-nav-collapsed', collapsed); | ||
| updateRail(); | ||
| } | ||
|
|
||
| function removeRail() { | ||
| if (rail && rail.parentNode) rail.parentNode.removeChild(rail); | ||
| rail = null; | ||
| } | ||
|
|
||
| function ensureRail() { | ||
| if (!isDesktop() || !hasVisibleSidebar()) { | ||
| removeRail(); | ||
| return; | ||
| } | ||
| if (rail && document.body.contains(rail)) return; | ||
|
|
||
| rail = document.createElement('button'); | ||
| rail.type = 'button'; | ||
| rail.className = 'ls-nav-rail'; | ||
| rail.innerHTML = '<span class="ls-nav-rail-btn" aria-hidden="true"></span>'; | ||
| attachInteractions(rail); | ||
| document.body.appendChild(rail); | ||
| updateRail(); | ||
| } | ||
|
|
||
| // Drag = resize (expanded only); plain click = toggle collapse (either state, | ||
| // mouse or keyboard). | ||
| function attachInteractions(el) { | ||
| var startX = 0; | ||
| var moved = false; | ||
| var dragging = false; | ||
| var dragEndAt = 0; | ||
| var animTimer = 0; | ||
|
|
||
| function onMove(e) { | ||
| if (!moved && Math.abs(e.clientX - startX) > DRAG_THRESHOLD) { | ||
| moved = true; | ||
| dragging = true; | ||
| document.documentElement.classList.add('ls-nav-dragging'); | ||
| document.body.style.userSelect = 'none'; | ||
| } | ||
| if (!dragging) return; | ||
| // Live: crossing the snap threshold collapses immediately (no release | ||
| // needed); dragging back out reopens and resumes resizing. | ||
| if (e.clientX < SNAP_COLLAPSE) { | ||
| if (!isCollapsed()) applyState(true); | ||
| } else { | ||
| if (isCollapsed()) applyState(false); | ||
| applyWidth(clampWidth(e.clientX)); | ||
| } | ||
| } | ||
|
|
||
| function onUp(e) { | ||
| document.removeEventListener('mousemove', onMove, true); | ||
| document.removeEventListener('mouseup', onUp, true); | ||
| if (!moved) return; // a click — handled by the click listener | ||
| dragEndAt = Date.now(); | ||
| document.documentElement.classList.remove('ls-nav-dragging'); | ||
| document.body.style.userSelect = ''; | ||
| // State was already applied live during the drag — just persist it. | ||
| if (isCollapsed()) { | ||
| setPref('1'); | ||
| } else { | ||
| applyWidth(clampWidth(e.clientX)); | ||
| setPref('0'); | ||
| } | ||
| } | ||
|
|
||
| // Resize only from the expanded edge; the collapsed rail is click-only. | ||
| el.addEventListener('mousedown', function (e) { | ||
| if (e.button !== 0 || isCollapsed()) return; | ||
| e.preventDefault(); | ||
| startX = e.clientX; | ||
| moved = false; | ||
| dragging = false; | ||
| document.addEventListener('mousemove', onMove, true); | ||
| document.addEventListener('mouseup', onUp, true); | ||
| }); | ||
|
|
||
| el.addEventListener('click', function () { | ||
| if (Date.now() - dragEndAt < 300) return; // swallow the click after a drag | ||
| var next = !isCollapsed(); | ||
| setPref(next ? '1' : '0'); | ||
| // ls-nav-animating turns the collapse transition on for this deliberate | ||
| // toggle (it's off by default so navigation never animates) and suppresses | ||
| // the hover reveal so the button/edge don't flash from the cursor sitting | ||
| // over the rail mid-transition. Add it + force a reflow before applyState | ||
| // so the width/opacity change animates from the current value, not snaps. | ||
| document.documentElement.classList.add('ls-nav-animating'); | ||
| document.documentElement.getBoundingClientRect(); // force reflow to arm the transition | ||
| applyState(next); | ||
| clearTimeout(animTimer); | ||
| animTimer = setTimeout(function () { | ||
| document.documentElement.classList.remove('ls-nav-animating'); | ||
| }, 320); | ||
| }); | ||
| } | ||
|
|
||
| function sync() { | ||
| var root = document.documentElement; | ||
| // Navigation/first paint must never animate (only deliberate toggles do — | ||
| // see the click handler). Clear the animate flag, and snap the rail button | ||
| // for this navigation-driven state change so its icon doesn't ghost in/out | ||
| // between pages; restore its transition next frame so hover reveals animate. | ||
| root.classList.remove('ls-nav-animating'); | ||
| root.classList.add('ls-nav-snap'); | ||
| applyState(shouldCollapse()); | ||
| ensureRail(); | ||
| requestAnimationFrame(function () { | ||
| root.classList.remove('ls-nav-snap'); | ||
| }); | ||
| } | ||
|
|
||
| if (document.readyState === 'loading') { | ||
| document.addEventListener('DOMContentLoaded', sync); | ||
| } else { | ||
| sync(); | ||
| } | ||
|
|
||
| // SPA navigation: Mintlify swaps content without a full reload. On a path | ||
| // change, re-sync. Otherwise only re-add the rail if Mintlify wiped it — a | ||
| // cheap guard so we don't read layout every frame. Custom-layout pages are | ||
| // handled by sync() on navigation plus the CSS :has(.is-custom) rule, so the | ||
| // rail never lingers visibly even without per-frame polling here. | ||
| var lastPath = location.pathname; | ||
| var ensureScheduled = false; | ||
| function scheduleEnsureRail() { | ||
| if (ensureScheduled) return; | ||
| ensureScheduled = true; | ||
| requestAnimationFrame(function () { | ||
| ensureScheduled = false; | ||
| ensureRail(); | ||
| }); | ||
| } | ||
| var observer = new MutationObserver(function () { | ||
| if (location.pathname !== lastPath) { | ||
| lastPath = location.pathname; | ||
| sync(); | ||
| } else if (!rail || !document.body.contains(rail)) { | ||
| scheduleEnsureRail(); | ||
| } | ||
| }); | ||
| observer.observe(document.body, { childList: true, subtree: true }); | ||
| window.addEventListener('popstate', sync); | ||
|
|
||
| var rafPending = false; | ||
| window.addEventListener('resize', function () { | ||
| if (rafPending) return; | ||
| rafPending = true; | ||
| requestAnimationFrame(function () { | ||
| rafPending = false; | ||
| ensureRail(); | ||
| }); | ||
| }); | ||
| })(); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.