azure-devops-ui
Version:
React components for building web UI in Azure DevOps
93 lines (92 loc) • 5.65 kB
JavaScript
import "../../CommonImports";
import "../../Core/core.css";
import "./SingleLayerMasterPanel.css";
import * as React from "react";
import { useObservable } from '../../Core/Observable';
import { TimerManagement } from '../../Core/TimerManagement';
import { Header, TitleSize } from '../../Header';
import { Intersection } from '../../Intersection';
import { Observer } from '../../Observer';
import { css } from '../../Util';
/**
* Props-controlled implementation of MasterPanel for experiences that don't need the layering of MasterDetailsContext
*/
export const SingleLayerMasterPanel = props => {
const { className, renderContent, renderHeader, renderSearch, showOnSmallScreens = false } = props;
const [isScrolled, setIsScrolled] = useObservable(false);
const containerElement = React.useRef(null);
const headerElement = React.useRef(null);
const hiddenHeaderElement = React.useRef(null);
const contentElement = React.useRef(null);
const initialHeaderHeight = React.useRef(0);
const scrolledHeaderHeight = React.useRef(0);
const timerManagement = React.useRef(new TimerManagement());
const blockingHeaderExpand = React.useRef(false);
const lastBlockTimeoutId = React.useRef();
const onContentScroll = () => {
if (containerElement.current && contentElement.current) {
const scrollTop = containerElement.current.scrollTop;
if (isScrolled.value && !blockingHeaderExpand.current && scrollTop === 0) {
setIsScrolled(false);
}
else if (!isScrolled.value &&
// Detects thrashing: if the large header is bigger than the container AND
// the small header is smaller than the container, we'll cause layout thrashing
// If that check fails, we're good to go and can apply the scrolled style to the header
!(initialHeaderHeight.current + contentElement.current.clientHeight > containerElement.current.clientHeight &&
scrolledHeaderHeight.current + contentElement.current.clientHeight < containerElement.current.clientHeight)) {
blockingHeaderExpand.current = true;
// This is done to prevent thrashing when the panel is only scrolled a little bit; the browser would reset scrollTop to 0, so we added a slight delay to allow users to scroll more smoothly
lastBlockTimeoutId.current && timerManagement.current.clearTimeout(lastBlockTimeoutId.current);
lastBlockTimeoutId.current = timerManagement.current.setTimeout(() => {
blockingHeaderExpand.current = false;
onContentScroll(); // Repeat process to verify
}, 100);
setIsScrolled(true);
}
}
};
const removeIds = (element) => {
if (element) {
element.id = "";
if (element.hasChildNodes()) {
const children = element.children;
for (let i = 0; i < children.length; i++) {
removeIds(children.item(i));
}
}
}
};
React.useEffect(() => {
if (headerElement.current) {
initialHeaderHeight.current = headerElement.current.clientHeight;
}
if (hiddenHeaderElement.current) {
// fix an a11y issue caused by the hidden header causing duplicate ids
removeIds(hiddenHeaderElement.current);
scrolledHeaderHeight.current = hiddenHeaderElement.current.clientHeight;
}
document.addEventListener("resize", onContentScroll);
return () => {
document.removeEventListener("resize", onContentScroll);
timerManagement.current.dispose();
};
}, []);
return (React.createElement(Intersection, null,
React.createElement("div", { className: css(className, "bolt-master-panel flex-column flex-noshrink scroll-auto", showOnSmallScreens && "show-on-small-screens"), onScroll: onContentScroll, ref: containerElement },
renderHeader && (React.createElement(React.Fragment, null,
React.createElement(Observer, { isScrolled: isScrolled }, (observerProps) => (React.createElement("div", { className: css("bolt-master-panel-header", renderSearch && "has-search", observerProps.isScrolled && "content-scrolled"), ref: headerElement }, renderHeader()))),
React.createElement("div", { "aria-hidden": "true", className: css("bolt-master-panel-header hide", renderSearch && "has-search", "content-scrolled"), ref: hiddenHeaderElement }, renderHeader()))),
React.createElement("div", { className: "bolt-master-panel-content flex-column", ref: contentElement },
renderSearch && React.createElement("div", { className: "bolt-master-panel-search" }, renderSearch()),
renderContent && renderContent()))));
};
export const SingleLayerMasterPanelHeader = props => {
const { title, titleAriaLevel, subTitle, onBackButtonClick } = props;
return (React.createElement(Header, { backButtonProps: onBackButtonClick
? {
onClick: onBackButtonClick,
subtle: true
}
: undefined, className: "bolt-master-panel-standard-header", description: subTitle, descriptionClassName: "bolt-master-panel-header-subtitle bolt-master-panel-header-secondary", title: title, titleAriaLevel: titleAriaLevel, titleClassName: "bolt-master-panel-header-title", titleSize: TitleSize.Large }));
};