Address sticky Header review feedback
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -1743,7 +1743,6 @@ export interface HeaderOwnProps {
|
||||
description?: string;
|
||||
// (undocumented)
|
||||
metadata?: HeaderMetadataItem[];
|
||||
// (undocumented)
|
||||
sticky?: boolean;
|
||||
// (undocumented)
|
||||
tabs?: HeaderNavTabItem[];
|
||||
|
||||
@@ -24,7 +24,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { BUIProvider } from '../../provider';
|
||||
import { Button, ButtonIcon, MenuTrigger, Menu, MenuItem } from '../../';
|
||||
import { RiMore2Line } from '@remixicon/react';
|
||||
import { Container } from '../Container/Container';
|
||||
import { Container } from '../Container';
|
||||
|
||||
const meta = preview.meta({
|
||||
title: 'Backstage UI/Header',
|
||||
@@ -415,8 +415,8 @@ export const NonSticky = meta.story({
|
||||
<Container pb="3">
|
||||
{Array.from({ length: 60 }, (_, i) => (
|
||||
<p key={i} style={{ marginBottom: '16px' }}>
|
||||
Scroll down to see the title bar stick to the top while the tags,
|
||||
description, and metadata scroll away. Line {i + 1}.
|
||||
Scroll down to see the entire header scroll away with the rest of
|
||||
the page content. Line {i + 1}.
|
||||
</p>
|
||||
))}
|
||||
</Container>
|
||||
|
||||
@@ -43,6 +43,16 @@ const getScrollParent = (element: HTMLElement | null): Element | null => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const isStickySentinelOutOfView = (
|
||||
sentinel: HTMLElement,
|
||||
root: Element | null,
|
||||
) => {
|
||||
const sentinelRect = sentinel.getBoundingClientRect();
|
||||
const rootTop = root ? root.getBoundingClientRect().top : 0;
|
||||
|
||||
return sentinelRect.bottom <= rootTop;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses inline Markdown links in a string and returns an array of React nodes.
|
||||
* URLs are sanitized via `@braintree/sanitize-url`; unsafe URLs are rendered as
|
||||
@@ -103,11 +113,31 @@ export const Header = (props: HeaderProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const root = getScrollParent(sentinel);
|
||||
|
||||
if (typeof IntersectionObserver === 'undefined') {
|
||||
const updateStuckState = () => {
|
||||
setIsStuck(isStickySentinelOutOfView(sentinel, root));
|
||||
};
|
||||
const scrollTarget = root ?? window;
|
||||
|
||||
updateStuckState();
|
||||
scrollTarget.addEventListener('scroll', updateStuckState, {
|
||||
passive: true,
|
||||
});
|
||||
window.addEventListener('resize', updateStuckState);
|
||||
|
||||
return () => {
|
||||
scrollTarget.removeEventListener('scroll', updateStuckState);
|
||||
window.removeEventListener('resize', updateStuckState);
|
||||
};
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
setIsStuck(!entry.isIntersecting);
|
||||
},
|
||||
{ root: getScrollParent(sentinel), threshold: 0 },
|
||||
{ root, threshold: 0 },
|
||||
);
|
||||
|
||||
observer.observe(sentinel);
|
||||
|
||||
@@ -116,6 +116,10 @@ export interface HeaderOwnProps {
|
||||
tags?: HeaderTag[];
|
||||
metadata?: HeaderMetadataItem[];
|
||||
className?: string;
|
||||
/**
|
||||
* Makes the title-and-actions row stick to the top of its nearest scroll
|
||||
* container while the rest of the header content scrolls away.
|
||||
*/
|
||||
sticky?: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user