only make order summary sticky when it's not longer than view (#47680)
* only sticky summary when it's not longer than view * Add changefile(s) from automation for the following project(s): woocommerce-blocks * fix tests * correctly check for height * switch how to get top * remove from editor --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
parent
501272c51b
commit
34f8c65f53
|
@ -9,7 +9,7 @@ import classNames from 'classnames';
|
|||
*/
|
||||
import { ForwardRefProps } from './types';
|
||||
|
||||
const Sidebar = forwardRef< HTMLInputElement, ForwardRefProps >(
|
||||
const Sidebar = forwardRef< HTMLDivElement, ForwardRefProps >(
|
||||
( { children, className = '' }, ref ): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -8,3 +8,4 @@ export * from './use-typography-props';
|
|||
export * from './use-is-mounted';
|
||||
export * from './use-spoken-message';
|
||||
export * from './use-style-props';
|
||||
export * from './use-observed-viewport';
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState, useRef, useEffect } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Returns a ref, its dimensions, and its visible viewport dimensions. Useful to know if an element should be sticky or not. This hook only runs when an element changes its intersection or dimensions.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* const App = () => {
|
||||
* const [ observedRef, observedElement, viewWindow ] = useObservedViewport();
|
||||
*
|
||||
* return (
|
||||
* <MyElement ref={ observedRef } className={ observedElement.height < viewWindow.height ? 'is-sticky': '' } />
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export function useObservedViewport< T extends HTMLElement >(): [
|
||||
React.Ref< T >,
|
||||
{ height: number; width: number },
|
||||
{ height: number; width: number }
|
||||
] {
|
||||
const [ observedElement, setObservedElement ] = useState( {
|
||||
height: 0,
|
||||
width: 0,
|
||||
} );
|
||||
|
||||
const [ viewWindow, setViewWindow ] = useState( {
|
||||
height: 0,
|
||||
width: 0,
|
||||
} );
|
||||
|
||||
const observedRef = useRef< T >( null );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! observedRef.current ) {
|
||||
return;
|
||||
}
|
||||
const element = observedRef.current;
|
||||
const resizeObserver = new ResizeObserver( ( entries ) => {
|
||||
entries.forEach( ( entry ) => {
|
||||
if ( entry.target === element ) {
|
||||
const { height, width } = entry.contentRect;
|
||||
const elementTop =
|
||||
element.computedStyleMap().get( 'top' )?.toString() ||
|
||||
'0';
|
||||
setObservedElement( {
|
||||
height: height + parseInt( elementTop, 10 ),
|
||||
width,
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
( entries ) => {
|
||||
entries.forEach( ( entry ) => {
|
||||
const { height, width } = entry.boundingClientRect;
|
||||
setObservedElement( { height, width } );
|
||||
if ( entry.target.ownerDocument.defaultView ) {
|
||||
setViewWindow( {
|
||||
height: entry.target.ownerDocument.defaultView
|
||||
?.innerHeight,
|
||||
width: entry.target.ownerDocument.defaultView
|
||||
?.innerWidth,
|
||||
} );
|
||||
}
|
||||
} );
|
||||
},
|
||||
{
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
threshold: 1,
|
||||
}
|
||||
);
|
||||
|
||||
resizeObserver.observe( element );
|
||||
intersectionObserver.observe( element );
|
||||
|
||||
return () => {
|
||||
if ( ! element ) {
|
||||
return;
|
||||
}
|
||||
|
||||
resizeObserver.unobserve( element );
|
||||
intersectionObserver.unobserve( element );
|
||||
};
|
||||
}, [] );
|
||||
return [ observedRef, observedElement, viewWindow ];
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
import classnames from 'classnames';
|
||||
import { Sidebar } from '@woocommerce/base-components/sidebar-layout';
|
||||
import { StoreNoticesContainer } from '@woocommerce/blocks-components';
|
||||
|
||||
import { useObservedViewport } from '@woocommerce/base-hooks';
|
||||
const FrontendBlock = ( {
|
||||
children,
|
||||
className,
|
||||
|
@ -12,9 +12,15 @@ const FrontendBlock = ( {
|
|||
children: JSX.Element;
|
||||
className?: string;
|
||||
} ): JSX.Element => {
|
||||
const [ observedRef, observedElement, viewWindow ] =
|
||||
useObservedViewport< HTMLDivElement >();
|
||||
const isSticky = observedElement.height < viewWindow.height;
|
||||
return (
|
||||
<Sidebar
|
||||
className={ classnames( 'wc-block-checkout__sidebar', className ) }
|
||||
ref={ observedRef }
|
||||
className={ classnames( 'wc-block-checkout__sidebar', className, {
|
||||
'is-sticky': isSticky,
|
||||
} ) }
|
||||
>
|
||||
<StoreNoticesContainer
|
||||
context={ 'woocommerce/checkout-totals-block' }
|
||||
|
|
|
@ -23,8 +23,10 @@
|
|||
|
||||
.is-large {
|
||||
.wc-block-checkout__sidebar {
|
||||
position: sticky;
|
||||
top: $gap-largest;
|
||||
align-self: flex-start;
|
||||
top: $gap-large;
|
||||
&.is-sticky {
|
||||
position: sticky;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,12 @@
|
|||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.is-large {
|
||||
.wc-block-checkout__sidebar {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.wc-lock-selected-block--move {
|
||||
|
|
|
@ -42,6 +42,18 @@ jest.mock( '@wordpress/compose', () => ( {
|
|||
useResizeObserver: jest.fn().mockReturnValue( [ null, { width: 0 } ] ),
|
||||
} ) );
|
||||
|
||||
global.ResizeObserver = jest.fn().mockImplementation( () => ( {
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
} ) );
|
||||
|
||||
global.IntersectionObserver = jest.fn().mockImplementation( () => ( {
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
} ) );
|
||||
|
||||
jest.mock( '@wordpress/element', () => {
|
||||
return {
|
||||
...jest.requireActual( '@wordpress/element' ),
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
Comment: This is a follow up to another PR not yet shipped.
|
||||
|
Loading…
Reference in New Issue