Iterate on forward dialog design feedback

Signed-off-by: Robin Townsend <robin@robin.town>
pull/21833/head
Robin Townsend 2021-05-21 12:41:29 -04:00
parent f34d61cf5d
commit 7a04502151
5 changed files with 116 additions and 81 deletions

View File

@ -23,7 +23,15 @@ limitations under the License.
min-height: 0; min-height: 0;
height: 80vh; height: 80vh;
.mx_ForwardDialog_preview { > h3 {
margin: 0 0 6px;
color: $secondary-fg-color;
font-size: $font-12px;
font-weight: $font-semi-bold;
line-height: $font-15px;
}
> .mx_ForwardDialog_preview {
max-height: 30%; max-height: 30%;
flex-shrink: 0; flex-shrink: 0;
overflow: scroll; overflow: scroll;
@ -43,7 +51,14 @@ limitations under the License.
} }
} }
.mx_ForwardList { > hr {
width: 100%;
border: none;
border-top: 1px solid $input-border-color;
margin: 12px 0;
}
> .mx_ForwardList {
display: contents; display: contents;
.mx_SearchBox { .mx_SearchBox {
@ -54,8 +69,6 @@ limitations under the License.
.mx_ForwardList_content { .mx_ForwardList_content {
flex-grow: 1; flex-grow: 1;
// Push the scrollbar into the gutter
margin-right: -6px;
} }
.mx_ForwardList_noResults { .mx_ForwardList_noResults {
@ -64,30 +77,24 @@ limitations under the License.
} }
.mx_ForwardList_results { .mx_ForwardList_results {
margin-right: 6px;
&:not(:first-child) { &:not(:first-child) {
margin-top: 24px; margin-top: 24px;
} }
> h3 {
margin: 0;
color: $secondary-fg-color;
font-size: $font-12px;
font-weight: $font-semi-bold;
line-height: $font-15px;
}
.mx_ForwardList_entry { .mx_ForwardList_entry {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-top: 12px;
height: 32px; height: 32px;
padding: 6px;
border-radius: 8px;
&:hover {
background-color: $groupFilterPanel-bg-color;
}
.mx_ForwardList_roomButton { .mx_ForwardList_roomButton {
display: flex; display: flex;
margin-right: 12px; margin-right: 12px;
flex-grow: 1;
min-width: 0; min-width: 0;
.mx_DecoratedRoomAvatar { .mx_DecoratedRoomAvatar {
@ -105,26 +112,39 @@ limitations under the License.
} }
.mx_ForwardList_sendButton { .mx_ForwardList_sendButton {
&.mx_ForwardList_sending, &.mx_ForwardList_sent { position: relative;
&::before {
content: ''; &:not(.mx_ForwardList_canSend) .mx_ForwardList_sendLabel {
display: inline-block; // Hide the "Send" label while preserving button size
visibility: hidden;
}
.mx_ForwardList_sendIcon, .mx_NotificationBadge {
position: absolute;
}
.mx_NotificationBadge {
opacity: 0.4;
}
&.mx_ForwardList_sending .mx_ForwardList_sendIcon {
background-color: $button-primary-bg-color; background-color: $button-primary-bg-color;
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
mask-position: center; mask-position: center;
mask-repeat: no-repeat; mask-repeat: no-repeat;
mask-size: 14px; mask-size: 14px;
width: 14px; width: 14px;
height: 14px; height: 14px;
margin-right: 5px;
}
} }
&.mx_ForwardList_sending::before { &.mx_ForwardList_sent .mx_ForwardList_sendIcon {
mask-image: url('$(res)/img/element-icons/circle-sending.svg'); background-color: $button-primary-bg-color;
}
&.mx_ForwardList_sent::before {
mask-image: url('$(res)/img/element-icons/circle-sent.svg'); mask-image: url('$(res)/img/element-icons/circle-sent.svg');
mask-position: center;
mask-repeat: no-repeat;
mask-size: 14px;
width: 14px;
height: 14px;
} }
} }
} }

View File

@ -31,9 +31,11 @@ import {avatarUrlForUser} from "../../../Avatar";
import EventTile from "../rooms/EventTile"; import EventTile from "../rooms/EventTile";
import SearchBox from "../../structures/SearchBox"; import SearchBox from "../../structures/SearchBox";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import AccessibleButton from "../elements/AccessibleButton"; import {Alignment} from '../elements/Tooltip';
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "../rooms/NotificationBadge";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
@ -82,52 +84,60 @@ const Entry: React.FC<IEntryProps> = ({ room, event, cli, onFinished }) => {
} }
}; };
let button;
if (room.maySendMessage()) {
let label;
let className; let className;
let disabled = false;
let title;
let icon;
if (sendState === SendState.CanSend) { if (sendState === SendState.CanSend) {
label = _t("Send");
className = "mx_ForwardList_canSend"; className = "mx_ForwardList_canSend";
} else if (sendState === SendState.Sending) { if (room.maySendMessage()) {
label = _t("Sending…"); title = _t("Send");
className = "mx_ForwardList_sending";
} else if (sendState === SendState.Sent) {
label = _t("Sent");
className = "mx_ForwardList_sent";
} else { } else {
label = _t("Failed to send"); disabled = true;
className = "mx_ForwardList_sendFailed"; title = _t("You do not have permission to do this");
} }
} else if (sendState === SendState.Sending) {
button = className = "mx_ForwardList_sending";
<AccessibleButton disabled = true;
kind={sendState === SendState.Failed ? "danger_outline" : "primary_outline"} title = _t("Sending…");
className={`mx_ForwardList_sendButton ${className}`} icon = <div className="mx_ForwardList_sendIcon"></div>;
onClick={send} } else if (sendState === SendState.Sent) {
disabled={sendState !== SendState.CanSend} className = "mx_ForwardList_sent";
> disabled = true;
{ label } title = _t("Sent");
</AccessibleButton>; icon = <div className="mx_ForwardList_sendIcon"></div>;
} else { } else {
button = className = "mx_ForwardList_sendFailed";
<AccessibleTooltipButton disabled = true;
kind="primary_outline" title = _t("Failed to send");
className="mx_ForwardList_sendButton mx_ForwardList_canSend" icon = <NotificationBadge
onClick={() => {}} notification={StaticNotificationState.RED_EXCLAMATION}
disabled={true} />;
title={_t("You do not have permission to post to this room")}
>
{ _t("Send") }
</AccessibleTooltipButton>;
} }
return <div className="mx_ForwardList_entry"> return <div className="mx_ForwardList_entry">
<AccessibleButton className="mx_ForwardList_roomButton" onClick={jumpToRoom}> <AccessibleTooltipButton
className="mx_ForwardList_roomButton"
onClick={jumpToRoom}
title={_t("Open link")}
yOffset={-20}
alignment={Alignment.Top}
>
<DecoratedRoomAvatar room={room} avatarSize={32} /> <DecoratedRoomAvatar room={room} avatarSize={32} />
<span className="mx_ForwardList_entry_name">{ room.name }</span> <span className="mx_ForwardList_entry_name">{ room.name }</span>
</AccessibleButton> </AccessibleTooltipButton>
{ button } <AccessibleTooltipButton
kind={sendState === SendState.Failed ? "danger_outline" : "primary_outline"}
className={`mx_ForwardList_sendButton ${className}`}
onClick={send}
disabled={disabled}
title={title}
yOffset={-20}
alignment={Alignment.Top}
>
<div className="mx_ForwardList_sendLabel">{ _t("Send") }</div>
{ icon }
</AccessibleTooltipButton>
</div>; </div>;
}; };
@ -180,6 +190,7 @@ const ForwardDialog: React.FC<IProps> = ({ cli, event, permalinkCreator, onFinis
onFinished={onFinished} onFinished={onFinished}
fixedWidth={false} fixedWidth={false}
> >
<h3>{ _t("Message preview") }</h3>
<div className={classnames("mx_ForwardDialog_preview", { <div className={classnames("mx_ForwardDialog_preview", {
"mx_IRCLayout": previewLayout == Layout.IRC, "mx_IRCLayout": previewLayout == Layout.IRC,
"mx_GroupLayout": previewLayout == Layout.Group, "mx_GroupLayout": previewLayout == Layout.Group,
@ -191,11 +202,11 @@ const ForwardDialog: React.FC<IProps> = ({ cli, event, permalinkCreator, onFinis
permalinkCreator={permalinkCreator} permalinkCreator={permalinkCreator}
/> />
</div> </div>
<hr />
<div className="mx_ForwardList"> <div className="mx_ForwardList">
<h2>{ _t("Forward to") }</h2>
<SearchBox <SearchBox
className="mx_textinput_icon mx_textinput_search" className="mx_textinput_icon mx_textinput_search"
placeholder={ _t("Filter your rooms and DMs") } placeholder={ _t("Search for rooms or people") }
onSearch={setQuery} onSearch={setQuery}
autoComplete={true} autoComplete={true}
autoFocus={true} autoFocus={true}

View File

@ -19,7 +19,7 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import AccessibleButton from "./AccessibleButton"; import AccessibleButton from "./AccessibleButton";
import Tooltip from './Tooltip'; import Tooltip, {Alignment} from './Tooltip';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> { interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
@ -28,6 +28,7 @@ interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
tooltipClassName?: string; tooltipClassName?: string;
forceHide?: boolean; forceHide?: boolean;
yOffset?: number; yOffset?: number;
alignment?: Alignment;
} }
interface IState { interface IState {
@ -66,13 +67,14 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
render() { render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const {title, tooltip, children, tooltipClassName, forceHide, yOffset, ...props} = this.props; const {title, tooltip, children, tooltipClassName, forceHide, yOffset, alignment, ...props} = this.props;
const tip = this.state.hover ? <Tooltip const tip = this.state.hover ? <Tooltip
className="mx_AccessibleTooltipButton_container" className="mx_AccessibleTooltipButton_container"
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)} tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
label={tooltip || title} label={tooltip || title}
yOffset={yOffset} yOffset={yOffset}
alignment={alignment}
/> : <div />; /> : <div />;
return ( return (
<AccessibleButton <AccessibleButton

View File

@ -2202,11 +2202,13 @@
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.", "PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.",
"Report a bug": "Report a bug", "Report a bug": "Report a bug",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.", "Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.",
"You do not have permission to do this": "You do not have permission to do this",
"Sending…": "Sending…", "Sending…": "Sending…",
"Sent": "Sent", "Sent": "Sent",
"Open link": "Open link",
"Forward message": "Forward message", "Forward message": "Forward message",
"Forward to": "Forward to", "Message preview": "Message preview",
"Filter your rooms and DMs": "Filter your rooms and DMs", "Search for rooms or people": "Search for rooms or people",
"Confirm abort of host creation": "Confirm abort of host creation", "Confirm abort of host creation": "Confirm abort of host creation",
"Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.",
"Abort": "Abort", "Abort": "Abort",

View File

@ -101,30 +101,30 @@ describe("ForwardDialog", () => {
})); }));
const firstButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").first(); const firstButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").first();
expect(firstButton.text()).toBe("Send"); expect(firstButton.render().is(".mx_ForwardList_canSend")).toBe(true);
act(() => { firstButton.simulate("click"); }); act(() => { firstButton.simulate("click"); });
expect(firstButton.text()).toBe("Sending…"); expect(firstButton.render().is(".mx_ForwardList_sending")).toBe(true);
await act(async () => { await act(async () => {
cancelSend(); cancelSend();
// Wait one tick for the button to realize the send failed // Wait one tick for the button to realize the send failed
await new Promise(resolve => setImmediate(resolve)); await new Promise(resolve => setImmediate(resolve));
}); });
expect(firstButton.text()).toBe("Failed to send"); expect(firstButton.render().is(".mx_ForwardList_sendFailed")).toBe(true);
const secondButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").at(1); const secondButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").at(1);
expect(secondButton.render().text()).toBe("Send"); expect(secondButton.render().is(".mx_ForwardList_canSend")).toBe(true);
act(() => { secondButton.simulate("click"); }); act(() => { secondButton.simulate("click"); });
expect(secondButton.text()).toBe("Sending…"); expect(secondButton.render().is(".mx_ForwardList_sending")).toBe(true);
await act(async () => { await act(async () => {
finishSend(); finishSend();
// Wait one tick for the button to realize the send succeeded // Wait one tick for the button to realize the send succeeded
await new Promise(resolve => setImmediate(resolve)); await new Promise(resolve => setImmediate(resolve));
}); });
expect(secondButton.text()).toBe("Sent"); expect(secondButton.render().is(".mx_ForwardList_sent")).toBe(true);
}); });
it("can render replies", async () => { it("can render replies", async () => {