UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

93 lines (92 loc) 5.65 kB
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 })); };