From c79eae46726881be09af2072432c16e8fb0ce0d8 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:08:38 +1300 Subject: [PATCH 01/23] Make sidebar container scroll internally --- .../sidebar-container/postcss/style.pcss | 42 +++++++--- .../blocks/sidebar-container/src/view.js | 83 ++++--------------- 2 files changed, 43 insertions(+), 82 deletions(-) diff --git a/mu-plugins/blocks/sidebar-container/postcss/style.pcss b/mu-plugins/blocks/sidebar-container/postcss/style.pcss index 20d7aedfb..f7615047d 100644 --- a/mu-plugins/blocks/sidebar-container/postcss/style.pcss +++ b/mu-plugins/blocks/sidebar-container/postcss/style.pcss @@ -14,6 +14,7 @@ @media (min-width: 1200px) { .wp-block-wporg-sidebar-container { --local--block-end-sidebar--width: 340px; + --local--top-offset: calc(var(--wp-admin--admin-bar--height, 0px) + var(--wp--custom--local-navigation-bar--spacing--height, 60px)); position: absolute; top: calc(var(--wp-global-header-offset, 90px) + var(--wp--custom--local-navigation-bar--spacing--height, 60px)); @@ -23,32 +24,47 @@ width: var(--local--block-end-sidebar--width); margin-top: var(--wp--custom--wporg-sidebar-container--spacing--margin--top); margin-bottom: 0 !important; + padding-top: var(--wp--preset--spacing--20); + + &:not(.is-fixed-sidebar) { + + /* Match width of custom scrollbar when fixed, stops content width changing */ + padding-right: 16px; + } &.is-fixed-sidebar { position: fixed; top: 0; + height: calc(100vh - var(--local--top-offset)); + margin-top: var(--local--top-offset); + overflow-y: scroll; - /* Make the space above the sidebar the same as the height of the local nav. */ - margin-top: calc(var(--wp-admin--admin-bar--height, 0px) + var(--wp--custom--local-navigation-bar--spacing--height, 60px) * 2); - } + /* Custom scrollbar so that it can be made visible on hover */ + &::-webkit-scrollbar, + &::-webkit-scrollbar-thumb { + background-color: transparent; + } - &.is-bottom-sidebar { - position: absolute; - } + &:hover::-webkit-scrollbar-thumb { + background-color: var(--wp--preset--color--charcoal-4); + border: 4.5px solid transparent; + background-clip: content-box; + border-radius: 10px; + } - &.is-fixed-sidebar .is-link-to-top, - &.is-bottom-sidebar .is-link-to-top { - display: block; - margin-top: 0; + .is-link-to-top { + display: block; + padding-top: var(--wp--preset--spacing--20); - & a { - color: var(--wp--preset--color--charcoal-4); + & a { + color: var(--wp--preset--color--charcoal-4); + } } } .wp-block-wporg-table-of-contents + .is-link-to-top { border-top: 1px solid var(--wp--preset--color--light-grey-1); - padding-top: var(--wp--preset--spacing--20); + padding-bottom: var(--wp--preset--spacing--20); } } } diff --git a/mu-plugins/blocks/sidebar-container/src/view.js b/mu-plugins/blocks/sidebar-container/src/view.js index 04f7ff705..fb3d2cde3 100644 --- a/mu-plugins/blocks/sidebar-container/src/view.js +++ b/mu-plugins/blocks/sidebar-container/src/view.js @@ -1,17 +1,14 @@ /** * Fallback values for custom properties match CSS defaults. */ -const globalNavHeight = 90; -const LOCAL_NAV_HEIGHT = getCustomPropValue( '--wp--custom--local-navigation-bar--spacing--height' ) || 60; +const GLOBAL_NAV_HEIGHT = getCustomPropValue( '--wp-global-header-height' ) || 90; const ADMIN_BAR_HEIGHT = parseInt( window.getComputedStyle( document.documentElement ).getPropertyValue( 'margin-top' ), 10 ); -const SPACE_FROM_BOTTOM = getCustomPropValue( '--wp--preset--spacing--edge-space' ) || 80; const SPACE_TO_TOP = getCustomPropValue( '--wp--custom--wporg-sidebar-container--spacing--margin--top' ) || 80; -const FIXED_HEADER_HEIGHT = globalNavHeight + LOCAL_NAV_HEIGHT + ADMIN_BAR_HEIGHT; -const SCROLL_POSITION_TO_FIX = globalNavHeight + SPACE_TO_TOP - LOCAL_NAV_HEIGHT - ADMIN_BAR_HEIGHT; +const SCROLL_POSITION_TO_FIX = GLOBAL_NAV_HEIGHT + SPACE_TO_TOP - ADMIN_BAR_HEIGHT; let container; let mainEl; @@ -36,59 +33,28 @@ function getCustomPropValue( name, element = document.body ) { * Check the position of the sidebar vs the height of the viewport & page * container, and toggle the "bottom" class to position the sidebar without * overlapping the footer. - * - * @return {boolean} True if the sidebar is at the bottom of the page. */ function onScroll() { // Only run the scroll code if the sidebar is floating on a wide screen. - if ( ! mainEl || ! container || ! window.matchMedia( '(min-width: 1200px)' ).matches ) { - return false; + if ( ! window.matchMedia( '(min-width: 1200px)' ).matches ) { + return; } - const scrollPosition = window.scrollY - ADMIN_BAR_HEIGHT; - - if ( ! container.classList.contains( 'is-bottom-sidebar' ) ) { - const footerStart = mainEl.offsetTop + mainEl.offsetHeight; - // The pixel location of the bottom of the sidebar, relative to the top of the page. - const sidebarBottom = scrollPosition + container.offsetHeight + container.offsetTop - ADMIN_BAR_HEIGHT; - - // Is the sidebar bottom crashing into the footer? - if ( footerStart - SPACE_FROM_BOTTOM < sidebarBottom ) { - container.classList.add( 'is-bottom-sidebar' ); - - // Bottom sidebar is absolutely positioned, so we need to set the top relative to the page origin. - // The pixel location of the top of the sidebar, relative to the footer. - const sidebarTop = - footerStart - container.offsetHeight - LOCAL_NAV_HEIGHT * 2 + ADMIN_BAR_HEIGHT - SPACE_FROM_BOTTOM; - container.style.setProperty( 'top', `${ sidebarTop }px` ); - - return true; - } - } else if ( container.getBoundingClientRect().top > LOCAL_NAV_HEIGHT * 2 + ADMIN_BAR_HEIGHT ) { - // If the top of the sidebar is above the top fixing position, switch back to just a fixed sidebar. - container.classList.remove( 'is-bottom-sidebar' ); - container.style.removeProperty( 'top' ); - } + const { scrollY, innerHeight: windowHeight } = window; + // const footerTop = footer.getBoundingClientRect().top; + const scrollPosition = scrollY - ADMIN_BAR_HEIGHT; // Toggle the fixed position based on whether the scrollPosition is greater than the initial gap from the top. container.classList.toggle( 'is-fixed-sidebar', scrollPosition > SCROLL_POSITION_TO_FIX ); - return false; -} + const footerStart = mainEl.offsetTop + mainEl.offsetHeight; -function isSidebarWithinViewport() { - if ( ! container ) { - return false; + // Is footerStart visible in the viewport? + if ( footerStart < scrollPosition + windowHeight ) { + container.style.setProperty( 'height', `${ footerStart - scrollPosition - container.offsetTop }px` ); + } else { + container.style.removeProperty( 'height' ); } - // Usable viewport height. - const viewHeight = window.innerHeight - LOCAL_NAV_HEIGHT + ADMIN_BAR_HEIGHT; - // Get the height of the sidebar, plus the top offset and 60px for the - // "Back to top" link, which isn't visible until `is-fixed-sidebar` is - // added, therefore not included in the offsetHeight value. - const sidebarHeight = container.offsetHeight + LOCAL_NAV_HEIGHT + 60; - // If the sidebar is shorter than the view area, apply the class so - // that it's fixed and scrolls with the page content. - return sidebarHeight < viewHeight; } function init() { @@ -109,30 +75,9 @@ function init() { } ); } - if ( isSidebarWithinViewport() ) { + if ( mainEl && container ) { onScroll(); // Run once to avoid footer collisions on load (ex, when linked to #reply-title). window.addEventListener( 'scroll', onScroll ); - - const observer = new window.ResizeObserver( () => { - // If the sidebar is positioned at the bottom and mainEl resizes, - // it will remain fixed at the previous bottom position, leading to a broken page layout. - // In this case manually trigger the scroll handler to reposition. - if ( container.classList.contains( 'is-bottom-sidebar' ) ) { - container.classList.remove( 'is-bottom-sidebar' ); - container.style.removeProperty( 'top' ); - const isBottom = onScroll(); - // After the sidebar is repositioned, also adjusts the scroll position - // to a point where the sidebar is visible. - if ( isBottom ) { - window.scrollTo( { - top: container.offsetTop - FIXED_HEADER_HEIGHT, - behavior: 'instant', - } ); - } - } - } ); - - observer.observe( mainEl ); } // If there is no table of contents, hide the heading. From 1fd41c15478f50d8af98b97ea0ec9251be5d8257 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Fri, 12 Jan 2024 14:43:52 +1300 Subject: [PATCH 02/23] Decouple sidebar container from ToC --- .../sidebar-container/postcss/style.pcss | 2 +- .../blocks/sidebar-container/src/view.js | 14 ----------- .../blocks/table-of-contents/src/block.json | 3 ++- .../blocks/table-of-contents/src/view.js | 24 +++++++++++++++++++ 4 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 mu-plugins/blocks/table-of-contents/src/view.js diff --git a/mu-plugins/blocks/sidebar-container/postcss/style.pcss b/mu-plugins/blocks/sidebar-container/postcss/style.pcss index f7615047d..76e8658ab 100644 --- a/mu-plugins/blocks/sidebar-container/postcss/style.pcss +++ b/mu-plugins/blocks/sidebar-container/postcss/style.pcss @@ -62,7 +62,7 @@ } } - .wp-block-wporg-table-of-contents + .is-link-to-top { + * + .is-link-to-top { border-top: 1px solid var(--wp--preset--color--light-grey-1); padding-bottom: var(--wp--preset--spacing--20); } diff --git a/mu-plugins/blocks/sidebar-container/src/view.js b/mu-plugins/blocks/sidebar-container/src/view.js index fb3d2cde3..7ac7d4243 100644 --- a/mu-plugins/blocks/sidebar-container/src/view.js +++ b/mu-plugins/blocks/sidebar-container/src/view.js @@ -60,20 +60,6 @@ function onScroll() { function init() { container = document.querySelector( '.wp-block-wporg-sidebar-container' ); mainEl = document.getElementById( 'wp--skip-link--target' ); - const toggleButton = container?.querySelector( '.wporg-table-of-contents__toggle' ); - const list = container?.querySelector( '.wporg-table-of-contents__list' ); - - if ( toggleButton && list ) { - toggleButton.addEventListener( 'click', function () { - if ( toggleButton.getAttribute( 'aria-expanded' ) === 'true' ) { - toggleButton.setAttribute( 'aria-expanded', false ); - list.removeAttribute( 'style' ); - } else { - toggleButton.setAttribute( 'aria-expanded', true ); - list.setAttribute( 'style', 'display:block;' ); - } - } ); - } if ( mainEl && container ) { onScroll(); // Run once to avoid footer collisions on load (ex, when linked to #reply-title). diff --git a/mu-plugins/blocks/table-of-contents/src/block.json b/mu-plugins/blocks/table-of-contents/src/block.json index 5cfe99478..77e164600 100644 --- a/mu-plugins/blocks/table-of-contents/src/block.json +++ b/mu-plugins/blocks/table-of-contents/src/block.json @@ -26,5 +26,6 @@ } }, "editorScript": "file:./index.js", - "style": "file:./style.css" + "style": "file:./style.css", + "viewScript": "file:./view.js" } diff --git a/mu-plugins/blocks/table-of-contents/src/view.js b/mu-plugins/blocks/table-of-contents/src/view.js new file mode 100644 index 000000000..67ac55992 --- /dev/null +++ b/mu-plugins/blocks/table-of-contents/src/view.js @@ -0,0 +1,24 @@ +function init() { + const container = document.querySelector( '.wp-block-wporg-table-of-contents' ); + + if ( ! container ) { + return; + } + + const toggleButton = container.querySelector( '.wporg-table-of-contents__toggle' ); + const list = container.querySelector( '.wporg-table-of-contents__list' ); + + if ( toggleButton && list ) { + toggleButton.addEventListener( 'click', function () { + if ( toggleButton.getAttribute( 'aria-expanded' ) === 'true' ) { + toggleButton.setAttribute( 'aria-expanded', false ); + list.removeAttribute( 'style' ); + } else { + toggleButton.setAttribute( 'aria-expanded', true ); + list.setAttribute( 'style', 'display:block;' ); + } + } ); + } +} + +window.addEventListener( 'load', init ); From b05e9334a55aa4fe679cc08f39bb28e5c7dee662 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:01:40 +1300 Subject: [PATCH 03/23] Allow multiple sidebar containers --- .../sidebar-container/postcss/style.pcss | 16 ++++++++++------ .../blocks/sidebar-container/src/view.js | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/mu-plugins/blocks/sidebar-container/postcss/style.pcss b/mu-plugins/blocks/sidebar-container/postcss/style.pcss index 76e8658ab..fbc148aa4 100644 --- a/mu-plugins/blocks/sidebar-container/postcss/style.pcss +++ b/mu-plugins/blocks/sidebar-container/postcss/style.pcss @@ -16,15 +16,19 @@ --local--block-end-sidebar--width: 340px; --local--top-offset: calc(var(--wp-admin--admin-bar--height, 0px) + var(--wp--custom--local-navigation-bar--spacing--height, 60px)); - position: absolute; - top: calc(var(--wp-global-header-offset, 90px) + var(--wp--custom--local-navigation-bar--spacing--height, 60px)); - - /* Right offset should be "edge spacing" at minimum, otherwise calculate it to be centered. */ - right: max(var(--wp--preset--spacing--edge-space), calc((100% - var(--wp--style--global--wide-size)) / 2)); width: var(--local--block-end-sidebar--width); - margin-top: var(--wp--custom--wporg-sidebar-container--spacing--margin--top); margin-bottom: 0 !important; padding-top: var(--wp--preset--spacing--20); + background: red; + + main & { + position: absolute; + top: calc(var(--wp-global-header-offset, 90px) + var(--wp--custom--local-navigation-bar--spacing--height, 60px)); + margin-top: var(--wp--custom--wporg-sidebar-container--spacing--margin--top); + + /* Right offset should be "edge spacing" at minimum, otherwise calculate it to be centered. */ + right: max(var(--wp--preset--spacing--edge-space), calc((100% - var(--wp--style--global--wide-size)) / 2)); + } &:not(.is-fixed-sidebar) { diff --git a/mu-plugins/blocks/sidebar-container/src/view.js b/mu-plugins/blocks/sidebar-container/src/view.js index 7ac7d4243..27198b1f5 100644 --- a/mu-plugins/blocks/sidebar-container/src/view.js +++ b/mu-plugins/blocks/sidebar-container/src/view.js @@ -10,7 +10,7 @@ const ADMIN_BAR_HEIGHT = parseInt( const SPACE_TO_TOP = getCustomPropValue( '--wp--custom--wporg-sidebar-container--spacing--margin--top' ) || 80; const SCROLL_POSITION_TO_FIX = GLOBAL_NAV_HEIGHT + SPACE_TO_TOP - ADMIN_BAR_HEIGHT; -let container; +let containers; let mainEl; /** @@ -45,23 +45,29 @@ function onScroll() { const scrollPosition = scrollY - ADMIN_BAR_HEIGHT; // Toggle the fixed position based on whether the scrollPosition is greater than the initial gap from the top. - container.classList.toggle( 'is-fixed-sidebar', scrollPosition > SCROLL_POSITION_TO_FIX ); + containers.forEach( ( container ) => { + container.classList.toggle( 'is-fixed-sidebar', scrollPosition > SCROLL_POSITION_TO_FIX ); + } ); const footerStart = mainEl.offsetTop + mainEl.offsetHeight; // Is footerStart visible in the viewport? if ( footerStart < scrollPosition + windowHeight ) { - container.style.setProperty( 'height', `${ footerStart - scrollPosition - container.offsetTop }px` ); + containers.forEach( ( container ) => { + container.style.setProperty( 'height', `${ footerStart - scrollPosition - container.offsetTop }px` ); + } ); } else { - container.style.removeProperty( 'height' ); + containers.forEach( ( container ) => { + container.style.removeProperty( 'height' ); + } ); } } function init() { - container = document.querySelector( '.wp-block-wporg-sidebar-container' ); + containers = document.querySelectorAll( '.wp-block-wporg-sidebar-container' ); mainEl = document.getElementById( 'wp--skip-link--target' ); - if ( mainEl && container ) { + if ( mainEl && containers.length ) { onScroll(); // Run once to avoid footer collisions on load (ex, when linked to #reply-title). window.addEventListener( 'scroll', onScroll ); } From 769f18eb64a98e67d0525274bd0e862616ea9fe8 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:06:20 +1300 Subject: [PATCH 04/23] Remove debug color --- mu-plugins/blocks/sidebar-container/postcss/style.pcss | 1 - 1 file changed, 1 deletion(-) diff --git a/mu-plugins/blocks/sidebar-container/postcss/style.pcss b/mu-plugins/blocks/sidebar-container/postcss/style.pcss index fbc148aa4..c9e1b45cc 100644 --- a/mu-plugins/blocks/sidebar-container/postcss/style.pcss +++ b/mu-plugins/blocks/sidebar-container/postcss/style.pcss @@ -19,7 +19,6 @@ width: var(--local--block-end-sidebar--width); margin-bottom: 0 !important; padding-top: var(--wp--preset--spacing--20); - background: red; main & { position: absolute; From 49d1facbe9b5b7ebc4d326148b0472e0f5c9e1f7 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:15:37 +1300 Subject: [PATCH 05/23] Make back to top link optional --- mu-plugins/blocks/sidebar-container/index.php | 10 ++++++---- mu-plugins/blocks/sidebar-container/postcss/style.pcss | 3 +-- mu-plugins/blocks/sidebar-container/src/block.json | 7 ++++++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/mu-plugins/blocks/sidebar-container/index.php b/mu-plugins/blocks/sidebar-container/index.php index 248c386b2..08edf085d 100644 --- a/mu-plugins/blocks/sidebar-container/index.php +++ b/mu-plugins/blocks/sidebar-container/index.php @@ -39,10 +39,12 @@ function init() { * @return string Returns the block markup. */ function render( $attributes, $content, $block ) { - $back_to_top = sprintf( - '
', - esc_html__( '↑ Back to top', 'wporg' ) - ); + $back_to_top = $attributes['hasBackToTop'] + ? sprintf( + '', + esc_html__( '↑ Back to top', 'wporg' ) + ) + : ''; $wrapper_attributes = get_block_wrapper_attributes(); return sprintf( diff --git a/mu-plugins/blocks/sidebar-container/postcss/style.pcss b/mu-plugins/blocks/sidebar-container/postcss/style.pcss index c9e1b45cc..b1a05dd91 100644 --- a/mu-plugins/blocks/sidebar-container/postcss/style.pcss +++ b/mu-plugins/blocks/sidebar-container/postcss/style.pcss @@ -18,7 +18,7 @@ width: var(--local--block-end-sidebar--width); margin-bottom: 0 !important; - padding-top: var(--wp--preset--spacing--20); + padding: var(--wp--preset--spacing--20) 0; main & { position: absolute; @@ -67,7 +67,6 @@ * + .is-link-to-top { border-top: 1px solid var(--wp--preset--color--light-grey-1); - padding-bottom: var(--wp--preset--spacing--20); } } } diff --git a/mu-plugins/blocks/sidebar-container/src/block.json b/mu-plugins/blocks/sidebar-container/src/block.json index 90d9b5ede..c07a7574d 100644 --- a/mu-plugins/blocks/sidebar-container/src/block.json +++ b/mu-plugins/blocks/sidebar-container/src/block.json @@ -7,7 +7,12 @@ "category": "layout", "description": "A sticky container to be used in 2-column layouts.", "textdomain": "wporg", - "attributes": {}, + "attributes": { + "hasBackToTop": { + "type": "boolean", + "default": true + } + }, "supports": { "inserter": false, "__experimentalLayout": true, From 0eb7deac7d84f20fc5296503d1d266b3f9bcba22 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Tue, 16 Jan 2024 17:17:33 +1300 Subject: [PATCH 06/23] Update docs --- mu-plugins/blocks/sidebar-container/src/view.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mu-plugins/blocks/sidebar-container/src/view.js b/mu-plugins/blocks/sidebar-container/src/view.js index 27198b1f5..1274c42bc 100644 --- a/mu-plugins/blocks/sidebar-container/src/view.js +++ b/mu-plugins/blocks/sidebar-container/src/view.js @@ -30,9 +30,9 @@ function getCustomPropValue( name, element = document.body ) { } /** - * Check the position of the sidebar vs the height of the viewport & page - * container, and toggle the "bottom" class to position the sidebar without - * overlapping the footer. + * Check the position of each sidebar relative to the scroll position, + * and toggle the "fixed" class at a certain point. + * Reduce the height of each sidebar to stop them overlapping the footer. */ function onScroll() { // Only run the scroll code if the sidebar is floating on a wide screen. @@ -41,7 +41,6 @@ function onScroll() { } const { scrollY, innerHeight: windowHeight } = window; - // const footerTop = footer.getBoundingClientRect().top; const scrollPosition = scrollY - ADMIN_BAR_HEIGHT; // Toggle the fixed position based on whether the scrollPosition is greater than the initial gap from the top. @@ -51,7 +50,7 @@ function onScroll() { const footerStart = mainEl.offsetTop + mainEl.offsetHeight; - // Is footerStart visible in the viewport? + // Is the footer visible in the viewport? if ( footerStart < scrollPosition + windowHeight ) { containers.forEach( ( container ) => { container.style.setProperty( 'height', `${ footerStart - scrollPosition - container.offsetTop }px` ); From 08191e060201c3bcbe8f76f5ae360cb2c1559771 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:15:36 +1300 Subject: [PATCH 07/23] Fix postcss selector nesting Co-authored-by: Kelly Dwan