Simple structuring of the room list itself
This covers the larger parts of the design, but doesn't deal with the nuances of hover states, badge sizing, etc.pull/21833/head
parent
4c1bc50649
commit
0c15b2bdb6
|
@ -16,7 +16,27 @@ limitations under the License.
|
|||
|
||||
@import "../../../../node_modules/react-resizable/css/styles.css";
|
||||
|
||||
.mx_RoomList2 .mx_RoomSubList2_labelContainer {
|
||||
z-index: 12;
|
||||
background-color: purple;
|
||||
.mx_RoomSublist2 {
|
||||
// The sublist is a column of rows, essentially
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin-left: 8px;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.mx_RoomSublist2_headerContainer {
|
||||
text-transform: uppercase;
|
||||
opacity: 0.5;
|
||||
line-height: $font-16px;
|
||||
font-size: $font-12px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_resizeBox {
|
||||
// Create another flexbox column for the tiles
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,5 +14,81 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Note: the room tile expects to be in a flexbox column container
|
||||
.mx_RoomTile2 {
|
||||
width: 100%;
|
||||
padding-bottom: 12px;
|
||||
|
||||
// The tile is also a flexbox row itself
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.mx_RoomTile2_avatarContainer {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomTile2_nameContainer {
|
||||
// Create a new column layout flexbox for the name parts
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.mx_RoomTile2_name,
|
||||
.mx_RoomTile2_messagePreview {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
// TODO: Ellipsis on the name and preview
|
||||
|
||||
.mx_RoomTile2_name {
|
||||
font-weight: 600;
|
||||
font-size: $font-14px;
|
||||
line-height: $font-19px;
|
||||
}
|
||||
|
||||
.mx_RoomTile2_messagePreview {
|
||||
font-size: $font-13px;
|
||||
line-height: $font-18px;
|
||||
color: $roomtile2-preview-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile2_badgeContainer {
|
||||
flex-grow: 1;
|
||||
|
||||
// Create another flexbox row because it's super easy to position the badge at
|
||||
// the end this way.
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
.mx_RoomTile2_badge {
|
||||
background-color: $roomtile2-badge-color;
|
||||
|
||||
&:not(.mx_RoomTile2_badgeEmpty) {
|
||||
border-radius: 16px;
|
||||
font-size: $font-10px;
|
||||
line-height: $font-14px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-right: 14px;
|
||||
color: #fff; // TODO: Variable
|
||||
|
||||
// TODO: Confirm padding on counted badges
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
&.mx_RoomTile2_badgeEmpty {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 6px;
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
&.mx_RoomTile2_badgeHighlight {
|
||||
// TODO: Use a more specific variable
|
||||
background-color: $warning-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,6 +172,11 @@ $header-divider-color: #91A1C0;
|
|||
|
||||
// ********************
|
||||
|
||||
// TODO: Update variables for new room list
|
||||
// TODO: Dark theme
|
||||
$roomtile2-preview-color: #9e9e9e;
|
||||
$roomtile2-badge-color: #61708b;
|
||||
|
||||
$roomtile-name-color: #61708b;
|
||||
$roomtile-badge-fg-color: $accent-fg-color;
|
||||
$roomtile-selected-color: #212121;
|
||||
|
|
|
@ -96,7 +96,7 @@ const TAG_AESTHETICS: {
|
|||
defaultHidden: false,
|
||||
},
|
||||
[DefaultTagID.DM]: {
|
||||
sectionLabel: _td("Direct Messages"),
|
||||
sectionLabel: _td("People"),
|
||||
isInvite: false,
|
||||
defaultHidden: false,
|
||||
addRoomLabel: _td("Start chat"),
|
||||
|
@ -200,6 +200,7 @@ export default class RoomList2 extends React.Component<IProps, IState> {
|
|||
addRoomLabel={aesthetics.addRoomLabel}
|
||||
isInvite={aesthetics.isInvite}
|
||||
layout={this.state.layouts.get(orderedTagId)}
|
||||
showMessagePreviews={orderedTagId === DefaultTagID.DM}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,15 +20,13 @@ import * as React from "react";
|
|||
import { createRef } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import classNames from 'classnames';
|
||||
import * as RoomNotifs from '../../../RoomNotifs';
|
||||
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import AccessibleButton from "../../views/elements/AccessibleButton";
|
||||
import AccessibleTooltipButton from "../../views/elements/AccessibleTooltipButton";
|
||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
import RoomTile2 from "./RoomTile2";
|
||||
import { ResizableBox, ResizeCallbackData } from "react-resizable";
|
||||
import { ListLayout } from "../../../stores/room-list/ListLayout";
|
||||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||
|
||||
/*******************************************************************
|
||||
* CAUTION *
|
||||
|
@ -43,6 +41,7 @@ interface IProps {
|
|||
rooms?: Room[];
|
||||
startAsHidden: boolean;
|
||||
label: string;
|
||||
showMessagePreviews: boolean;
|
||||
onAddRoom?: () => void;
|
||||
addRoomLabel: string;
|
||||
isInvite: boolean;
|
||||
|
@ -93,7 +92,13 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
|
||||
if (this.props.rooms) {
|
||||
for (const room of this.props.rooms) {
|
||||
tiles.push(<RoomTile2 room={room} key={`room-${room.roomId}`}/>);
|
||||
tiles.push(
|
||||
<RoomTile2
|
||||
room={room}
|
||||
key={`room-${room.roomId}`}
|
||||
showMessagePreview={this.props.showMessagePreviews}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,25 +106,16 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
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: Handle badge count
|
||||
// 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_RoomSublist2_chevron': true,
|
||||
'mx_RoomSublist2_chevronRight': false, // isCollapsed
|
||||
'mx_RoomSublist2_chevronDown': true, // !isCollapsed
|
||||
});
|
||||
chevron = (<div className={chevronClasses}/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<RovingTabIndexWrapper inputRef={this.headerButton}>
|
||||
{({onFocus, isActive, ref}) => {
|
||||
|
@ -127,52 +123,55 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
const tabIndex = isActive ? 0 : -1;
|
||||
|
||||
// TODO: Collapsed state
|
||||
let badge;
|
||||
if (true) { // !isCollapsed
|
||||
const badgeClasses = classNames({
|
||||
'mx_RoomSublist2_badge': true,
|
||||
'mx_RoomSublist2_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 = (
|
||||
<AccessibleButton
|
||||
tabIndex={tabIndex}
|
||||
className={badgeClasses}
|
||||
aria-label={_t("Jump to first unread room.")}
|
||||
>
|
||||
<div>
|
||||
{FormattingUtils.formatCount(notifCount)}
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
} else if (this.props.isInvite && this.hasTiles()) {
|
||||
// Render the `!` badge for invites
|
||||
badge = (
|
||||
<AccessibleButton
|
||||
tabIndex={tabIndex}
|
||||
className={badgeClasses}
|
||||
aria-label={_t("Jump to first invite.")}
|
||||
>
|
||||
<div>
|
||||
{FormattingUtils.formatCount(this.numTiles)}
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: Handle badge count
|
||||
// let badge;
|
||||
// if (true) { // !isCollapsed
|
||||
// const showCount = localStorage.getItem("mx_rls_count") || notifHighlight;
|
||||
// const badgeClasses = classNames({
|
||||
// 'mx_RoomSublist2_badge': true,
|
||||
// 'mx_RoomSublist2_badgeHighlight': notifHighlight,
|
||||
// 'mx_RoomSublist2_badgeEmpty': !showCount,
|
||||
// });
|
||||
// // Wrap the contents in a div and apply styles to the child div so that the browser default outline works
|
||||
// if (notifCount > 0) {
|
||||
// const count = <div>{FormattingUtils.formatCount(notifCount)}</div>;
|
||||
// badge = (
|
||||
// <AccessibleButton
|
||||
// tabIndex={tabIndex}
|
||||
// className={badgeClasses}
|
||||
// aria-label={_t("Jump to first unread room.")}
|
||||
// >
|
||||
// {showCount ? count : null}
|
||||
// </AccessibleButton>
|
||||
// );
|
||||
// } else if (this.props.isInvite && this.hasTiles()) {
|
||||
// // Render the `!` badge for invites
|
||||
// badge = (
|
||||
// <AccessibleButton
|
||||
// tabIndex={tabIndex}
|
||||
// className={badgeClasses}
|
||||
// aria-label={_t("Jump to first invite.")}
|
||||
// >
|
||||
// <div>
|
||||
// {FormattingUtils.formatCount(this.numTiles)}
|
||||
// </div>
|
||||
// </AccessibleButton>
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
let addRoomButton = null;
|
||||
if (!!this.props.onAddRoom) {
|
||||
addRoomButton = (
|
||||
<AccessibleTooltipButton
|
||||
tabIndex={tabIndex}
|
||||
onClick={this.onAddRoom}
|
||||
className="mx_RoomSublist2_addButton"
|
||||
title={this.props.addRoomLabel || _t("Add room")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// TODO: Aux button
|
||||
// let addRoomButton = null;
|
||||
// if (!!this.props.onAddRoom) {
|
||||
// addRoomButton = (
|
||||
// <AccessibleTooltipButton
|
||||
// tabIndex={tabIndex}
|
||||
// onClick={this.onAddRoom}
|
||||
// className="mx_RoomSublist2_addButton"
|
||||
// title={this.props.addRoomLabel || _t("Add room")}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
// TODO: a11y (see old component)
|
||||
return (
|
||||
|
@ -184,11 +183,8 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
role="treeitem"
|
||||
aria-level="1"
|
||||
>
|
||||
{chevron}
|
||||
<span>{this.props.label}</span>
|
||||
</AccessibleButton>
|
||||
{badge}
|
||||
{addRoomButton}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
@ -243,13 +239,14 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
|||
// TODO: CSS TBD
|
||||
// TODO: Show N more instead of infinity more?
|
||||
// TODO: Safely use the same height of a tile, not hardcoded hacks
|
||||
const moreTileHeightPx = `${layout.tileHeight}px`;
|
||||
visibleTiles.splice(visibleTiles.length - 1, 1, (
|
||||
<div
|
||||
onClick={this.onShowAllClick}
|
||||
style={{height: '34px', lineHeight: '34px', backgroundColor: 'green', cursor: 'pointer'}}
|
||||
style={{height: moreTileHeightPx, lineHeight: moreTileHeightPx, backgroundColor: 'transparent', cursor: 'pointer'}}
|
||||
key='showall'
|
||||
>
|
||||
{_t("Show %(n)s more rooms", {n: numMissing})}
|
||||
{_t("Show %(n)s more", {n: numMissing})}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ enum NotificationColor {
|
|||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
showMessagePreview: boolean;
|
||||
|
||||
// TODO: Allow falsifying counts (for invites and stuff)
|
||||
// TODO: Transparency? Was this ever used?
|
||||
|
@ -192,33 +193,22 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
|||
// TODO: a11y proper
|
||||
// TODO: Render more than bare minimum
|
||||
|
||||
const hasBadge = this.state.notificationState.color > NotificationColor.Bold;
|
||||
const isUnread = this.state.notificationState.color > NotificationColor.None;
|
||||
const classes = classNames({
|
||||
'mx_RoomTile2': true,
|
||||
// 'mx_RoomTile_selected': this.state.selected,
|
||||
'mx_RoomTile2_unread': isUnread,
|
||||
'mx_RoomTile2_unreadNotify': this.state.notificationState.color >= NotificationColor.Grey,
|
||||
'mx_RoomTile2_highlight': this.state.notificationState.color >= NotificationColor.Red,
|
||||
'mx_RoomTile2_invited': this.roomIsInvite,
|
||||
// 'mx_RoomTile_menuDisplayed': isMenuDisplayed,
|
||||
'mx_RoomTile2_noBadges': !hasBadge,
|
||||
// 'mx_RoomTile_transparent': this.props.transparent,
|
||||
// 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
|
||||
});
|
||||
|
||||
const avatarClasses = classNames({
|
||||
'mx_RoomTile2_avatar': true,
|
||||
});
|
||||
|
||||
|
||||
let badge;
|
||||
const hasBadge = this.state.notificationState.color > NotificationColor.Bold;
|
||||
if (hasBadge) {
|
||||
const hasNotif = this.state.notificationState.color >= NotificationColor.Red;
|
||||
const isEmptyBadge = !localStorage.getItem("mx_rl_rt_badgeCount");
|
||||
const badgeClasses = classNames({
|
||||
'mx_RoomTile2_badge': true,
|
||||
'mx_RoomTile2_badgeButton': false, // this.state.badgeHover || isMenuDisplayed
|
||||
'mx_RoomTile2_badgeHighlight': hasNotif,
|
||||
'mx_RoomTile2_badgeEmpty': isEmptyBadge,
|
||||
});
|
||||
badge = <div className={badgeClasses}>{this.state.notificationState.symbol}</div>;
|
||||
const symbol = this.state.notificationState.symbol;
|
||||
badge = <div className={badgeClasses}>{isEmptyBadge ? null : symbol}</div>;
|
||||
}
|
||||
|
||||
// TODO: the original RoomTile uses state for the room name. Do we need to?
|
||||
|
@ -226,20 +216,21 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
|||
if (typeof name !== 'string') name = '';
|
||||
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
||||
|
||||
const nameClasses = classNames({
|
||||
'mx_RoomTile2_name': true,
|
||||
'mx_RoomTile2_invite': this.roomIsInvite,
|
||||
'mx_RoomTile2_badgeShown': hasBadge,
|
||||
});
|
||||
|
||||
// TODO: Support collapsed state properly
|
||||
let tooltip = null;
|
||||
if (false) { // isCollapsed
|
||||
if (this.state.hover) {
|
||||
tooltip = <Tooltip className="mx_RoomTile2_tooltip" label={this.props.room.name} />
|
||||
}
|
||||
// TODO: Tooltip?
|
||||
|
||||
let messagePreview = null;
|
||||
if (this.props.showMessagePreview) {
|
||||
// TODO: Actually get the real message preview from state
|
||||
messagePreview = <div className="mx_RoomTile2_messagePreview">I just ate a pie.</div>;
|
||||
}
|
||||
|
||||
const nameClasses = classNames({
|
||||
"mx_RoomTile2_name": true,
|
||||
"mx_RoomTile2_nameWithPreview": !!messagePreview,
|
||||
});
|
||||
|
||||
const avatarSize = 32;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<RovingTabIndexWrapper inputRef={this.roomTile}>
|
||||
|
@ -254,20 +245,18 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
|||
onClick={this.onTileClick}
|
||||
role="treeitem"
|
||||
>
|
||||
<div className={avatarClasses}>
|
||||
<div className="mx_RoomTile2_avatarContainer">
|
||||
<RoomAvatar room={this.props.room} width={24} height={24}/>
|
||||
</div>
|
||||
<div className="mx_RoomTile2_avatarContainer">
|
||||
<RoomAvatar room={this.props.room} width={avatarSize} height={avatarSize}/>
|
||||
</div>
|
||||
<div className="mx_RoomTile2_nameContainer">
|
||||
<div className="mx_RoomTile2_labelContainer">
|
||||
<div title={name} className={nameClasses} tabIndex={-1} dir="auto">
|
||||
{name}
|
||||
</div>
|
||||
<div title={name} className={nameClasses} tabIndex={-1} dir="auto">
|
||||
{name}
|
||||
</div>
|
||||
{messagePreview}
|
||||
</div>
|
||||
<div className="mx_RoomTile2_badgeContainer">
|
||||
{badge}
|
||||
</div>
|
||||
{tooltip}
|
||||
</AccessibleButton>
|
||||
}
|
||||
</RovingTabIndexWrapper>
|
||||
|
|
|
@ -1090,6 +1090,7 @@
|
|||
"Low priority": "Low priority",
|
||||
"Historical": "Historical",
|
||||
"System Alerts": "System Alerts",
|
||||
"People": "People",
|
||||
"This room": "This room",
|
||||
"Joining room …": "Joining room …",
|
||||
"Loading …": "Loading …",
|
||||
|
@ -1133,10 +1134,7 @@
|
|||
"Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>",
|
||||
"Not now": "Not now",
|
||||
"Don't ask me again": "Don't ask me again",
|
||||
"Jump to first unread room.": "Jump to first unread room.",
|
||||
"Jump to first invite.": "Jump to first invite.",
|
||||
"Add room": "Add room",
|
||||
"Show %(n)s more rooms": "Show %(n)s more rooms",
|
||||
"Show %(n)s more": "Show %(n)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.",
|
||||
|
@ -2017,6 +2015,9 @@
|
|||
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
|
||||
"Active call": "Active call",
|
||||
"There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?": "There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?",
|
||||
"Jump to first unread room.": "Jump to first unread room.",
|
||||
"Jump to first invite.": "Jump to first invite.",
|
||||
"Add room": "Add room",
|
||||
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
|
||||
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
|
||||
"Search failed": "Search failed",
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const TILE_HEIGHT_PX = 34;
|
||||
const TILE_HEIGHT_PX = 44;
|
||||
|
||||
interface ISerializedListLayout {
|
||||
numTiles: number;
|
||||
|
|
Loading…
Reference in New Issue