diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index a73658d916..eaa22a3efa 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -121,6 +121,21 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations } } + .mx_LeftPanel2_roomListWrapper { + display: flex; + flex-grow: 1; + overflow: hidden; + min-height: 0; + + &.stickyBottom { + padding-bottom: 32px; + } + + &.stickyTop { + padding-top: 32px; + } + } + .mx_LeftPanel2_actualRoomListContainer { flex-grow: 1; // fill the available space overflow-y: auto; diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index d08bc09031..194e615099 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -54,9 +54,6 @@ limitations under the License. max-width: 100%; z-index: 2; // Prioritize headers in the visible list over sticky ones - // Set the same background color as the room list for sticky headers - background-color: $roomlist2-bg-color; - // Create a flexbox to make ordering easy display: flex; align-items: center; diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index e67a85db25..e92b0fa9ae 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -21,6 +21,7 @@ import classNames from "classnames"; import dis from "../../dispatcher/dispatcher"; import { _t } from "../../languageHandler"; import RoomList2 from "../views/rooms/RoomList2"; +import { HEADER_HEIGHT } from "../views/rooms/RoomSublist2"; import { Action } from "../../dispatcher/actions"; import UserMenu from "./UserMenu"; import RoomSearch from "./RoomSearch"; @@ -135,7 +136,6 @@ export default class LeftPanel2 extends React.Component { const bottom = rlRect.bottom; const top = rlRect.top; const sublists = list.querySelectorAll(".mx_RoomSublist2"); - const headerHeight = 32; // Note: must match the CSS! const headerRightMargin = 24; // calculated from margins and widths to align with non-sticky tiles const headerStickyWidth = rlRect.width - headerRightMargin; @@ -148,17 +148,22 @@ export default class LeftPanel2 extends React.Component { const header = sublist.querySelector(".mx_RoomSublist2_stickable"); header.style.removeProperty("display"); // always clear display:none first - if (slRect.top + headerHeight > bottom && !gotBottom) { + 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 - (headerHeight / 3)) < top) { + } 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. header.classList.add("mx_RoomSublist2_headerContainer_sticky"); header.classList.add("mx_RoomSublist2_headerContainer_stickyTop"); header.style.width = `${headerStickyWidth}px`; - header.style.top = `${rlRect.top}px`; + header.style.top = `${rlRect.top - HEADER_HEIGHT}px`; if (lastTopHeader) { lastTopHeader.style.display = "none"; } @@ -171,6 +176,26 @@ export default class LeftPanel2 extends React.Component { header.style.removeProperty("top"); } } + + // 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"); + } + if (lastTopHeader) { + listWrapper.classList.add("stickyTop"); + } else { + listWrapper.classList.remove("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; + } } // TODO: Improve header reliability: https://github.com/vector-im/riot-web/issues/14232 @@ -324,15 +349,17 @@ export default class LeftPanel2 extends React.Component { diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index a45631553b..ce1b8123c5 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -58,6 +58,7 @@ import {ActionPayload} from "../../../dispatcher/payloads"; const SHOW_N_BUTTON_HEIGHT = 32; // As defined by CSS const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS +export const HEADER_HEIGHT = 32; // As defined by CSS const MAX_PADDING_HEIGHT = SHOW_N_BUTTON_HEIGHT + RESIZE_HANDLE_HEIGHT; @@ -259,7 +260,11 @@ export default class RoomSublist2 extends React.Component { const possibleSticky = target.parentElement; const sublist = possibleSticky.parentElement.parentElement; - if (possibleSticky.classList.contains('mx_RoomSublist2_headerContainer_sticky')) { + const list = sublist.parentElement.parentElement; + // the scrollTop is capped at the height of the header in LeftPanel2 + const isAtTop = list.scrollTop <= HEADER_HEIGHT; + const isSticky = possibleSticky.classList.contains('mx_RoomSublist2_headerContainer_sticky'); + if (isSticky && !isAtTop) { // is sticky - jump to list sublist.scrollIntoView({behavior: 'smooth'}); } else {