diff --git a/app/assets/stylesheets/layout.css b/app/assets/stylesheets/layout.css index 8450fab4..351ff9c3 100644 --- a/app/assets/stylesheets/layout.css +++ b/app/assets/stylesheets/layout.css @@ -93,15 +93,19 @@ body { } } +/* The sidebar spans all four grid rows, so sizing it against its grid area makes + it as tall as the whole document and its overflow never kicks in. Stick it to + the viewport instead, so the table of contents scrolls its own content. */ :where(#sidebar) { background-color: var(--color-subtle-light); - block-size: 100%; + block-size: 100dvh; font-size: var(--font-medium-responsive); grid-area: sidebar; inline-size: 25vw; - max-block-size: 100%; + inset-block-start: 0; + max-block-size: 100dvh; overflow: auto; - position: relative; + position: sticky; transition: margin-inline-start 0.2s ease-out; :has(#sidebar-toggle:checked) & { diff --git a/test/system/sidebar_scroll_test.rb b/test/system/sidebar_scroll_test.rb new file mode 100644 index 00000000..ab7b9c66 --- /dev/null +++ b/test/system/sidebar_scroll_test.rb @@ -0,0 +1,41 @@ +require "application_system_test_case" + +class SidebarScrollTest < ApplicationSystemTestCase + setup do + sign_in "kevin@example.com" + + leaves(:welcome_page).leafable.update! body: ([ "A paragraph of the book." ] * 400).join("\n\n") + + visit leafable_slug_path(leaves(:welcome_page)) + find("label.sidebar__toggle").click + + assert_operator document_height, :>, viewport_height * 2, + "the test page should be considerably taller than the viewport" + end + + test "the table of contents fits the viewport instead of the page" do + assert_operator sidebar_height, :<=, viewport_height, + "the sidebar is #{sidebar_height}px tall in a #{viewport_height}px viewport: " \ + "it grows with the length of the page instead of staying within the screen" + end + + test "the table of contents stays put while the page scrolls" do + execute_script "window.scrollTo(0, document.documentElement.scrollHeight)" + + assert_equal 0, sidebar_top.round, + "the sidebar scrolled away with the page instead of staying in view" + end + + private + def viewport_height = evaluate_script("window.innerHeight") + + def document_height = evaluate_script("document.documentElement.scrollHeight") + + def sidebar_height = sidebar_rect["height"] + + def sidebar_top = sidebar_rect["top"] + + def sidebar_rect + evaluate_script("document.querySelector('#sidebar').getBoundingClientRect().toJSON()") + end +end