diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 746f373e64..c725b02f84 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -138,6 +138,34 @@ limitations under the License. text-overflow: ellipsis; overflow: hidden; white-space: nowrap; + + .mx_RoomSublist2_collapseBtn { + display: inline-block; + position: relative; + + // Default hidden + visibility: hidden; + width: 0; + height: 0; + + &::before { + content: ''; + width: 12px; + height: 12px; + position: absolute; + top: 1px; + left: 1px; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + } + + &.mx_RoomSublist2_collapseBtn_collapsed::before { + mask-image: url('$(res)/img/feather-customised/chevron-right.svg'); + } + } } } @@ -251,6 +279,17 @@ limitations under the License. background-color: $roomlist2-button-bg-color; } } + + .mx_RoomSublist2_headerContainer { + .mx_RoomSublist2_headerText { + .mx_RoomSublist2_collapseBtn { + visibility: visible; + width: 12px; + height: 12px; + margin-right: 4px; + } + } + } } &.mx_RoomSublist2_minimized { diff --git a/res/img/feather-customised/chevron-right.svg b/res/img/feather-customised/chevron-right.svg new file mode 100644 index 0000000000..258de414a1 --- /dev/null +++ b/res/img/feather-customised/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 650828e9b0..ba0ba211b7 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -105,13 +105,11 @@ export default class LeftPanel2 extends React.Component { const header = sublist.querySelector(".mx_RoomSublist2_stickable"); if (slRect.top + headerHeight > bottom && !gotBottom) { - console.log(`${header.textContent} is off the bottom`); 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) { - console.log(`${header.textContent} is off the top`); header.classList.add("mx_RoomSublist2_headerContainer_sticky"); header.classList.add("mx_RoomSublist2_headerContainer_stickyTop"); header.style.width = `${headerStickyWidth}px`; diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 5c23004a4f..2b0c549bd5 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -134,7 +134,28 @@ export default class RoomSublist2 extends React.Component { this.forceUpdate(); // because the layout doesn't trigger a re-render }; + private onHeaderClick = (ev: React.MouseEvent) => { + 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) { @@ -249,6 +270,11 @@ export default class RoomSublist2 extends React.Component { ); } + 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, @@ -264,7 +290,9 @@ export default class RoomSublist2 extends React.Component { className={"mx_RoomSublist2_headerText"} role="treeitem" aria-level={1} + onClick={this.onHeaderClick} > + {this.props.label} {this.renderMenu()} diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index af9d6801a3..f17001f64e 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -21,11 +21,13 @@ const TILE_HEIGHT_PX = 44; interface ISerializedListLayout { numTiles: number; showPreviews: boolean; + collapsed: boolean; } export class ListLayout { private _n = 0; private _previews = false; + private _collapsed = false; constructor(public readonly tagId: TagID) { const serialized = localStorage.getItem(this.key); @@ -34,9 +36,19 @@ export class ListLayout { const parsed = JSON.parse(serialized); this._n = parsed.numTiles; this._previews = parsed.showPreviews; + this._collapsed = parsed.collapsed; } } + public get isCollapsed(): boolean { + return this._collapsed; + } + + public set isCollapsed(v: boolean) { + this._collapsed = v; + this.save(); + } + public get showPreviews(): boolean { return this._previews; } @@ -100,6 +112,7 @@ export class ListLayout { return { numTiles: this.visibleTiles, showPreviews: this.showPreviews, + collapsed: this.isCollapsed, }; } }