Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Implement new Read Receipt design (#8389)
Browse files Browse the repository at this point in the history
* feat: introduce new alignment types for tooltip
* feat: introduce new hook for tooltips
* feat: allow using onFocus callback for RovingAccessibleButton
* feat: allow using custom class for ContextMenu
* feat: allow setting tab index for avatar
* refactor: move read receipts out of event tile
* feat: implement new read receipt design
* feat: update SentReceipt to match new read receipts as well
  • Loading branch information
justjanne committed Apr 22, 2022
1 parent 03c4677 commit ee2ee3c
Show file tree
Hide file tree
Showing 18 changed files with 551 additions and 268 deletions.
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
@import "./views/rooms/_NotificationBadge.scss";
@import "./views/rooms/_PinnedEventTile.scss";
@import "./views/rooms/_PresenceLabel.scss";
@import "./views/rooms/_ReadReceiptGroup.scss";
@import "./views/rooms/_RecentlyViewedButton.scss";
@import "./views/rooms/_ReplyPreview.scss";
@import "./views/rooms/_ReplyTile.scss";
Expand Down
4 changes: 2 additions & 2 deletions res/css/views/right_panel/_TimelineCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ limitations under the License.
padding-left: 36px;
}

.mx_EventTile_readAvatars {
top: -10px;
.mx_ReadReceiptGroup {
top: -6px;
}

.mx_WhoIsTypingTile {
Expand Down
6 changes: 3 additions & 3 deletions res/css/views/rooms/_EventBubbleTile.scss
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ limitations under the License.
}
}

.mx_EventTile_readAvatars {
.mx_ReadReceiptGroup {
position: absolute;
right: -78px; // as close to right gutter without clipping as possible
bottom: 0;
Expand Down Expand Up @@ -585,8 +585,8 @@ limitations under the License.
right: 127px; // align with that of right-column bubbles
}

.mx_EventTile_readAvatars {
right: -18px; // match alignment to RRs of chat bubbles
.mx_ReadReceiptGroup {
right: -14px; // match alignment to RRs of chat bubbles
}

&::before {
Expand Down
44 changes: 6 additions & 38 deletions res/css/views/rooms/_EventTile.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,18 @@ $left-gutter: 64px;

.mx_EventTile_receiptSent,
.mx_EventTile_receiptSending {
// Give it some dimensions so the tooltip can position properly
position: relative;
display: inline-block;
width: 14px;
height: 14px;
// We don't use `position: relative` on the element because then it won't line
// up with the other read receipts
width: 16px;
height: 16px;

&::before {
background-color: $tertiary-content;
mask-repeat: no-repeat;
mask-position: center;
mask-size: 14px;
width: 14px;
height: 14px;
mask-size: 16px;
width: 16px;
height: 16px;
content: '';
position: absolute;
top: 0;
Expand Down Expand Up @@ -349,36 +347,6 @@ $left-gutter: 64px;
}
}

.mx_EventTile_readAvatars {
position: relative;
display: inline-block;
width: 14px;
height: 14px;
// This aligns the avatar with the last line of the
// message. We want to move it one line up - 2.2rem
top: -2.2rem;
user-select: none;
z-index: 1;
}

.mx_EventTile_readAvatars .mx_BaseAvatar {
position: absolute;
display: inline-block;
height: $font-14px;
width: $font-14px;

will-change: left, top;
transition:
left var(--transition-short) ease-out,
top var(--transition-standard) ease-out;
}

.mx_EventTile_readAvatarRemainder {
color: $event-timestamp-color;
font-size: $font-11px;
position: absolute;
}

.mx_EventTile_bigEmoji {
font-size: 48px;
line-height: 57px;
Expand Down
2 changes: 1 addition & 1 deletion res/css/views/rooms/_GroupLayout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ $left-gutter: 64px;
top: 3px;
}

.mx_EventTile_readAvatars {
.mx_ReadReceiptGroup {
// This aligns the avatar with the last line of the
// message. We want to move it one line up - 2rem
top: -2rem;
Expand Down
4 changes: 2 additions & 2 deletions res/css/views/rooms/_IRCLayout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ $irc-line-height: $font-18px;
order: 5;
flex-shrink: 0;

.mx_EventTile_readAvatars {
top: 0.2rem; // ($irc-line-height - avatar height) / 2
.mx_ReadReceiptGroup {
top: -0.3rem; // ($irc-line-height - avatar height) / 2
}
}

Expand Down
146 changes: 146 additions & 0 deletions res/css/views/rooms/_ReadReceiptGroup.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
Copyright 2022 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.
*/

.mx_ReadReceiptGroup {
position: relative;
display: inline-block;
// This aligns the avatar with the last line of the
// message. We want to move it one line up
// See .mx_GroupLayout .mx_EventTile .mx_EventTile_line in _GroupLayout.scss
top: calc(-$font-22px - 3px);
user-select: none;
z-index: 1;

.mx_ReadReceiptGroup_button {
display: inline-flex;
flex-direction: row;
height: 16px;
padding: 4px;
border-radius: 6px;

&.mx_AccessibleButton {
&:hover {
background: $event-selected-color;
}
}
}

.mx_ReadReceiptGroup_remainder {
color: $secondary-content;
font-size: $font-11px;
line-height: $font-16px;
margin-right: 4px;
}

.mx_ReadReceiptGroup_container {
position: relative;
display: block;
height: 100%;

.mx_BaseAvatar {
position: absolute;
display: inline-block;
height: 14px;
width: 14px;
border: 1px solid $background;
border-radius: 100%;

will-change: left, top;
transition:
left var(--transition-short) ease-out,
top var(--transition-standard) ease-out;
}
}
}

.mx_ReadReceiptGroup_popup {
max-height: 300px;
width: 220px;
border-radius: 8px;
display: flex;
flex-direction: column;
text-align: left;
font-size: 12px;
line-height: 15px;

right: 0;

&.mx_ContextualMenu_top {
top: 8px;
}

&.mx_ContextualMenu_bottom {
bottom: 8px;
}

.mx_ReadReceiptGroup_title {
font-size: 12px;
line-height: 15px;
margin: 16px 16px 8px;
font-weight: 600;
// shouldn’t be actually focusable
outline: none;
}

.mx_AutoHideScrollbar {
.mx_ReadReceiptGroup_person {
display: flex;
flex-direction: row;
padding: 4px;
margin: 0 12px;
border-radius: 8px;

&:hover {
background: $menu-selected-color;
}

&:last-child {
margin-bottom: 8px;
}

.mx_BaseAvatar {
margin: 6px 8px;
align-self: center;
justify-self: center;
}

.mx_ReadReceiptGroup_name {
display: flex;
flex-direction: column;
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;

p {
margin: 2px 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}

.mx_ReadReceiptGroup_secondary {
color: $secondary-content;
}
}
}
}
}

.mx_ReadReceiptGroup_person--tooltip {
overflow-y: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
16 changes: 12 additions & 4 deletions src/accessibility/roving/RovingAccessibleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ import AccessibleButton from "../../components/views/elements/AccessibleButton";
import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types";

interface IProps extends Omit<React.ComponentProps<typeof AccessibleButton>, "onFocus" | "inputRef" | "tabIndex"> {
interface IProps extends Omit<React.ComponentProps<typeof AccessibleButton>, "inputRef" | "tabIndex"> {
inputRef?: Ref;
}

// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
export const RovingAccessibleButton: React.FC<IProps> = ({ inputRef, ...props }) => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
return <AccessibleButton {...props} onFocus={onFocus} inputRef={ref} tabIndex={isActive ? 0 : -1} />;
export const RovingAccessibleButton: React.FC<IProps> = ({ inputRef, onFocus, ...props }) => {
const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
return <AccessibleButton
{...props}
onFocus={event => {
onFocusInternal();
onFocus?.(event);
}}
inputRef={ref}
tabIndex={isActive ? 0 : -1}
/>;
};

16 changes: 12 additions & 4 deletions src/accessibility/roving/RovingAccessibleTooltipButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@ import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types";

type ATBProps = React.ComponentProps<typeof AccessibleTooltipButton>;
interface IProps extends Omit<ATBProps, "onFocus" | "inputRef" | "tabIndex"> {
interface IProps extends Omit<ATBProps, "inputRef" | "tabIndex"> {
inputRef?: Ref;
}

// Wrapper to allow use of useRovingTabIndex for simple AccessibleTooltipButtons outside of React Functional Components.
export const RovingAccessibleTooltipButton: React.FC<IProps> = ({ inputRef, ...props }) => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
return <AccessibleTooltipButton {...props} onFocus={onFocus} inputRef={ref} tabIndex={isActive ? 0 : -1} />;
export const RovingAccessibleTooltipButton: React.FC<IProps> = ({ inputRef, onFocus, ...props }) => {
const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
return <AccessibleTooltipButton
{...props}
onFocus={event => {
onFocusInternal();
onFocus?.(event);
}}
inputRef={ref}
tabIndex={isActive ? 0 : -1}
/>;
};

3 changes: 2 additions & 1 deletion src/components/structures/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export interface IProps extends IPosition {
// whether this context menu should be focus managed. If false it must handle itself
managed?: boolean;
wrapperClassName?: string;
menuClassName?: string;

// If true, this context menu will be mounted as a child to the parent container. Otherwise
// it will be mounted to a container at the root of the DOM.
Expand Down Expand Up @@ -319,7 +320,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
'mx_ContextualMenu_withChevron_bottom': chevronFace === ChevronFace.Bottom,
'mx_ContextualMenu_rightAligned': this.props.rightAligned === true,
'mx_ContextualMenu_bottomAligned': this.props.bottomAligned === true,
});
}, this.props.menuClassName);

const menuStyle: CSSProperties = {};
if (props.menuWidth) {
Expand Down
1 change: 1 addition & 0 deletions src/components/views/avatars/BaseAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ interface IProps {
onClick?: React.MouseEventHandler;
inputRef?: React.RefObject<HTMLImageElement & HTMLSpanElement>;
className?: string;
tabIndex?: number;
}

const calculateUrls = (url, urls, lowBandwidth) => {
Expand Down
3 changes: 2 additions & 1 deletion src/components/views/avatars/MemberAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" |
title?: string;
style?: any;
forceHistorical?: boolean; // true to deny `feature_use_only_current_profiles` usage. Default false.
hideTitle?: boolean;
}

interface IState {
Expand Down Expand Up @@ -124,7 +125,7 @@ export default class MemberAvatar extends React.PureComponent<IProps, IState> {
<BaseAvatar
{...otherProps}
name={this.state.name}
title={this.state.title}
title={this.props.hideTitle ? undefined : this.state.title}
idName={userId}
url={this.state.imageUrl}
onClick={onClick}
Expand Down
12 changes: 12 additions & 0 deletions src/components/views/elements/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export enum Alignment {
Top, // Centered
Bottom, // Centered
InnerBottom, // Inside the target, at the bottom
TopRight, // On top of the target, right aligned
TopCenter, // On top of the target, center aligned
}

export interface ITooltipProps {
Expand Down Expand Up @@ -149,6 +151,16 @@ export default class Tooltip extends React.Component<ITooltipProps> {
style.top = baseTop + parentBox.height - 50;
style.left = horizontalCenter;
style.transform = "translate(-50%)";
break;
case Alignment.TopRight:
style.top = baseTop - 5;
style.right = width - parentBox.right - window.pageXOffset;
style.transform = "translate(5px, -100%)";
break;
case Alignment.TopCenter:
style.top = baseTop - 5;
style.left = horizontalCenter;
style.transform = "translate(-50%, -100%)";
}

return style;
Expand Down
Loading

0 comments on commit ee2ee3c

Please sign in to comment.