Skip to content

Commit

Permalink
Make the inline breakpoint configurable for each container
Browse files Browse the repository at this point in the history
This allows individual containers to become inline at different breakpoints,
eg. the Chapter List remaining fixed and scrolling when the ToC is already displayed inline.
  • Loading branch information
adamwoodnz committed Jan 30, 2024
1 parent 7c2c1a7 commit f223f6d
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 46 deletions.
4 changes: 3 additions & 1 deletion mu-plugins/blocks/sidebar-container/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
'<div %1$s>%2$s%3$s</div>',
'<div %1$s data-breakpoint="%2$s">%3$s%4$s</div>',
$wrapper_attributes,
esc_attr( $inlineBreakpoint ),
$content,
$back_to_top
);
Expand Down
45 changes: 31 additions & 14 deletions mu-plugins/blocks/sidebar-container/postcss/style.pcss
Original file line number Diff line number Diff line change
@@ -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);

Expand All @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions mu-plugins/blocks/sidebar-container/src/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"hasBackToTop": {
"type": "boolean",
"default": true
},
"inlineBreakpoint": {
"type": "string",
"default": "1200px"
}
},
"supports": {
Expand Down
89 changes: 58 additions & 31 deletions mu-plugins/blocks/sidebar-container/src/view.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -30,50 +26,81 @@ 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() {
containers = document.querySelectorAll( '.wp-block-wporg-sidebar-container' );
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' );
Expand Down

0 comments on commit f223f6d

Please sign in to comment.