Merge pull request #4912 from matrix-org/bwindels/bettersticky

Add wrapper to room list so sticky headers don't need a background
This commit is contained in:
Bruno Windels 2020-07-08 13:34:01 +00:00 committed by GitHub
commit 989e4a9ceb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 17 deletions

View file

@ -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 { .mx_LeftPanel2_actualRoomListContainer {
flex-grow: 1; // fill the available space flex-grow: 1; // fill the available space
overflow-y: auto; overflow-y: auto;

View file

@ -54,9 +54,6 @@ limitations under the License.
max-width: 100%; max-width: 100%;
z-index: 2; // Prioritize headers in the visible list over sticky ones 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 // Create a flexbox to make ordering easy
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -21,6 +21,7 @@ import classNames from "classnames";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import RoomList2 from "../views/rooms/RoomList2"; import RoomList2 from "../views/rooms/RoomList2";
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist2";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import UserMenu from "./UserMenu"; import UserMenu from "./UserMenu";
import RoomSearch from "./RoomSearch"; import RoomSearch from "./RoomSearch";
@ -135,7 +136,6 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
const bottom = rlRect.bottom; const bottom = rlRect.bottom;
const top = rlRect.top; const top = rlRect.top;
const sublists = list.querySelectorAll<HTMLDivElement>(".mx_RoomSublist2"); const sublists = list.querySelectorAll<HTMLDivElement>(".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 headerRightMargin = 24; // calculated from margins and widths to align with non-sticky tiles
const headerStickyWidth = rlRect.width - headerRightMargin; const headerStickyWidth = rlRect.width - headerRightMargin;
@ -148,17 +148,22 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
const header = sublist.querySelector<HTMLDivElement>(".mx_RoomSublist2_stickable"); const header = sublist.querySelector<HTMLDivElement>(".mx_RoomSublist2_stickable");
header.style.removeProperty("display"); // always clear display:none first 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_sticky");
header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom"); header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom");
header.style.width = `${headerStickyWidth}px`; header.style.width = `${headerStickyWidth}px`;
header.style.removeProperty("top"); header.style.removeProperty("top");
gotBottom = true; 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_sticky");
header.classList.add("mx_RoomSublist2_headerContainer_stickyTop"); header.classList.add("mx_RoomSublist2_headerContainer_stickyTop");
header.style.width = `${headerStickyWidth}px`; header.style.width = `${headerStickyWidth}px`;
header.style.top = `${rlRect.top}px`; header.style.top = `${rlRect.top - HEADER_HEIGHT}px`;
if (lastTopHeader) { if (lastTopHeader) {
lastTopHeader.style.display = "none"; lastTopHeader.style.display = "none";
} }
@ -171,6 +176,26 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
header.style.removeProperty("top"); 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 // TODO: Improve header reliability: https://github.com/vector-im/riot-web/issues/14232
@ -324,6 +349,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
<aside className="mx_LeftPanel2_roomListContainer"> <aside className="mx_LeftPanel2_roomListContainer">
{this.renderHeader()} {this.renderHeader()}
{this.renderSearchExplore()} {this.renderSearchExplore()}
<div className="mx_LeftPanel2_roomListWrapper">
<div <div
className={roomListClasses} className={roomListClasses}
onScroll={this.onScroll} onScroll={this.onScroll}
@ -334,6 +360,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
> >
{roomList} {roomList}
</div> </div>
</div>
</aside> </aside>
</div> </div>
); );

View file

@ -58,6 +58,7 @@ import {ActionPayload} from "../../../dispatcher/payloads";
const SHOW_N_BUTTON_HEIGHT = 32; // As defined by CSS const SHOW_N_BUTTON_HEIGHT = 32; // As defined by CSS
const RESIZE_HANDLE_HEIGHT = 4; // 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; const MAX_PADDING_HEIGHT = SHOW_N_BUTTON_HEIGHT + RESIZE_HANDLE_HEIGHT;
@ -259,7 +260,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
const possibleSticky = target.parentElement; const possibleSticky = target.parentElement;
const sublist = possibleSticky.parentElement.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 // is sticky - jump to list
sublist.scrollIntoView({behavior: 'smooth'}); sublist.scrollIntoView({behavior: 'smooth'});
} else { } else {