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:
Seghir Nadir 2024-05-23 00:09:50 +02:00 committed by GitHub
parent 501272c51b
commit 34f8c65f53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 129 additions and 5 deletions

View File

@ -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

View File

@ -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';

View File

@ -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 ];
}

View File

@ -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' }

View File

@ -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;
}
}
}

View File

@ -31,6 +31,12 @@
position: relative;
}
}
.is-large {
.wc-block-checkout__sidebar {
top: 0;
}
}
}
body.wc-lock-selected-block--move {

View File

@ -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' ),

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Comment: This is a follow up to another PR not yet shipped.