diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index 7aefc21a49..9b0d4579f0 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -156,7 +156,7 @@ export default class RoomList2 extends React.Component { const sublist = this.sublistRefs[tagId]; if (sublist) sublist.current.setHeight(height); - // TODO: Check overflow + // TODO: Check overflow (see old impl) // Don't store a height for collapsed sublists if (!this.sublistCollapseStates[tagId]) { @@ -178,6 +178,7 @@ export default class RoomList2 extends React.Component { } private collectSublistRef(tagId: string, ref: React.RefObject) { + // TODO: Is this needed? if (!ref) { delete this.sublistRefs[tagId]; } else { @@ -206,11 +207,9 @@ export default class RoomList2 extends React.Component { const aesthetics: ITagAesthetics = TAG_AESTHETICS[orderedTagId]; if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); - const onAddRoomFn = () => { - if (!aesthetics.onAddRoom) return; - aesthetics.onAddRoom(dis); - }; + const onAddRoomFn = aesthetics.onAddRoom ? () => aesthetics.onAddRoom(dis) : null; components.push( { + private headerButton = createRef(); + public setHeight(size: number) { + // TODO: Do a thing } - public render() { - // TODO: Proper rendering + private hasTiles(): boolean { + return this.numTiles > 0; + } + + private get numTiles(): number { + // TODO: Account for group invites + return (this.props.rooms || []).length; + } + + private onAddRoom = (e) => { + e.stopPropagation(); + if (this.props.onAddRoom) this.props.onAddRoom(); + }; + + private renderTiles(): React.ReactElement[] { + const tiles: React.ReactElement[] = []; + + if (this.props.rooms) { + for (const room of this.props.rooms) { + tiles.push(); + } + } + + return tiles; + } + + private renderHeader(): React.ReactElement { + const notifications = !this.props.isInvite + ? RoomNotifs.aggregateNotificationCount(this.props.rooms) + : {count: 0, highlight: true}; + const notifCount = notifications.count; + const notifHighlight = notifications.highlight; + + // TODO: Title on collapsed + // TODO: Incoming call box + + let chevron = null; + if (this.hasTiles()) { + const chevronClasses = classNames({ + 'mx_RoomSubList_chevron': true, + 'mx_RoomSubList_chevronRight': false, // isCollapsed + 'mx_RoomSubList_chevronDown': true, // !isCollapsed + }); + chevron = (
); + } - const rooms = this.props.rooms.map(r => ( -
{r.name} ({r.roomId})
- )); return ( -
-

{this.props.label}

- {rooms} + + {({onFocus, isActive, ref}) => { + const tabIndex = isActive ? 0 : -1; + + let badge; + if (true) { // !isCollapsed + const badgeClasses = classNames({ + 'mx_RoomSubList_badge': true, + 'mx_RoomSubList_badgeHighlight': notifHighlight, + }); + // Wrap the contents in a div and apply styles to the child div so that the browser default outline works + if (notifCount > 0) { + badge = ( + +
+ {FormattingUtils.formatCount(notifCount)} +
+
+ ); + } else if (this.props.isInvite && this.hasTiles()) { + // Render the `!` badge for invites + badge = ( + +
+ {FormattingUtils.formatCount(this.numTiles)} +
+
+ ); + } + } + + let addRoomButton = null; + if (!!this.props.onAddRoom) { + addRoomButton = ( + + ); + } + + // TODO: a11y + return ( +
+ + {chevron} + {this.props.label} + + {badge} + {addRoomButton} +
+ ); + }} +
+ ); + } + + public render(): React.ReactElement { + // TODO: Proper rendering + // TODO: Error boundary + + const tiles = this.renderTiles(); + + const classes = classNames({ + // TODO: Proper collapse support + 'mx_RoomSubList': true, + 'mx_RoomSubList_hidden': false, // len && isCollapsed + 'mx_RoomSubList_nonEmpty': this.hasTiles(), // len && !isCollapsed + }); + + let content = null; + if (tiles.length > 0) { + // TODO: Lazy list rendering + // TODO: Whatever scrolling magic needs to happen here + content = ( + + {tiles} + + ) + } + + // TODO: onKeyDown support + return ( +
+ {this.renderHeader()} + {content}
); } diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx new file mode 100644 index 0000000000..cb9ce5cf1a --- /dev/null +++ b/src/components/views/rooms/RoomTile2.tsx @@ -0,0 +1,118 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd +Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { createRef } from "react"; +import { Room } from "matrix-js-sdk/src/models/room"; +import classNames from "classnames"; +import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; +import AccessibleButton from "../../views/elements/AccessibleButton"; +import RoomAvatar from "../../views/avatars/RoomAvatar"; + +interface IProps { + room: Room; + + // TODO: Allow faslifying counts (for invites and stuff) + // TODO: Transparency? + // TODO: Incoming call? + // TODO: onClick +} + +interface IState { +} + +// TODO: Finish stub +export default class RoomTile2 extends React.Component { + private roomTile = createRef(); + + // TODO: Custom status + // TODO: Lock icon + // TODO: DM indicator + // TODO: Presence indicator + // TODO: e2e shields + // TODO: Handle changes to room aesthetics (name, join rules, etc) + // TODO: scrollIntoView? + // TODO: hover, badge, etc + // TODO: isSelected for hover effects + // TODO: Context menu + // TODO: a11y + + public render(): React.ReactElement { + // TODO: Collapsed state + // TODO: Invites + // TODO: a11y proper + // TODO: Render more than bare minimum + + const classes = classNames({ + 'mx_RoomTile': true, + // 'mx_RoomTile_selected': this.state.selected, + // 'mx_RoomTile_unread': this.props.unread, + // 'mx_RoomTile_unreadNotify': notifBadges, + // 'mx_RoomTile_highlight': mentionBadges, + // 'mx_RoomTile_invited': isInvite, + // 'mx_RoomTile_menuDisplayed': isMenuDisplayed, + 'mx_RoomTile_noBadges': true, // !badges + // 'mx_RoomTile_transparent': this.props.transparent, + // 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed, + }); + + const avatarClasses = classNames({ + 'mx_RoomTile_avatar': true, + }); + + // TODO: the original RoomTile uses state for the room name. Do we need to? + let name = this.props.room.name; + if (typeof name !== 'string') name = ''; + name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon + + const nameClasses = classNames({ + 'mx_RoomTile_name': true, + 'mx_RoomTile_invite': false, + 'mx_RoomTile_badgeShown': false, + }); + + return ( + + + {({onFocus, isActive, ref}) => + +
+
+ +
+
+
+
+
+ {name} +
+
+
+
+ } +
+
+ ); + } +}