diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index cfb9bc3b6d..b550b2f002 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -16,10 +16,6 @@ limitations under the License. // TODO: Rename to mx_RoomSublist during replacement of old component -// TODO: Just use the 3 selectors we need from this instead of importing it. -// We're going to end up with heavy modifications anyways. -@import "../../../../node_modules/react-resizable/css/styles.css"; - .mx_RoomSublist2 { // The sublist is a column of rows, essentially display: flex; @@ -63,18 +59,83 @@ limitations under the License. } .mx_RoomSublist2_resizeBox { + margin-bottom: 4px; // for the resize handle + position: relative; + // Create another flexbox column for the tiles display: flex; flex-direction: column; overflow: hidden; .mx_RoomSublist2_showMoreButton { - height: 44px; // 1 room tile high cursor: pointer; + font-size: $font-13px; + line-height: $font-18px; + color: $roomtile2-preview-color; + + // This is the same color as the left panel background because it needs + // to occlude the lastmost tile in the list. + background-color: $header-panel-bg-color; + + // Update the render() function for RoomSublist2 if these change + // Update the ListLayout class for minVisibleTiles if these change. + // + // At 24px high and 8px padding on the top this equates to 0.65 of + // a tile due to how the padding calculations work. + height: 24px; + padding-top: 8px; + + // We force this to the bottom so it will overlap rooms as needed. + // We account for the space it takes up (24px) in the code through padding. + position: absolute; + bottom: 4px; // the height of the resize handle + left: 0; + right: 0; // We create a flexbox to cheat at alignment display: flex; align-items: center; + + .mx_RoomSublist2_showMoreButtonChevron { + position: relative; + width: 16px; + height: 16px; + margin-left: 12px; + margin-right: 18px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $roomtile2-preview-color; + } + } + + // Class name comes from the ResizableBox component + // The hover state needs to use the whole sublist, not just the resizable box, + // so that selector is below and one level higher. + .react-resizable-handle { + cursor: ns-resize; + border-radius: 2px; + + // This is positioned directly below the 'show more' button. + position: absolute; + bottom: 0; + left: 0; + right: 0; + + // This is to visually align the bar in the list. Should be 12px from + // either side of the list. We define this after the positioning to + // trick the browser. + margin-left: 4px; + margin-right: 8px; } } + + // The aforementioned selector for the hover state. + &:hover .react-resizable-handle { + opacity: 0.2; + + // Update the render() function for RoomSublist2 if this changes + border: 2px solid $primary-fg-color; + } } diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index cd27156cbd..4ad803d046 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -178,46 +178,61 @@ export default class RoomSublist2 extends React.Component { let content = null; if (tiles.length > 0) { + const layout = this.props.layout; // to shorten calls + // TODO: Lazy list rendering // TODO: Whatever scrolling magic needs to happen here - const layout = this.props.layout; // to shorten calls - const minTilesPx = layout.tilesToPixels(Math.min(tiles.length, layout.minVisibleTiles)); - const maxTilesPx = layout.tilesToPixels(tiles.length); - const tilesPx = layout.tilesToPixels(Math.min(tiles.length, layout.visibleTiles)); + + const nVisible = Math.floor(layout.visibleTiles); + const visibleTiles = tiles.slice(0, nVisible); + + // If we're hiding rooms, show a 'show more' button to the user. This button + // floats above the resize handle, if we have one present + let showMoreButton = null; + if (tiles.length > nVisible) { + // we have a cutoff condition - add the button to show all + const numMissing = tiles.length - visibleTiles.length; + showMoreButton = ( +
+ + {/* set by CSS masking */} + + + {_t("Show %(count)s more", {count: numMissing})} + +
+ ); + } + + // Figure out if we need a handle let handles = ['s']; if (layout.visibleTiles >= tiles.length && tiles.length <= layout.minVisibleTiles) { handles = []; // no handles, we're at a minimum } - // TODO: This might need adjustment, however for now it is fine as a round. - const nVisible = Math.round(layout.visibleTiles); - const visibleTiles = tiles.slice(0, nVisible); + // We have to account for padding so we can accommodate a 'show more' button and + // the resize handle, which are pinned to the bottom of the container. This is the + // easiest way to have a resize handle below the button as otherwise we're writing + // our own resize handling and that doesn't sound fun. + // + // The layout class has some helpers for dealing with padding, as we don't want to + // apply it in all cases. If we apply it in all cases, the resizing feels like it + // goes backwards and can become wildly incorrect (visibleTiles says 18 when there's + // only mathematically 7 possible). - // If we're hiding rooms, show a 'show more' button to the user. This button - // replaces the last visible tile, so will always show 2+ rooms. We do this - // because if it said "show 1 more room" we had might as well show that room - // instead. We also replace the last item so we don't have to adjust our math - // on pixel heights, etc. It's much easier to pretend the button is a tile. - if (tiles.length > nVisible) { - // we have a cutoff condition - add the button to show all + const showMoreHeight = 32; // As defined by CSS + const resizeHandleHeight = 4; // As defined by CSS - // we +1 to account for the room we're about to hide with our 'show more' button - // this results in the button always being 1+, and not needing an i18n `count`. - const numMissing = (tiles.length - visibleTiles.length) + 1; + // The padding is variable though, so figure out what we need padding for. + let padding = 0; + if (showMoreButton) padding += showMoreHeight; + if (handles.length > 0) padding += resizeHandleHeight; + + const minTilesPx = layout.calculateTilesToPixelsMin(tiles.length, layout.minVisibleTiles, padding); + const maxTilesPx = layout.tilesToPixelsWithPadding(tiles.length, padding); + const tilesWithoutPadding = Math.min(tiles.length, layout.visibleTiles); + const tilesPx = layout.calculateTilesToPixelsMin(tiles.length, tilesWithoutPadding, padding); - // TODO: CSS TBD - // TODO: Make this an actual tile - // TODO: This is likely to pop out of the list, consider that. - visibleTiles.splice(visibleTiles.length - 1, 1, ( -
- {_t("Show %(n)s more", {n: numMissing})} -
- )); - } content = ( { className="mx_RoomSublist2_resizeBox" > {visibleTiles} + {showMoreButton} ) } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 21943ef3f6..c819d3c941 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1135,7 +1135,8 @@ "Securely back up your keys to avoid losing them. Learn more.": "Securely back up your keys to avoid losing them. Learn more.", "Not now": "Not now", "Don't ask me again": "Don't ask me again", - "Show %(n)s more": "Show %(n)s more", + "Show %(count)s more|other": "Show %(count)s more", + "Show %(count)s more|one": "Show %(count)s more", "Options": "Options", "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", "%(count)s unread messages including mentions.|one": "1 unread mention.", diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index a6abb7d37a..afcab8e12a 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -52,7 +52,24 @@ export class ListLayout { } public get minVisibleTiles(): number { - return 3; + // the .65 comes from the CSS where the show more button is + // mathematically 65% of a tile when floating. + return 4.65; + } + + public calculateTilesToPixelsMin(maxTiles: number, n: number, possiblePadding: number): number { + // Only apply the padding if we're about to use maxTiles as we need to + // plan for the padding. If we're using n, the padding is already accounted + // for by the resizing stuff. + let padding = 0; + if (maxTiles < n) { + padding = possiblePadding; + } + return this.tilesToPixels(Math.min(maxTiles, n)) + padding; + } + + public tilesToPixelsWithPadding(n: number, padding: number): number { + return this.tilesToPixels(n) + padding; } public tilesToPixels(n: number): number {