From c540b37ce8a195595b6c6e6773497ed8c885a959 Mon Sep 17 00:00:00 2001 From: cpap Date: Thu, 22 Oct 2020 17:02:08 +0300 Subject: [PATCH 01/13] Adds forward Ref to the sidebar --- client/layout/sidebar/index.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/layout/sidebar/index.jsx b/client/layout/sidebar/index.jsx index 8b8229c9ac215..7c253d45f05df 100644 --- a/client/layout/sidebar/index.jsx +++ b/client/layout/sidebar/index.jsx @@ -10,18 +10,22 @@ import SidebarRegion from './region'; */ import './style.scss'; -export default function Sidebar( { children, onClick, className } ) { +const Sidebar = React.forwardRef( ( { children, onClick, className, ...props }, ref ) => { const hasRegions = React.Children.toArray( children ).some( ( el ) => el.type === SidebarRegion ); const finalClassName = classNames( 'sidebar', className, { 'has-regions': hasRegions } ); return (
{ children }
); -} +} ); + +export default Sidebar; From 7df1f7395e43d8efd6fa125608bbae26e735d90a Mon Sep 17 00:00:00 2001 From: cpap Date: Thu, 22 Oct 2020 17:12:26 +0300 Subject: [PATCH 02/13] First iteration of moving sidebar on scroll --- client/my-sites/sidebar-unified/index.jsx | 29 +++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/client/my-sites/sidebar-unified/index.jsx b/client/my-sites/sidebar-unified/index.jsx index e3f7d3cc01721..3e864fb2af643 100644 --- a/client/my-sites/sidebar-unified/index.jsx +++ b/client/my-sites/sidebar-unified/index.jsx @@ -10,7 +10,7 @@ /** * External dependencies */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; /** @@ -31,11 +31,36 @@ import { itemLinkMatches } from '../sidebar/utils'; import { getSidebarIsCollapsed } from 'calypso/state/ui/selectors'; import './style.scss'; +const masterbarHeight = 32; + export const MySitesSidebarUnified = ( { path } ) => { const menuItems = useSiteMenuItems(); const isAllDomainsView = useDomainsViewStatus(); const isRequestingMenu = useSelector( getIsRequestingAdminMenu ); const sidebarIsCollapsed = useSelector( getSidebarIsCollapsed ); + const [ sidebarStyles, setSidebarStyles ] = useState( { top: 0 } ); + const sidebar = React.createRef(); + + useEffect( () => { + if ( + typeof window !== 'undefined' && + sidebar.current !== 'undefined' && + sidebar.current !== null && + sidebar.current.scrollHeight + masterbarHeight > window.innerHeight + ) { + window.onscroll = () => { + const currentScrollPos = window.pageYOffset; + const maxScroll = sidebar.current.scrollHeight + masterbarHeight - window.innerHeight; + if ( currentScrollPos >= 0 && currentScrollPos < maxScroll ) { + setSidebarStyles( { top: -currentScrollPos } ); + } else { + setSidebarStyles( { top: 'inherit' } ); + } + }; + } + + // Need to cleanup + }, [ sidebar ] ); /** * If there are no menu items and we are currently requesting some, @@ -48,7 +73,7 @@ export const MySitesSidebarUnified = ( { path } ) => { } return ( - + { menuItems.map( ( item, i ) => { const isSelected = item?.url && itemLinkMatches( item.url, path ); From 07ff1d3f161572a916c66b3abbb47e1cab094184 Mon Sep 17 00:00:00 2001 From: cpap Date: Mon, 26 Oct 2020 11:14:46 +0200 Subject: [PATCH 03/13] Removes forward ref from sidebar --- client/layout/sidebar/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/layout/sidebar/index.jsx b/client/layout/sidebar/index.jsx index 7c253d45f05df..0e20e38294179 100644 --- a/client/layout/sidebar/index.jsx +++ b/client/layout/sidebar/index.jsx @@ -10,7 +10,7 @@ import SidebarRegion from './region'; */ import './style.scss'; -const Sidebar = React.forwardRef( ( { children, onClick, className, ...props }, ref ) => { +const Sidebar = ( { children, onClick, className, ...props }, ref ) => { const hasRegions = React.Children.toArray( children ).some( ( el ) => el.type === SidebarRegion ); const finalClassName = classNames( 'sidebar', className, { 'has-regions': hasRegions } ); @@ -26,6 +26,6 @@ const Sidebar = React.forwardRef( ( { children, onClick, className, ...props }, { children } ); -} ); +}; export default Sidebar; From da59daa6799e1305d72b65e5b1584ca08efcd83e Mon Sep 17 00:00:00 2001 From: cpap Date: Mon, 26 Oct 2020 11:15:25 +0200 Subject: [PATCH 04/13] Uses plain JS for sidebar positioning --- client/my-sites/sidebar-unified/index.jsx | 33 ++++----------- client/my-sites/sidebar-unified/utils.jsx | 49 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 25 deletions(-) create mode 100644 client/my-sites/sidebar-unified/utils.jsx diff --git a/client/my-sites/sidebar-unified/index.jsx b/client/my-sites/sidebar-unified/index.jsx index 3e864fb2af643..4c4b0c0903c01 100644 --- a/client/my-sites/sidebar-unified/index.jsx +++ b/client/my-sites/sidebar-unified/index.jsx @@ -10,7 +10,7 @@ /** * External dependencies */ -import React, { useState, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; /** @@ -29,38 +29,21 @@ import 'calypso/state/admin-menu/init'; import Spinner from 'calypso/components/spinner'; import { itemLinkMatches } from '../sidebar/utils'; import { getSidebarIsCollapsed } from 'calypso/state/ui/selectors'; +import { handleScroll } from './utils'; import './style.scss'; -const masterbarHeight = 32; - export const MySitesSidebarUnified = ( { path } ) => { const menuItems = useSiteMenuItems(); const isAllDomainsView = useDomainsViewStatus(); const isRequestingMenu = useSelector( getIsRequestingAdminMenu ); const sidebarIsCollapsed = useSelector( getSidebarIsCollapsed ); - const [ sidebarStyles, setSidebarStyles ] = useState( { top: 0 } ); - const sidebar = React.createRef(); useEffect( () => { - if ( - typeof window !== 'undefined' && - sidebar.current !== 'undefined' && - sidebar.current !== null && - sidebar.current.scrollHeight + masterbarHeight > window.innerHeight - ) { - window.onscroll = () => { - const currentScrollPos = window.pageYOffset; - const maxScroll = sidebar.current.scrollHeight + masterbarHeight - window.innerHeight; - if ( currentScrollPos >= 0 && currentScrollPos < maxScroll ) { - setSidebarStyles( { top: -currentScrollPos } ); - } else { - setSidebarStyles( { top: 'inherit' } ); - } - }; - } - - // Need to cleanup - }, [ sidebar ] ); + window.addEventListener( 'scroll', () => handleScroll() ); + return () => { + window.removeEventListener( 'scroll', () => handleScroll() ); + }; + } ); /** * If there are no menu items and we are currently requesting some, @@ -73,7 +56,7 @@ export const MySitesSidebarUnified = ( { path } ) => { } return ( - + { menuItems.map( ( item, i ) => { const isSelected = item?.url && itemLinkMatches( item.url, path ); diff --git a/client/my-sites/sidebar-unified/utils.jsx b/client/my-sites/sidebar-unified/utils.jsx new file mode 100644 index 0000000000000..983963bbb190f --- /dev/null +++ b/client/my-sites/sidebar-unified/utils.jsx @@ -0,0 +1,49 @@ +const masterbarHeight = document.getElementById( 'header' ).scrollHeight; +const secondaryEl = document.getElementById( 'secondary' ); +let last_known_scroll_position = 0; +let ticking = false; // Used for Scroll event throttling. + +export const handleScroll = () => { + const windowHeight = window?.innerHeight; + const sidebarEl = document.getElementById( 'sidebar' ); + const sidebarElHeight = sidebarEl?.scrollHeight; + const sidebarOffset = -sidebarEl.getBoundingClientRect().top; + + if ( + ! ticking && + typeof window !== 'undefined' && + sidebarEl !== 'undefined' && + sidebarEl !== null && + sidebarElHeight + masterbarHeight > windowHeight // Only run when sidebar & masterbar are taller than window height. + ) { + // Throttle scroll event + window.requestAnimationFrame( function () { + const maxScroll = sidebarElHeight + masterbarHeight - windowHeight; // Max sidebar inner scroll. + const scrollY = -document.body.getBoundingClientRect().top; // Get current scroll position. + const amountScrolled = scrollY - last_known_scroll_position; + + console.log( `Window Height ${ windowHeight }` ); + console.log( `Sidebar Height ${ sidebarElHeight }` ); + console.log( `Current Scroll ${ scrollY }` ); + console.log( `Max Scroll ${ maxScroll }` ); + console.log( sidebarOffset ); + console.log( `Sidebar Top ${ sidebarOffset + scrollY }` ); + console.log( '------------------------------------' ); + + if ( scrollY >= 0 && scrollY <= maxScroll ) { + // We are moving DOWN. Scroll down the sidebar to current scroll position. + sidebarEl.style.top = `-${ scrollY }px`; + } else if ( scrollY > maxScroll && scrollY < last_known_scroll_position ) { + // We are moving UP. Scroll up the sidebar for the amount scrolled. + sidebarEl.style.top = 'inherit'; + sidebarEl.style.bottom = `${ amountScrolled }px`; + } else { + // Stick to sidebar bottom and don't overscroll. + sidebarEl.style.top = 'inherit'; // Default style has bottom: 0. + } + last_known_scroll_position = scrollY; + ticking = false; + } ); + } + ticking = true; +}; From c80027a5c8fa372bbef4d471bbb9524324f079eb Mon Sep 17 00:00:00 2001 From: cpap Date: Mon, 26 Oct 2020 19:53:35 +0200 Subject: [PATCH 05/13] Removes stale sidebar ref --- client/layout/sidebar/index.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/layout/sidebar/index.jsx b/client/layout/sidebar/index.jsx index 0e20e38294179..ff07c88abeb42 100644 --- a/client/layout/sidebar/index.jsx +++ b/client/layout/sidebar/index.jsx @@ -10,13 +10,12 @@ import SidebarRegion from './region'; */ import './style.scss'; -const Sidebar = ( { children, onClick, className, ...props }, ref ) => { +const Sidebar = ( { children, onClick, className, ...props } ) => { const hasRegions = React.Children.toArray( children ).some( ( el ) => el.type === SidebarRegion ); const finalClassName = classNames( 'sidebar', className, { 'has-regions': hasRegions } ); return (
Date: Tue, 27 Oct 2020 01:58:43 +0200 Subject: [PATCH 06/13] Makes sidebar scroll same as wp-admin --- client/my-sites/sidebar-unified/style.scss | 10 ++ client/my-sites/sidebar-unified/utils.jsx | 123 +++++++++++++++------ 2 files changed, 102 insertions(+), 31 deletions(-) diff --git a/client/my-sites/sidebar-unified/style.scss b/client/my-sites/sidebar-unified/style.scss index fa8e865bfab48..8f8d1f67ab962 100644 --- a/client/my-sites/sidebar-unified/style.scss +++ b/client/my-sites/sidebar-unified/style.scss @@ -41,6 +41,7 @@ $font-size: rem( 14px ); .is-nav-unification { // client/layout/sidebar/style.scss .sidebar { + position: relative; background-color: var( --color-sidebar-background ); padding-bottom: 12px; @@ -497,3 +498,12 @@ $font-size: rem( 14px ); } } } + +@media screen and ( max-width: 660px ) { + // client/layout/sidebar/style.scss + .is-nav-unification { + .sidebar { + position: absolute; + } + } +} diff --git a/client/my-sites/sidebar-unified/utils.jsx b/client/my-sites/sidebar-unified/utils.jsx index 983963bbb190f..22c82acb7b3dc 100644 --- a/client/my-sites/sidebar-unified/utils.jsx +++ b/client/my-sites/sidebar-unified/utils.jsx @@ -1,49 +1,110 @@ -const masterbarHeight = document.getElementById( 'header' ).scrollHeight; const secondaryEl = document.getElementById( 'secondary' ); -let last_known_scroll_position = 0; +let lastScrollPosition = 0; // Used for calculating scroll direction. +let sidebarTop = 0; // Current sidebar top position. +let pinnedSidebarTop = true; +let pinnedSidebarBottom = false; let ticking = false; // Used for Scroll event throttling. export const handleScroll = () => { const windowHeight = window?.innerHeight; - const sidebarEl = document.getElementById( 'sidebar' ); - const sidebarElHeight = sidebarEl?.scrollHeight; - const sidebarOffset = -sidebarEl.getBoundingClientRect().top; + const secondaryElHeight = secondaryEl?.scrollHeight; + const masterbarHeight = document.getElementById( 'header' ).getBoundingClientRect().height; if ( ! ticking && typeof window !== 'undefined' && - sidebarEl !== 'undefined' && - sidebarEl !== null && - sidebarElHeight + masterbarHeight > windowHeight // Only run when sidebar & masterbar are taller than window height. + window.innerWidth > 660 && + secondaryEl !== 'undefined' && + secondaryEl !== null && + secondaryElHeight + masterbarHeight > windowHeight // Only run when sidebar & masterbar are taller than window height. ) { // Throttle scroll event window.requestAnimationFrame( function () { - const maxScroll = sidebarElHeight + masterbarHeight - windowHeight; // Max sidebar inner scroll. + const maxScroll = secondaryElHeight + masterbarHeight - windowHeight; // Max sidebar inner scroll. const scrollY = -document.body.getBoundingClientRect().top; // Get current scroll position. - const amountScrolled = scrollY - last_known_scroll_position; - - console.log( `Window Height ${ windowHeight }` ); - console.log( `Sidebar Height ${ sidebarElHeight }` ); - console.log( `Current Scroll ${ scrollY }` ); - console.log( `Max Scroll ${ maxScroll }` ); - console.log( sidebarOffset ); - console.log( `Sidebar Top ${ sidebarOffset + scrollY }` ); - console.log( '------------------------------------' ); - - if ( scrollY >= 0 && scrollY <= maxScroll ) { - // We are moving DOWN. Scroll down the sidebar to current scroll position. - sidebarEl.style.top = `-${ scrollY }px`; - } else if ( scrollY > maxScroll && scrollY < last_known_scroll_position ) { - // We are moving UP. Scroll up the sidebar for the amount scrolled. - sidebarEl.style.top = 'inherit'; - sidebarEl.style.bottom = `${ amountScrolled }px`; - } else { - // Stick to sidebar bottom and don't overscroll. - sidebarEl.style.top = 'inherit'; // Default style has bottom: 0. + + // Check for overscrolling, this happens when swiping up at the top of the document in modern browsers. + if ( scrollY < 0 ) { + // Stick the sidebar to the top. + if ( ! pinnedSidebarTop ) { + pinnedSidebarTop = true; + pinnedSidebarBottom = false; + secondaryEl.style.position = 'fixed'; + secondaryEl.style.top = 0; + secondaryEl.style.bottom = 0; + } + + ticking = false; + return; + } else if ( scrollY + windowHeight > document.body.scrollHeight - 1 ) { + // When overscrolling at the bottom, stick the sidebar to the bottom. + if ( ! pinnedSidebarBottom ) { + pinnedSidebarBottom = true; + pinnedSidebarTop = false; + + secondaryEl.style.position = 'fixed'; + secondaryEl.style.top = 'inherit'; + secondaryEl.style.bottom = 0; + } + + ticking = false; + return; + } + + if ( scrollY > lastScrollPosition ) { + // When a down scroll has been detected. + + if ( pinnedSidebarTop ) { + pinnedSidebarTop = false; + sidebarTop = masterbarHeight; + + if ( scrollY > maxScroll ) { + //In case we have already passed the available scroll of the sidebar, add the current scroll + sidebarTop += scrollY; + } + + secondaryEl.style.position = 'absolute'; + secondaryEl.style.top = `${ sidebarTop }px`; + secondaryEl.style.bottom = 'inherit'; + } else if ( + ! pinnedSidebarBottom && + scrollY + masterbarHeight > maxScroll + secondaryEl.offsetTop + ) { + // Pin it to the bottom. + pinnedSidebarBottom = true; + + secondaryEl.style.position = 'fixed'; + secondaryEl.style.top = 'inherit'; + secondaryEl.style.bottom = 0; + } + } else if ( scrollY < lastScrollPosition ) { + // When a scroll up is detected. + + // If it was pinned to the bottom, unpin and calculate relative scroll. + if ( pinnedSidebarBottom ) { + pinnedSidebarBottom = false; + + // Calculate new offset position. + sidebarTop = scrollY + masterbarHeight - maxScroll; + + secondaryEl.style.position = 'absolute'; + secondaryEl.style.top = `${ sidebarTop }px`; + secondaryEl.style.bottom = 'inherit'; + } else if ( ! pinnedSidebarTop && scrollY + masterbarHeight < sidebarTop ) { + // Pin it to the top. + pinnedSidebarTop = true; + sidebarTop = masterbarHeight; + + secondaryEl.style.position = 'fixed'; + secondaryEl.style.top = `${ sidebarTop }px`; + secondaryEl.style.bottom = 'inherit'; + } } - last_known_scroll_position = scrollY; + + lastScrollPosition = scrollY; + ticking = false; } ); + ticking = true; } - ticking = true; }; From 074f857febbeb70ddd7f6ae325e2a60dfc046413 Mon Sep 17 00:00:00 2001 From: cpap Date: Tue, 27 Oct 2020 02:06:56 +0200 Subject: [PATCH 07/13] Removes sidebar id --- client/my-sites/sidebar-unified/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/my-sites/sidebar-unified/index.jsx b/client/my-sites/sidebar-unified/index.jsx index 4c4b0c0903c01..6763389a39e19 100644 --- a/client/my-sites/sidebar-unified/index.jsx +++ b/client/my-sites/sidebar-unified/index.jsx @@ -56,7 +56,7 @@ export const MySitesSidebarUnified = ( { path } ) => { } return ( - + { menuItems.map( ( item, i ) => { const isSelected = item?.url && itemLinkMatches( item.url, path ); From c81895fd9341b4766f4da0518a1347ba27224e7f Mon Sep 17 00:00:00 2001 From: cpap Date: Wed, 28 Oct 2020 19:45:07 +0200 Subject: [PATCH 08/13] Properly clears the callback --- client/my-sites/sidebar-unified/index.jsx | 6 ++++-- client/my-sites/sidebar-unified/utils.jsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/my-sites/sidebar-unified/index.jsx b/client/my-sites/sidebar-unified/index.jsx index 6763389a39e19..62322fb5eefc7 100644 --- a/client/my-sites/sidebar-unified/index.jsx +++ b/client/my-sites/sidebar-unified/index.jsx @@ -32,6 +32,8 @@ import { getSidebarIsCollapsed } from 'calypso/state/ui/selectors'; import { handleScroll } from './utils'; import './style.scss'; +const scrollCallback = () => handleScroll(); + export const MySitesSidebarUnified = ( { path } ) => { const menuItems = useSiteMenuItems(); const isAllDomainsView = useDomainsViewStatus(); @@ -39,9 +41,9 @@ export const MySitesSidebarUnified = ( { path } ) => { const sidebarIsCollapsed = useSelector( getSidebarIsCollapsed ); useEffect( () => { - window.addEventListener( 'scroll', () => handleScroll() ); + window.addEventListener( 'scroll', scrollCallback ); return () => { - window.removeEventListener( 'scroll', () => handleScroll() ); + window.removeEventListener( 'scroll', scrollCallback ); }; } ); diff --git a/client/my-sites/sidebar-unified/utils.jsx b/client/my-sites/sidebar-unified/utils.jsx index 22c82acb7b3dc..f8a5fecfc7ee6 100644 --- a/client/my-sites/sidebar-unified/utils.jsx +++ b/client/my-sites/sidebar-unified/utils.jsx @@ -11,7 +11,7 @@ export const handleScroll = () => { const masterbarHeight = document.getElementById( 'header' ).getBoundingClientRect().height; if ( - ! ticking && + ! ticking && // Do not run until next requestAnimationFrame typeof window !== 'undefined' && window.innerWidth > 660 && secondaryEl !== 'undefined' && From 58f4db500de38ea2fd0410316d128123e7e841cf Mon Sep 17 00:00:00 2001 From: cpap Date: Wed, 28 Oct 2020 20:22:23 +0200 Subject: [PATCH 09/13] Fixes sidebar detaching --- client/my-sites/sidebar-unified/style.scss | 2 ++ client/my-sites/sidebar-unified/utils.jsx | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/my-sites/sidebar-unified/style.scss b/client/my-sites/sidebar-unified/style.scss index 8f8d1f67ab962..4054c35c09ae5 100644 --- a/client/my-sites/sidebar-unified/style.scss +++ b/client/my-sites/sidebar-unified/style.scss @@ -44,6 +44,8 @@ $font-size: rem( 14px ); position: relative; background-color: var( --color-sidebar-background ); padding-bottom: 12px; + min-height: calc( 100vh - var( --masterbar-height ) ); + box-sizing: border-box; .sidebar__separator { margin: 0 0 11px; diff --git a/client/my-sites/sidebar-unified/utils.jsx b/client/my-sites/sidebar-unified/utils.jsx index f8a5fecfc7ee6..ed772545e66bc 100644 --- a/client/my-sites/sidebar-unified/utils.jsx +++ b/client/my-sites/sidebar-unified/utils.jsx @@ -11,11 +11,11 @@ export const handleScroll = () => { const masterbarHeight = document.getElementById( 'header' ).getBoundingClientRect().height; if ( - ! ticking && // Do not run until next requestAnimationFrame typeof window !== 'undefined' && - window.innerWidth > 660 && secondaryEl !== 'undefined' && secondaryEl !== null && + window.innerWidth > 660 && // Do not run when sidebar is fullscreen + ! ticking && // Do not run until next requestAnimationFrame secondaryElHeight + masterbarHeight > windowHeight // Only run when sidebar & masterbar are taller than window height. ) { // Throttle scroll event From 493b60ce2aff84aac17961ee8d2640e15ed799e3 Mon Sep 17 00:00:00 2001 From: cpap Date: Wed, 28 Oct 2020 21:13:53 +0200 Subject: [PATCH 10/13] Transfer code from sidebar to layout --- client/layout/index.jsx | 14 ++- client/layout/utils.ts | 111 ++++++++++++++++++++++ client/my-sites/sidebar-unified/index.jsx | 12 +-- client/my-sites/sidebar-unified/utils.jsx | 110 --------------------- 4 files changed, 124 insertions(+), 123 deletions(-) delete mode 100644 client/my-sites/sidebar-unified/utils.jsx diff --git a/client/layout/index.jsx b/client/layout/index.jsx index 011c8fd29f241..ecb2567d3597d 100644 --- a/client/layout/index.jsx +++ b/client/layout/index.jsx @@ -50,7 +50,9 @@ import { withCurrentRoute } from 'calypso/components/route'; // goofy import for environment badge, which is SSR'd import 'calypso/components/environment-badge/style.scss'; import './style.scss'; -import { getShouldShowAppBanner } from './utils'; +import { getShouldShowAppBanner, handleScroll } from './utils'; + +const scrollCallback = () => handleScroll(); class Layout extends Component { static propTypes = { @@ -78,6 +80,15 @@ class Layout extends Component { .classList.add( `is-${ this.props.colorSchemePreference }` ); } } + if ( config.isEnabled( 'nav-unification' ) ) { + window.addEventListener( 'scroll', scrollCallback ); + } + } + + componentWillUnmount() { + if ( config.isEnabled( 'nav-unification' ) ) { + window.removeEventListener( 'scroll', scrollCallback ); + } } componentDidUpdate( prevProps ) { @@ -155,7 +166,6 @@ class Layout extends Component { }; const { shouldShowAppBanner } = this.props; - return (
{ + const secondaryEl = document.getElementById( 'secondary' ); + const windowHeight = window?.innerHeight; + const secondaryElHeight = secondaryEl?.scrollHeight; + const masterbarHeight = document.getElementById( 'header' ).getBoundingClientRect().height; + + if ( + typeof window !== 'undefined' && + secondaryEl !== 'undefined' && + secondaryEl !== null && + window.innerWidth > 660 && // Do not run when sidebar is fullscreen + ! ticking && // Do not run until next requestAnimationFrame + secondaryElHeight + masterbarHeight > windowHeight // Only run when sidebar & masterbar are taller than window height. + ) { + // Throttle scroll event + window.requestAnimationFrame( function () { + const maxScroll = secondaryElHeight + masterbarHeight - windowHeight; // Max sidebar inner scroll. + const scrollY = -document.body.getBoundingClientRect().top; // Get current scroll position. + + // Check for overscrolling, this happens when swiping up at the top of the document in modern browsers. + if ( scrollY < 0 ) { + // Stick the sidebar to the top. + if ( ! pinnedSidebarTop ) { + pinnedSidebarTop = true; + pinnedSidebarBottom = false; + secondaryEl.style.position = 'fixed'; + secondaryEl.style.top = 0; + secondaryEl.style.bottom = 0; + } + + ticking = false; + return; + } else if ( scrollY + windowHeight > document.body.scrollHeight - 1 ) { + // When overscrolling at the bottom, stick the sidebar to the bottom. + if ( ! pinnedSidebarBottom ) { + pinnedSidebarBottom = true; + pinnedSidebarTop = false; + + secondaryEl.style.position = 'fixed'; + secondaryEl.style.top = 'inherit'; + secondaryEl.style.bottom = 0; + } + + ticking = false; + return; + } + + if ( scrollY > lastScrollPosition ) { + // When a down scroll has been detected. + + if ( pinnedSidebarTop ) { + pinnedSidebarTop = false; + sidebarTop = masterbarHeight; + + if ( scrollY > maxScroll ) { + //In case we have already passed the available scroll of the sidebar, add the current scroll + sidebarTop += scrollY; + } + + secondaryEl.style.position = 'absolute'; + secondaryEl.style.top = `${ sidebarTop }px`; + secondaryEl.style.bottom = 'inherit'; + } else if ( + ! pinnedSidebarBottom && + scrollY + masterbarHeight > maxScroll + secondaryEl.offsetTop + ) { + // Pin it to the bottom. + pinnedSidebarBottom = true; + + secondaryEl.style.position = 'fixed'; + secondaryEl.style.top = 'inherit'; + secondaryEl.style.bottom = 0; + } + } else if ( scrollY < lastScrollPosition ) { + // When a scroll up is detected. + + // If it was pinned to the bottom, unpin and calculate relative scroll. + if ( pinnedSidebarBottom ) { + pinnedSidebarBottom = false; + + // Calculate new offset position. + sidebarTop = scrollY + masterbarHeight - maxScroll; + + secondaryEl.style.position = 'absolute'; + secondaryEl.style.top = `${ sidebarTop }px`; + secondaryEl.style.bottom = 'inherit'; + } else if ( ! pinnedSidebarTop && scrollY + masterbarHeight < sidebarTop ) { + // Pin it to the top. + pinnedSidebarTop = true; + sidebarTop = masterbarHeight; + + secondaryEl.style.position = 'fixed'; + secondaryEl.style.top = `${ sidebarTop }px`; + secondaryEl.style.bottom = 'inherit'; + } + } + + lastScrollPosition = scrollY; + + ticking = false; + } ); + ticking = true; + } +}; diff --git a/client/my-sites/sidebar-unified/index.jsx b/client/my-sites/sidebar-unified/index.jsx index 62322fb5eefc7..e3f7d3cc01721 100644 --- a/client/my-sites/sidebar-unified/index.jsx +++ b/client/my-sites/sidebar-unified/index.jsx @@ -10,7 +10,7 @@ /** * External dependencies */ -import React, { useEffect } from 'react'; +import React from 'react'; import { useSelector } from 'react-redux'; /** @@ -29,24 +29,14 @@ import 'calypso/state/admin-menu/init'; import Spinner from 'calypso/components/spinner'; import { itemLinkMatches } from '../sidebar/utils'; import { getSidebarIsCollapsed } from 'calypso/state/ui/selectors'; -import { handleScroll } from './utils'; import './style.scss'; -const scrollCallback = () => handleScroll(); - export const MySitesSidebarUnified = ( { path } ) => { const menuItems = useSiteMenuItems(); const isAllDomainsView = useDomainsViewStatus(); const isRequestingMenu = useSelector( getIsRequestingAdminMenu ); const sidebarIsCollapsed = useSelector( getSidebarIsCollapsed ); - useEffect( () => { - window.addEventListener( 'scroll', scrollCallback ); - return () => { - window.removeEventListener( 'scroll', scrollCallback ); - }; - } ); - /** * If there are no menu items and we are currently requesting some, * then show a spinner. The check for menuItems is necessary because diff --git a/client/my-sites/sidebar-unified/utils.jsx b/client/my-sites/sidebar-unified/utils.jsx deleted file mode 100644 index ed772545e66bc..0000000000000 --- a/client/my-sites/sidebar-unified/utils.jsx +++ /dev/null @@ -1,110 +0,0 @@ -const secondaryEl = document.getElementById( 'secondary' ); -let lastScrollPosition = 0; // Used for calculating scroll direction. -let sidebarTop = 0; // Current sidebar top position. -let pinnedSidebarTop = true; -let pinnedSidebarBottom = false; -let ticking = false; // Used for Scroll event throttling. - -export const handleScroll = () => { - const windowHeight = window?.innerHeight; - const secondaryElHeight = secondaryEl?.scrollHeight; - const masterbarHeight = document.getElementById( 'header' ).getBoundingClientRect().height; - - if ( - typeof window !== 'undefined' && - secondaryEl !== 'undefined' && - secondaryEl !== null && - window.innerWidth > 660 && // Do not run when sidebar is fullscreen - ! ticking && // Do not run until next requestAnimationFrame - secondaryElHeight + masterbarHeight > windowHeight // Only run when sidebar & masterbar are taller than window height. - ) { - // Throttle scroll event - window.requestAnimationFrame( function () { - const maxScroll = secondaryElHeight + masterbarHeight - windowHeight; // Max sidebar inner scroll. - const scrollY = -document.body.getBoundingClientRect().top; // Get current scroll position. - - // Check for overscrolling, this happens when swiping up at the top of the document in modern browsers. - if ( scrollY < 0 ) { - // Stick the sidebar to the top. - if ( ! pinnedSidebarTop ) { - pinnedSidebarTop = true; - pinnedSidebarBottom = false; - secondaryEl.style.position = 'fixed'; - secondaryEl.style.top = 0; - secondaryEl.style.bottom = 0; - } - - ticking = false; - return; - } else if ( scrollY + windowHeight > document.body.scrollHeight - 1 ) { - // When overscrolling at the bottom, stick the sidebar to the bottom. - if ( ! pinnedSidebarBottom ) { - pinnedSidebarBottom = true; - pinnedSidebarTop = false; - - secondaryEl.style.position = 'fixed'; - secondaryEl.style.top = 'inherit'; - secondaryEl.style.bottom = 0; - } - - ticking = false; - return; - } - - if ( scrollY > lastScrollPosition ) { - // When a down scroll has been detected. - - if ( pinnedSidebarTop ) { - pinnedSidebarTop = false; - sidebarTop = masterbarHeight; - - if ( scrollY > maxScroll ) { - //In case we have already passed the available scroll of the sidebar, add the current scroll - sidebarTop += scrollY; - } - - secondaryEl.style.position = 'absolute'; - secondaryEl.style.top = `${ sidebarTop }px`; - secondaryEl.style.bottom = 'inherit'; - } else if ( - ! pinnedSidebarBottom && - scrollY + masterbarHeight > maxScroll + secondaryEl.offsetTop - ) { - // Pin it to the bottom. - pinnedSidebarBottom = true; - - secondaryEl.style.position = 'fixed'; - secondaryEl.style.top = 'inherit'; - secondaryEl.style.bottom = 0; - } - } else if ( scrollY < lastScrollPosition ) { - // When a scroll up is detected. - - // If it was pinned to the bottom, unpin and calculate relative scroll. - if ( pinnedSidebarBottom ) { - pinnedSidebarBottom = false; - - // Calculate new offset position. - sidebarTop = scrollY + masterbarHeight - maxScroll; - - secondaryEl.style.position = 'absolute'; - secondaryEl.style.top = `${ sidebarTop }px`; - secondaryEl.style.bottom = 'inherit'; - } else if ( ! pinnedSidebarTop && scrollY + masterbarHeight < sidebarTop ) { - // Pin it to the top. - pinnedSidebarTop = true; - sidebarTop = masterbarHeight; - - secondaryEl.style.position = 'fixed'; - secondaryEl.style.top = `${ sidebarTop }px`; - secondaryEl.style.bottom = 'inherit'; - } - } - - lastScrollPosition = scrollY; - - ticking = false; - } ); - ticking = true; - } -}; From a2f4db683fa9fd3952eeacca83303ded6e4f61db Mon Sep 17 00:00:00 2001 From: cpap Date: Thu, 29 Oct 2020 11:34:31 +0200 Subject: [PATCH 11/13] Fixes TS warnings --- client/layout/utils.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/client/layout/utils.ts b/client/layout/utils.ts index 129d753e3b719..62545eb815130 100644 --- a/client/layout/utils.ts +++ b/client/layout/utils.ts @@ -21,16 +21,19 @@ let pinnedSidebarTop = true; let pinnedSidebarBottom = false; let ticking = false; // Used for Scroll event throttling. -export const handleScroll = (): null => { +export const handleScroll = (): void => { const secondaryEl = document.getElementById( 'secondary' ); const windowHeight = window?.innerHeight; const secondaryElHeight = secondaryEl?.scrollHeight; - const masterbarHeight = document.getElementById( 'header' ).getBoundingClientRect().height; + const masterbarHeight = document.getElementById( 'header' )?.getBoundingClientRect().height; + if ( - typeof window !== 'undefined' && - secondaryEl !== 'undefined' && + typeof window !== undefined && + secondaryEl !== undefined && secondaryEl !== null && + secondaryElHeight !== undefined && + masterbarHeight !== undefined && window.innerWidth > 660 && // Do not run when sidebar is fullscreen ! ticking && // Do not run until next requestAnimationFrame secondaryElHeight + masterbarHeight > windowHeight // Only run when sidebar & masterbar are taller than window height. @@ -47,8 +50,8 @@ export const handleScroll = (): null => { pinnedSidebarTop = true; pinnedSidebarBottom = false; secondaryEl.style.position = 'fixed'; - secondaryEl.style.top = 0; - secondaryEl.style.bottom = 0; + secondaryEl.style.top = '0'; + secondaryEl.style.bottom = '0'; } ticking = false; @@ -61,7 +64,7 @@ export const handleScroll = (): null => { secondaryEl.style.position = 'fixed'; secondaryEl.style.top = 'inherit'; - secondaryEl.style.bottom = 0; + secondaryEl.style.bottom = '0'; } ticking = false; @@ -92,7 +95,7 @@ export const handleScroll = (): null => { secondaryEl.style.position = 'fixed'; secondaryEl.style.top = 'inherit'; - secondaryEl.style.bottom = 0; + secondaryEl.style.bottom = '0'; } } else if ( scrollY < lastScrollPosition ) { // When a scroll up is detected. From c44518d6b469da39ac2c4be463dd2df206738214 Mon Sep 17 00:00:00 2001 From: cpap Date: Thu, 29 Oct 2020 13:58:34 +0200 Subject: [PATCH 12/13] Fixes sidebar height / position on resize --- client/layout/index.jsx | 4 +++- client/layout/utils.ts | 9 +++++---- client/my-sites/sidebar-unified/style.scss | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/client/layout/index.jsx b/client/layout/index.jsx index ecb2567d3597d..56714876291ae 100644 --- a/client/layout/index.jsx +++ b/client/layout/index.jsx @@ -52,7 +52,7 @@ import 'calypso/components/environment-badge/style.scss'; import './style.scss'; import { getShouldShowAppBanner, handleScroll } from './utils'; -const scrollCallback = () => handleScroll(); +const scrollCallback = ( e ) => handleScroll( e ); class Layout extends Component { static propTypes = { @@ -82,12 +82,14 @@ class Layout extends Component { } if ( config.isEnabled( 'nav-unification' ) ) { window.addEventListener( 'scroll', scrollCallback ); + window.addEventListener( 'resize', scrollCallback ); } } componentWillUnmount() { if ( config.isEnabled( 'nav-unification' ) ) { window.removeEventListener( 'scroll', scrollCallback ); + window.removeEventListener( 'resize', scrollCallback ); } } diff --git a/client/layout/utils.ts b/client/layout/utils.ts index 62545eb815130..67bf925b2db85 100644 --- a/client/layout/utils.ts +++ b/client/layout/utils.ts @@ -21,13 +21,14 @@ let pinnedSidebarTop = true; let pinnedSidebarBottom = false; let ticking = false; // Used for Scroll event throttling. -export const handleScroll = (): void => { +export const handleScroll = ( event: any ): void => { + console.log( event ); + const secondaryEl = document.getElementById( 'secondary' ); const windowHeight = window?.innerHeight; const secondaryElHeight = secondaryEl?.scrollHeight; const masterbarHeight = document.getElementById( 'header' )?.getBoundingClientRect().height; - if ( typeof window !== undefined && secondaryEl !== undefined && @@ -36,7 +37,7 @@ export const handleScroll = (): void => { masterbarHeight !== undefined && window.innerWidth > 660 && // Do not run when sidebar is fullscreen ! ticking && // Do not run until next requestAnimationFrame - secondaryElHeight + masterbarHeight > windowHeight // Only run when sidebar & masterbar are taller than window height. + ( secondaryElHeight + masterbarHeight > windowHeight || 'resize' === event.type ) // Only run when sidebar & masterbar are taller than window height OR we have a resize event ) { // Throttle scroll event window.requestAnimationFrame( function () { @@ -71,7 +72,7 @@ export const handleScroll = (): void => { return; } - if ( scrollY > lastScrollPosition ) { + if ( scrollY >= lastScrollPosition ) { // When a down scroll has been detected. if ( pinnedSidebarTop ) { diff --git a/client/my-sites/sidebar-unified/style.scss b/client/my-sites/sidebar-unified/style.scss index 4054c35c09ae5..816617e4e32ad 100644 --- a/client/my-sites/sidebar-unified/style.scss +++ b/client/my-sites/sidebar-unified/style.scss @@ -440,6 +440,7 @@ $font-size: rem( 14px ); .is-nav-unification { &.focus-content .layout__content { padding: 71px 24px 24px; + transition: padding 0.15s ease-in-out; } .sidebar { From eb3d16259f2c0ee1d4d39e510e3a4f7ef63bcbcc Mon Sep 17 00:00:00 2001 From: cpap Date: Thu, 29 Oct 2020 14:53:13 +0200 Subject: [PATCH 13/13] Removes console log --- client/layout/utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/layout/utils.ts b/client/layout/utils.ts index 67bf925b2db85..fd3fd7cd63999 100644 --- a/client/layout/utils.ts +++ b/client/layout/utils.ts @@ -22,8 +22,6 @@ let pinnedSidebarBottom = false; let ticking = false; // Used for Scroll event throttling. export const handleScroll = ( event: any ): void => { - console.log( event ); - const secondaryEl = document.getElementById( 'secondary' ); const windowHeight = window?.innerHeight; const secondaryElHeight = secondaryEl?.scrollHeight;