diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index eaa22a3efa..b3f7fcc8ee 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -122,16 +122,19 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations } .mx_LeftPanel2_roomListWrapper { + // Create a flexbox to ensure the containing items cause appropriate overflow. display: flex; + flex-grow: 1; overflow: hidden; min-height: 0; + margin-top: 12px; // so we're not up against the search/filter - &.stickyBottom { + &.mx_LeftPanel2_roomListWrapper_stickyBottom { padding-bottom: 32px; } - &.stickyTop { + &.mx_LeftPanel2_roomListWrapper_stickyTop { padding-top: 32px; } } diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 30389a4ec5..1d13f25b8f 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -24,10 +24,6 @@ limitations under the License. margin-left: 8px; width: 100%; - &:first-child { - margin-top: 12px; // so we're not up against the search/filter - } - .mx_RoomSublist2_headerContainer { // Create a flexbox to make alignment easy display: flex; @@ -49,10 +45,15 @@ limitations under the License. padding-bottom: 8px; height: 24px; + // Hide the header container if the contained element is stickied. + // We don't use display:none as that causes the header to go away too. + &.mx_RoomSublist2_headerContainer_hasSticky { + height: 0; + } + .mx_RoomSublist2_stickable { flex: 1; max-width: 100%; - z-index: 2; // Prioritize headers in the visible list over sticky ones // Create a flexbox to make ordering easy display: flex; @@ -64,7 +65,6 @@ limitations under the License. // when sticky scrolls instead of collapses the list. &.mx_RoomSublist2_headerContainer_sticky { position: fixed; - z-index: 1; // over top of other elements, but still under the ones in the visible list height: 32px; // to match the header container // width set by JS } diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 0875914b78..316b590aea 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -124,42 +124,42 @@ export default class LeftPanel2 extends React.Component { } private doStickyHeaders(list: HTMLDivElement) { - const rlRect = list.getBoundingClientRect(); - const bottom = rlRect.bottom; - const top = rlRect.top; + const topEdge = list.scrollTop; + const bottomEdge = list.offsetHeight + list.scrollTop; const sublists = list.querySelectorAll(".mx_RoomSublist2"); - const headerRightMargin = 24; // calculated from margins and widths to align with non-sticky tiles - const headerStickyWidth = rlRect.width - headerRightMargin; + const headerRightMargin = 16; // calculated from margins and widths to align with non-sticky tiles + const headerStickyWidth = list.clientWidth - headerRightMargin; - let gotBottom = false; let lastTopHeader; + let firstBottomHeader; for (const sublist of sublists) { - const slRect = sublist.getBoundingClientRect(); - const header = sublist.querySelector(".mx_RoomSublist2_stickable"); + const headerContainer = header.parentElement; // .mx_RoomSublist2_headerContainer header.style.removeProperty("display"); // always clear display:none first + headerContainer.classList.remove("mx_RoomSublist2_headerContainer_hasSticky"); - if (slRect.top + HEADER_HEIGHT > bottom && !gotBottom) { - header.classList.add("mx_RoomSublist2_headerContainer_sticky"); - header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom"); - header.style.width = `${headerStickyWidth}px`; - header.style.removeProperty("top"); - gotBottom = true; - } else if (((slRect.top - (HEADER_HEIGHT * 0.6) + HEADER_HEIGHT) < top) || sublist === sublists[0]) { - // the header should become sticky once it is 60% or less out of view at the top. - // We also add HEADER_HEIGHT because the sticky header is put above the scrollable area, - // into the padding of .mx_LeftPanel2_roomListWrapper, - // by subtracting HEADER_HEIGHT from the top below. - // We also always try to make the first sublist header sticky. + // When an element is <=40% off screen, make it take over + const offScreenFactor = 0.4; + const isOffTop = (sublist.offsetTop + (offScreenFactor * HEADER_HEIGHT)) <= topEdge; + const isOffBottom = (sublist.offsetTop + (offScreenFactor * HEADER_HEIGHT)) >= bottomEdge; + + if (isOffTop || sublist === sublists[0]) { header.classList.add("mx_RoomSublist2_headerContainer_sticky"); header.classList.add("mx_RoomSublist2_headerContainer_stickyTop"); + headerContainer.classList.add("mx_RoomSublist2_headerContainer_hasSticky"); header.style.width = `${headerStickyWidth}px`; - header.style.top = `${rlRect.top - HEADER_HEIGHT}px`; + header.style.top = `${list.parentElement.offsetTop}px`; if (lastTopHeader) { lastTopHeader.style.display = "none"; } lastTopHeader = header; + } else if (isOffBottom && !firstBottomHeader) { + header.classList.add("mx_RoomSublist2_headerContainer_sticky"); + header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom"); + headerContainer.classList.add("mx_RoomSublist2_headerContainer_hasSticky"); + header.style.width = `${headerStickyWidth}px`; + firstBottomHeader = header; } else { header.classList.remove("mx_RoomSublist2_headerContainer_sticky"); header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop"); @@ -171,22 +171,16 @@ export default class LeftPanel2 extends React.Component { // add appropriate sticky classes to wrapper so it has // the necessary top/bottom padding to put the sticky header in - const listWrapper = list.parentElement; - if (gotBottom) { - listWrapper.classList.add("stickyBottom"); - } else { - listWrapper.classList.remove("stickyBottom"); - } + const listWrapper = list.parentElement; // .mx_LeftPanel2_roomListWrapper if (lastTopHeader) { - listWrapper.classList.add("stickyTop"); + listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyTop"); } else { - listWrapper.classList.remove("stickyTop"); + listWrapper.classList.remove("mx_LeftPanel2_roomListWrapper_stickyTop"); } - - // ensure scroll doesn't go above the gap left by the header of - // the first sublist always being sticky if no other header is sticky - if (list.scrollTop < HEADER_HEIGHT) { - list.scrollTop = HEADER_HEIGHT; + if (firstBottomHeader) { + listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyBottom"); + } else { + listWrapper.classList.remove("mx_LeftPanel2_roomListWrapper_stickyBottom"); } }