Merge pull request #4758 from matrix-org/travis/room-list/sticky

Sticky and collapsing headers for new room list
This commit is contained in:
Travis Ralston 2020-06-17 08:28:38 -06:00 committed by GitHub
commit d2831ffb0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 187 additions and 18 deletions

View file

@ -86,6 +86,43 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
}
};
// TODO: Apply this on resize, init, etc for reliability
private onScroll = (ev: React.MouseEvent<HTMLDivElement>) => {
const list = ev.target as HTMLDivElement;
const rlRect = list.getBoundingClientRect();
const bottom = rlRect.bottom;
const top = rlRect.top;
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 headerStickyWidth = rlRect.width - headerRightMargin;
let gotBottom = false;
for (const sublist of sublists) {
const slRect = sublist.getBoundingClientRect();
const header = sublist.querySelector<HTMLDivElement>(".mx_RoomSublist2_stickable");
if (slRect.top + headerHeight > bottom && !gotBottom) {
header.classList.add("mx_RoomSublist2_headerContainer_sticky");
header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom");
header.style.width = `${headerStickyWidth}px`;
gotBottom = true;
} else if (slRect.top < top) {
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`;
} else {
header.classList.remove("mx_RoomSublist2_headerContainer_sticky");
header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop");
header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom");
header.style.width = `unset`;
}
}
};
private renderHeader(): React.ReactNode {
// TODO: Update when profile info changes
// TODO: Presence
@ -191,7 +228,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
<aside className="mx_LeftPanel2_roomListContainer">
{this.renderHeader()}
{this.renderSearchExplore()}
<div className="mx_LeftPanel2_actualRoomListContainer">
<div className="mx_LeftPanel2_actualRoomListContainer" onScroll={this.onScroll}>
{roomList}
</div>
</aside>

View file

@ -134,7 +134,28 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
this.forceUpdate(); // because the layout doesn't trigger a re-render
};
private onHeaderClick = (ev: React.MouseEvent<HTMLDivElement>) => {
let target = ev.target as HTMLDivElement;
if (!target.classList.contains('mx_RoomSublist2_headerText')) {
// If we don't have the headerText class, the user clicked the span in the headerText.
target = target.parentElement as HTMLDivElement;
}
const possibleSticky = target.parentElement;
const sublist = possibleSticky.parentElement.parentElement;
if (possibleSticky.classList.contains('mx_RoomSublist2_headerContainer_sticky')) {
// is sticky - jump to list
sublist.scrollIntoView({behavior: 'smooth'});
} else {
// on screen - toggle collapse
this.props.layout.isCollapsed = !this.props.layout.isCollapsed;
this.forceUpdate(); // because the layout doesn't trigger an update
}
};
private renderTiles(): React.ReactElement[] {
if (this.props.layout && this.props.layout.isCollapsed) return []; // don't waste time on rendering
const tiles: React.ReactElement[] = [];
if (this.props.rooms) {
@ -250,6 +271,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
);
}
const collapseClasses = classNames({
'mx_RoomSublist2_collapseBtn': true,
'mx_RoomSublist2_collapseBtn_collapsed': this.props.layout && this.props.layout.isCollapsed,
});
const classes = classNames({
'mx_RoomSublist2_headerContainer': true,
'mx_RoomSublist2_headerContainer_withAux': !!addRoomButton,
@ -258,19 +284,23 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
// TODO: a11y (see old component)
return (
<div className={classes}>
<AccessibleButton
inputRef={ref}
tabIndex={tabIndex}
className={"mx_RoomSublist2_headerText"}
role="treeitem"
aria-level={1}
>
<span>{this.props.label}</span>
</AccessibleButton>
{this.renderMenu()}
{addRoomButton}
<div className="mx_RoomSublist2_badgeContainer">
{badge}
<div className='mx_RoomSublist2_stickable'>
<AccessibleButton
inputRef={ref}
tabIndex={tabIndex}
className={"mx_RoomSublist2_headerText"}
role="treeitem"
aria-level={1}
onClick={this.onHeaderClick}
>
<span className={collapseClasses} />
<span>{this.props.label}</span>
</AccessibleButton>
{this.renderMenu()}
{addRoomButton}
<div className="mx_RoomSublist2_badgeContainer">
{badge}
</div>
</div>
</div>
);