Skip to content

feat(site): add responsive table of contents rail - #1858

Merged
decepulis merged 4 commits into
mainfrom
codex/feat/docs-toc-rail
Jul 22, 2026
Merged

feat(site): add responsive table of contents rail#1858
decepulis merged 4 commits into
mainfrom
codex/feat/docs-toc-rail

Conversation

@decepulis

@decepulis decepulis commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replace the mobile and tablet docs table-of-contents select with a notion-style stripe rail that keeps document position visible without consuming vertical space. Tapping the rail opens the existing desktop table of contents in an accessible popover.

Screenshots

docs-toc-popover-mobile-light docs-toc-rail-mobile-light docs-toc-rail-tablet-dark


Note

Low Risk
Docs-site UI and navigation only; behavior is covered by unit tests for rail geometry with no auth or data changes.

Overview
Replaces the mobile/tablet docs TOC dropdown with a fixed left-edge stripe rail that reflects heading depth and the active section; tapping it opens the same desktop TOC list in a Base UI popover (closes on scroll, resize, and at the xl breakpoint).

Adds calculateRailGeometry (with Vitest coverage) so rail stripes compress to fit the viewport, plus starting-style / ending-style Tailwind variants for popover enter/exit. Desktop TOC gains aria-label and aria-current="location" on the active link.

The docs framework page drops the dedicated mobile TOC grid row and related --mobile-toc-h / --scroll-mt layout variables now that the rail no longer consumes vertical space.

Reviewed by Cursor Bugbot for commit 63d85ee. Bugbot is set up for automated code reviews on this repo. Configure here.

@netlify

netlify Bot commented Jul 22, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 63d85ee
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a612e07dcb9880008e2156b
😎 Deploy Preview https://deploy-preview-1858--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Jul 22, 2026 8:54pm

Request Review

return () => {
window.removeEventListener('resize', updateViewportLayout);
window.visualViewport?.removeEventListener('resize', updateViewportLayout);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Visual viewport ignored in layout

Medium Severity

The mobile TOC layout registers a visualViewport resize listener but computes railTop and availableHeight only from window.innerHeight. When the visible viewport shrinks (mobile browser chrome, on-screen keyboard), the rail can sit off-screen and the popover maxHeight can exceed what users can actually see.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a52d972. Configure here.

@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html — no changes
Presets (7)
Entry Initial Lazy
/video (default) 51.00 kB 54.24 kB
/video (default + hls) 190.25 kB 54.24 kB
/video (minimal) 51.02 kB 54.24 kB
/video (minimal + hls) 190.21 kB 54.24 kB
/audio (default) 44.26 kB 54.24 kB
/audio (minimal) 41.47 kB 54.24 kB
/background 4.20 kB
Media (10)
Entry Initial
/media/background-video 1.14 kB
/media/container 1.71 kB
/media/dash-video 214.79 kB
/media/hlsjs-video 141.52 kB
/media/mux-audio 164.00 kB
/media/mux-video 163.70 kB
/media/native-hls-video 9.05 kB
/media/simple-hls-audio-only 17.19 kB
/media/simple-hls-video 18.69 kB
/media/vimeo-video 12.31 kB
Players (5)
Entry Initial
/video/player 8.22 kB
/audio/player 5.38 kB
/background/player 3.92 kB
/live-video/player 7.63 kB
/live-audio/player 5.39 kB
Skins (30)
Entry Type Initial Lazy
/video/minimal-skin.css css 5.70 kB
/video/skin.css css 5.65 kB
/video/minimal-skin js 51.02 kB 54.24 kB
/video/minimal-skin.tailwind js 51.64 kB 54.24 kB
/video/skin js 50.96 kB 54.24 kB
/video/skin.tailwind js 51.77 kB 54.24 kB
/audio/minimal-skin.css css 3.97 kB
/audio/skin.css css 3.86 kB
/audio/minimal-skin js 41.45 kB 54.24 kB
/audio/minimal-skin.tailwind js 42.04 kB 54.24 kB
/audio/skin js 44.27 kB 54.24 kB
/audio/skin.tailwind js 44.82 kB 54.24 kB
/background/skin.css css 133 B
/background/skin js 1.14 kB
/live-video/minimal-skin.css css 5.70 kB
/live-video/skin.css css 5.65 kB
/live-video/minimal-skin js 47.89 kB 54.24 kB
/live-video/minimal-skin.tailwind js 48.45 kB 54.24 kB
/live-video/skin js 49.48 kB 54.24 kB
/live-video/skin.tailwind js 50.11 kB 54.24 kB
/live-audio/minimal-skin.css css 3.97 kB
/live-audio/skin.css css 3.86 kB
/live-audio/minimal-skin js 33.86 kB 54.24 kB
/live-audio/minimal-skin.tailwind js 33.22 kB 54.24 kB
/live-audio/skin js 36.79 kB 54.24 kB
/live-audio/skin.tailwind js 36.29 kB 54.24 kB
/global.css css 183 B
/shared.css css 153 B
/tailwind.css css 161 B
/skin-element js 1.51 kB
UI Components (39)
Entry Initial
/ui/airplay-button 2.54 kB
/ui/alert-dialog 2.90 kB
/ui/alert-dialog-close 2.36 kB
/ui/alert-dialog-description 2.31 kB
/ui/alert-dialog-title 2.25 kB
/ui/audio-track-radio-group 3.03 kB
/ui/buffering-indicator 2.62 kB
/ui/captions-button 2.66 kB
/ui/captions-radio-group 3.03 kB
/ui/cast-button 2.57 kB
/ui/compounds 3.19 kB
/ui/controls 2.96 kB
/ui/error-dialog 2.97 kB
/ui/fullscreen-button 2.57 kB
/ui/hotkey 2.47 kB
/ui/menu 3.00 kB
/ui/mute-button 2.56 kB
/ui/pip-button 2.55 kB
/ui/play-button 2.61 kB
/ui/playback-rate-button 2.63 kB
/ui/playback-rate-radio-group 2.86 kB
/ui/popover 3.13 kB
/ui/poster 2.49 kB
/ui/quality-radio-group 3.00 kB
/ui/seek-button 2.56 kB
/ui/seek-indicator 2.74 kB
/ui/seek-indicator-value 385 B
/ui/slider 2.99 kB
/ui/status-announcer 2.50 kB
/ui/status-indicator 2.64 kB
/ui/status-indicator-value 450 B
/ui/thumbnail 2.56 kB
/ui/time 2.98 kB
/ui/time-slider 3.00 kB
/ui/tooltip 2.86 kB
/ui/volume-indicator 2.57 kB
/ui/volume-indicator-fill 441 B
/ui/volume-indicator-value 514 B
/ui/volume-slider 2.99 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react — no changes
Presets (7)
Entry Initial Lazy
/video (default) 40.56 kB 54.24 kB
/video (default + hls) 178.73 kB 54.24 kB
/video (minimal) 40.94 kB 54.24 kB
/video (minimal + hls) 179.07 kB 54.24 kB
/audio (default) 33.76 kB 54.24 kB
/audio (minimal) 33.80 kB 54.24 kB
/background 579 B
Media (9)
Entry Initial
/media/background-video 403 B
/media/dash-video 212.98 kB
/media/hlsjs-video 139.79 kB
/media/mux-audio 162.38 kB
/media/mux-video 162.41 kB
/media/native-hls-video 7.26 kB
/media/simple-hls-audio-only 15.39 kB
/media/simple-hls-video 16.96 kB
/media/vimeo-video 10.44 kB
Skins (27)
Entry Type Initial Lazy
/tailwind.css css 161 B
/video/minimal-skin.css css 5.55 kB
/video/skin.css css 5.52 kB
/video/minimal-skin js 40.88 kB 54.24 kB
/video/minimal-skin.tailwind js 46.97 kB 54.24 kB
/video/skin js 40.47 kB 54.24 kB
/video/skin.tailwind js 46.63 kB 54.24 kB
/audio/minimal-skin.css css 3.79 kB
/audio/skin.css css 3.68 kB
/audio/minimal-skin js 33.80 kB 54.24 kB
/audio/minimal-skin.tailwind js 35.90 kB 54.24 kB
/audio/skin js 33.68 kB 54.24 kB
/audio/skin.tailwind js 37.92 kB 54.24 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 5.55 kB
/live-video/skin.css css 5.52 kB
/live-video/minimal-skin js 35.43 kB 54.24 kB
/live-video/minimal-skin.tailwind js 41.46 kB 54.24 kB
/live-video/skin js 35.44 kB 54.24 kB
/live-video/skin.tailwind js 41.55 kB 54.24 kB
/live-audio/minimal-skin.css css 3.79 kB
/live-audio/skin.css css 3.68 kB
/live-audio/minimal-skin js 24.25 kB 54.24 kB
/live-audio/minimal-skin.tailwind js 27.42 kB 54.24 kB
/live-audio/skin js 24.31 kB 54.24 kB
/live-audio/skin.tailwind js 27.45 kB 54.24 kB
UI Components (33)
Entry Initial
/ui/airplay-button 2.45 kB
/ui/alert-dialog 2.60 kB
/ui/audio-track 2.32 kB
/ui/buffering-indicator 2.51 kB
/ui/captions-button 2.55 kB
/ui/captions-radio-group 2.29 kB
/ui/cast-button 2.46 kB
/ui/controls 2.53 kB
/ui/error-dialog 2.58 kB
/ui/fullscreen-button 2.42 kB
/ui/gesture 2.31 kB
/ui/hotkey 2.32 kB
/ui/live-button 2.40 kB
/ui/menu 2.56 kB
/ui/mute-button 2.38 kB
/ui/pip-button 2.50 kB
/ui/play-button 2.49 kB
/ui/playback-rate 2.45 kB
/ui/playback-rate-button 2.47 kB
/ui/popover 3.02 kB
/ui/poster 2.43 kB
/ui/quality 2.26 kB
/ui/seek-button 2.40 kB
/ui/seek-indicator 2.58 kB
/ui/slider 2.70 kB
/ui/status-announcer 2.30 kB
/ui/status-indicator 2.34 kB
/ui/thumbnail 2.46 kB
/ui/time 2.23 kB
/ui/time-slider 2.54 kB
/ui/tooltip 2.97 kB
/ui/volume-indicator 2.33 kB
/ui/volume-slider 2.52 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (68)
Entry Initial Lazy
. 10.39 kB
/dom 17.70 kB
/dom/media/custom-media-element 2.09 kB
/dom/media/dash 208.97 kB
/dom/media/google-cast 4.03 kB
/dom/media/hls-js 135.87 kB
/dom/media/media-host 1.25 kB
/dom/media/media-played-ranges 576 B
/dom/media/mux 151.37 kB
/dom/media/native-hls 3.07 kB
/dom/media/simple-hls 16.48 kB
/dom/media/simple-hls-audio-only 14.93 kB
/dom/media/vimeo 9.87 kB
/media/predicate 563 B
/i18n 2.66 kB 54.24 kB
/i18n/locales/all 30.58 kB
/i18n/locales/ar 1.21 kB
/i18n/locales/az 1.08 kB
/i18n/locales/bg 1.25 kB
/i18n/locales/bn 1.27 kB
/i18n/locales/bs 1014 B
/i18n/locales/ca 1.05 kB
/i18n/locales/cs 1.04 kB
/i18n/locales/cy 1.00 kB
/i18n/locales/da 996 B
/i18n/locales/de 1.08 kB
/i18n/locales/el 1.45 kB
/i18n/locales/en 621 B
/i18n/locales/es 1010 B
/i18n/locales/et 1.05 kB
/i18n/locales/eu 1.01 kB
/i18n/locales/fa 1.20 kB
/i18n/locales/fi 1.03 kB
/i18n/locales/fr 1.07 kB
/i18n/locales/gd 1.10 kB
/i18n/locales/gl 1014 B
/i18n/locales/he 1.12 kB
/i18n/locales/hi 1.28 kB
/i18n/locales/hr 1.02 kB
/i18n/locales/hu 1.09 kB
/i18n/locales/it 1023 B
/i18n/locales/ja 1.17 kB
/i18n/locales/ko 1.11 kB
/i18n/locales/lv 1.08 kB
/i18n/locales/mr 1.28 kB
/i18n/locales/nb 1000 B
/i18n/locales/ne 1.28 kB
/i18n/locales/nl 1013 B
/i18n/locales/nn 996 B
/i18n/locales/oc 1.05 kB
/i18n/locales/pl 1.12 kB
/i18n/locales/pt 1.01 kB
/i18n/locales/pt-BR 1.01 kB
/i18n/locales/pt-PT 1009 B
/i18n/locales/ro 1.05 kB
/i18n/locales/ru 1.33 kB
/i18n/locales/sk 1.10 kB
/i18n/locales/sl 1.02 kB
/i18n/locales/sr 1.03 kB
/i18n/locales/sv 1022 B
/i18n/locales/te 1.31 kB
/i18n/locales/th 1.29 kB
/i18n/locales/tr 1.07 kB
/i18n/locales/uk 1.36 kB
/i18n/locales/vi 1.08 kB
/i18n/locales/zh 1.01 kB
/i18n/locales/zh-CN 1.01 kB
/i18n/locales/zh-TW 1.01 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Initial
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Initial
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (11)
Entry Initial
/array 104 B
/dom 2.74 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/percent 281 B
/string 231 B
/style 190 B
/time 629 B
/number 158 B
📦 @videojs/spf — no changes
Entries (4)
Entry Initial
. 4.45 kB
/dom 6.32 kB
/hls 15.40 kB
/background-video 12.85 kB

ℹ️ How to interpret

JS sizes are initial static graph totals (minified + brotli). Lazy dynamic chunks are shown separately when present.

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current initial sizes.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 63d85ee. Configure here.

window.addEventListener('scroll', closeOnDocumentScroll, { passive: true });

return () => window.removeEventListener('scroll', closeOnDocumentScroll);
}, [open]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Popover closes on inner scroll

Medium Severity

While the TOC popover is open, a window scroll listener always calls setOpen(false). Scrolling over the popup still moves the document when the list does not overflow or when scroll chaining hits the end of overflow-y-auto, so the popover can dismiss during normal TOC interaction.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 63d85ee. Configure here.


return (
<nav ref={navRef} className={clsx('', className)}>
<nav ref={navRef} aria-label="On this page" className={clsx('', className)}>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: className={className} ?

@decepulis
decepulis merged commit 53e543b into main Jul 22, 2026
26 checks passed
@decepulis
decepulis deleted the codex/feat/docs-toc-rail branch July 22, 2026 23:26
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.

2 participants