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..deaa07fe6 100644
--- a/mu-plugins/blocks/sidebar-container/postcss/style.pcss
+++ b/mu-plugins/blocks/sidebar-container/postcss/style.pcss
@@ -1,21 +1,32 @@
-.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);
+
+ /* These vars are used in JS calcs */
+ --local--nav--offset: var(--wp--custom--local-navigation-bar--spacing--height, 60px);
+ --local--padding: var(--wp--preset--spacing--20);
- & a {
- text-decoration-line: none;
+ /* 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));
+ }
- &:hover {
- text-decoration-line: underline;
+ & .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);
@@ -30,7 +41,7 @@
top: 0;
height: calc(100vh - var(--local--offset-top));
margin-top: var(--local--offset-top) !important;
- padding: var(--local--padding-top) 0;
+ padding: var(--local--padding) 0;
overflow-y: scroll;
/* Custom scrollbar so that it can be made visible on hover */
@@ -60,8 +71,14 @@
border-top: 1px solid var(--wp--preset--color--light-grey-1);
}
}
+}
+
+main .wp-block-wporg-sidebar-container {
+
+ /* Hide the main sidebar until layout classes have been applied, to avoid FOUC */
+ display: none;
- 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);
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..d80f0c766 100644
--- a/mu-plugins/blocks/sidebar-container/src/view.js
+++ b/mu-plugins/blocks/sidebar-container/src/view.js
@@ -1,17 +1,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;
+let globalNavHeight;
+const scrollHandlers = [];
/**
* Get the value of a CSS custom property.
@@ -30,39 +26,62 @@ function getCustomPropValue( name, element = document.body ) {
}
/**
- * Check the position of each sidebar relative to the scroll position,
+ * Check the position of the 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.
+ * Reduce the height of the sidebar to stop it 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', 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' );
- } );
- }
+ }
+ };
+}
+
+/**
+ * Set the height for the admin bar and global nav vars.
+ * Set the floating sidebar class on each container based on their breakpoint.
+ * Show hidden containers after layout.
+ */
+function onResize() {
+ adminBarHeight = getCustomPropValue( '--wp-admin--admin-bar--height' ) || 32;
+ globalNavHeight = getCustomPropValue( '--wp-global-header-height' ) || 90;
+
+ containers.forEach( ( container ) => {
+ // Toggle the floating class based on the configured breakpoint.
+ const shouldFloat = window.matchMedia( `(min-width: ${ container.dataset.breakpoint })` ).matches;
+ container.classList.toggle( 'is-floating-sidebar', shouldFloat );
+ // Show the sidebar after layout, if it has been hidden to avoid FOUC.
+ if ( 'none' === window.getComputedStyle( container ).display ) {
+ container.style.setProperty( 'display', 'revert' );
+ }
+ } );
+
+ scrollHandlers.forEach( ( handler ) => handler() );
}
function init() {
@@ -70,10 +89,18 @@ 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 );
+ window.addEventListener( 'scroll', scrollHandler );
+ } );
}
+ // Run once to set height vars and position elements on load.
+ // Avoids footer collisions (ex, when linked to #reply-title).
+ 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' );