diff --git a/mu-plugins/blocks/sidebar-container/index.php b/mu-plugins/blocks/sidebar-container/index.php index 08edf085d..4056b6982 100644 --- a/mu-plugins/blocks/sidebar-container/index.php +++ b/mu-plugins/blocks/sidebar-container/index.php @@ -45,11 +45,13 @@ function render( $attributes, $content, $block ) { esc_html__( '↑ Back to top', 'wporg' ) ) : ''; + $inlineBreakpoint = $attributes['inlineBreakpoint']; $wrapper_attributes = get_block_wrapper_attributes(); return sprintf( - '
%2$s%3$s
', + '
%3$s%4$s
', $wrapper_attributes, + esc_attr( $inlineBreakpoint ), $content, $back_to_top ); diff --git a/mu-plugins/blocks/sidebar-container/postcss/style.pcss b/mu-plugins/blocks/sidebar-container/postcss/style.pcss index fe8bcdd4e..de49d5de5 100644 --- a/mu-plugins/blocks/sidebar-container/postcss/style.pcss +++ b/mu-plugins/blocks/sidebar-container/postcss/style.pcss @@ -1,21 +1,34 @@ -.wp-block-wporg-sidebar-container .is-link-to-top { - display: none; +.wp-block-wporg-sidebar-container { + --local--offset-top: var(--wp-admin--admin-bar--height, 0); - & a { - text-decoration-line: none; + /* These vars are used in JS calcs */ + --local--nav--offset: var(--wp--custom--local-navigation-bar--spacing--height, 60px); + --local--padding-top: var(--wp--preset--spacing--20); - &:hover { - text-decoration-line: underline; + background-color: aliceblue; + + /* Account for local nav height on larger screens where it becomes fixed. */ + @media (min-width: 890px) { + /* stylelint-disable-next-line length-zero-no-unit */ + --local--nav--offset: 0px; + --local--offset-top: calc(var(--wp-admin--admin-bar--height, 0px) + var(--wp--custom--local-navigation-bar--spacing--height, 60px)); + } + + & .is-link-to-top { + display: none; + + & a { + text-decoration-line: none; + + &:hover { + text-decoration-line: underline; + } } } -} -/* Slot the search & table of contents into a floating sidebar on large screens. */ -@media (min-width: 1200px) { - .wp-block-wporg-sidebar-container { + /* Slot the search & table of contents into a floating sidebar on large screens. */ + &.is-floating-sidebar { --local--block-end-sidebar--width: 340px; - --local--offset-top: calc(var(--wp-admin--admin-bar--height, 0px) + var(--wp--custom--local-navigation-bar--spacing--height, 60px)); - --local--padding-top: var(--wp--preset--spacing--20); width: var(--local--block-end-sidebar--width); @@ -60,15 +73,16 @@ border-top: 1px solid var(--wp--preset--color--light-grey-1); } } +} - main .wp-block-wporg-sidebar-container { - 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); +main .wp-block-wporg-sidebar-container.is-floating-sidebar { + 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)); - /* 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)); - } } /* Set up the custom properties. These can be overridden by settings in theme.json. */ diff --git a/mu-plugins/blocks/sidebar-container/src/block.json b/mu-plugins/blocks/sidebar-container/src/block.json index c07a7574d..557d1652f 100644 --- a/mu-plugins/blocks/sidebar-container/src/block.json +++ b/mu-plugins/blocks/sidebar-container/src/block.json @@ -11,6 +11,10 @@ "hasBackToTop": { "type": "boolean", "default": true + }, + "inlineBreakpoint": { + "type": "string", + "default": "1200px" } }, "supports": { diff --git a/mu-plugins/blocks/sidebar-container/src/view.js b/mu-plugins/blocks/sidebar-container/src/view.js index 3d5b724a3..e97b39701 100644 --- a/mu-plugins/blocks/sidebar-container/src/view.js +++ b/mu-plugins/blocks/sidebar-container/src/view.js @@ -2,16 +2,13 @@ * Fallback values for custom properties match CSS defaults. */ -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_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 containers; let mainEl; +let adminBarHeight = getCustomPropValue( '--wp-admin--admin-bar--height' ) || 32; +let globalNavHeight = getCustomPropValue( '--wp-global-header-height' ) || 90; +const scrollHandlers = []; /** * Get the value of a CSS custom property. @@ -33,36 +30,37 @@ function getCustomPropValue( name, element = document.body ) { * 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. + * + * @param {HTMLElement} container */ -function onScroll() { - // Only run the scroll code if the sidebar is floating on a wide screen. - if ( ! window.matchMedia( '(min-width: 1200px)' ).matches ) { - return; - } +function createScrollHandler( container ) { + return function onWindowChange() { + // Only run the scroll code if the sidebar is floating. + if ( ! container.classList.contains( 'is-floating-sidebar' ) ) { + return false; + } - const { scrollY, innerHeight: windowHeight } = window; - const scrollPosition = scrollY - ADMIN_BAR_HEIGHT; + const { scrollY, innerHeight: windowHeight } = window; + const scrollPosition = scrollY - adminBarHeight; + const localNavOffset = getCustomPropValue( '--local--nav--offset', container ); + const paddingTop = getCustomPropValue( '--local--padding-top', container ); - // Toggle the fixed position based on whether the scrollPosition is greater than the initial gap from the top. - containers.forEach( ( container ) => { + // Toggle the fixed position based on whether the scrollPosition is greater than the + // initial gap from the top minus the padding applied when fixed. container.classList.toggle( 'is-fixed-sidebar', - scrollPosition > SCROLL_POSITION_TO_FIX - getCustomPropValue( '--local--padding-top', container ) + scrollPosition > SPACE_TO_TOP + globalNavHeight + localNavOffset - adminBarHeight - paddingTop ); - } ); - const footerStart = mainEl.offsetTop + mainEl.offsetHeight; + const footerStart = mainEl.offsetTop + mainEl.offsetHeight; - // Is the footer visible in the viewport? - if ( footerStart < scrollPosition + windowHeight ) { - containers.forEach( ( container ) => { + // Is the footer visible in the viewport? + if ( footerStart < scrollPosition + windowHeight ) { container.style.setProperty( 'height', `${ footerStart - scrollPosition - container.offsetTop }px` ); - } ); - } else { - containers.forEach( ( container ) => { + } else { container.style.removeProperty( 'height' ); - } ); - } + } + }; } function init() { @@ -70,10 +68,31 @@ function init() { mainEl = document.getElementById( 'wp--skip-link--target' ); if ( mainEl && containers.length ) { - onScroll(); // Run once to avoid footer collisions on load (ex, when linked to #reply-title). - window.addEventListener( 'scroll', onScroll ); + containers.forEach( ( container ) => { + const scrollHandler = createScrollHandler( container ); + scrollHandlers.push( scrollHandler ); + scrollHandler(); // Run once to avoid footer collisions on load (ex, when linked to #reply-title). + window.addEventListener( 'scroll', scrollHandler ); + } ); + } + + function onResize() { + adminBarHeight = getCustomPropValue( '--wp-admin--admin-bar--height' ) || 32; + globalNavHeight = getCustomPropValue( '--wp-global-header-height' ) || 90; + + containers.forEach( ( container ) => { + const breakpoint = container.dataset.breakpoint; + const shouldFloat = window.matchMedia( `(min-width: ${ breakpoint })` ).matches; + // Toggle the floating class if the window is wider than the configured breakpoint. + container.classList.toggle( 'is-floating-sidebar', shouldFloat ); + } ); + + scrollHandlers.forEach( ( handler ) => handler() ); } + onResize(); + window.addEventListener( 'resize', onResize() ); + // If there is no table of contents, hide the heading. if ( ! document.querySelector( '.wp-block-wporg-table-of-contents' ) ) { const heading = document.querySelector( '.wp-block-wporg-sidebar-container h2' );