Tweaks to informational architecture 1.1 (#7052)

* Move user avatar to Space panel

* Add room list header for 'Home' or 'Space Name' to room list
Add existing Space context menus to room list header

* Re-add pending room join spinner

* Iterate RoomListHeader plus context menu

* Iterate space context menu

* Iterate room list + interactions

* Move DND to new iA model

* Replace composer custom status management with usermenu one

* Cull Quick Actions

* Iterate minimized room list state

* delint

* Merge the RoomListNumResults into the RoomListHeader

* Make the search shortcut prompt semi-bold

* Iterate RoomListHeader based on design review

* Iterate UserMenu based on feedback

* Add name to expanded spacepanel usermenu button

* i18n

* Make room sub list aux button components more generic

* Change left panel explore button to only refer to room directory

* Iterate RoomListHeader

* Fix custom user status input field width in Chrome

* Bring back Notification settings button

* delint

* i18n

* post-merge fix

* iterate pr

* Remove unused state

* update copy

* Apply suggestions from PR review

* delint

* Update invite iconography

* Iterate Space context menu to match Figma

* Fix chevron alignment

* Fix edge case for RoomListHeader on metaspaces

* Wire up general rageshake-driven feedback mechanism

* Add IA1.1 info toast

* add missing alt attribute

* delint

* delint

* tweak ia toast priority

* e2e test account for new toast

* autofocus feedback field and remove old subheading

* tweak copy

* Iterate space panel colours to match Figma

* Iterate PR

* delint

* Fix feedback submission with object setting values

* iterate based on review

* Tweak colours and update splash image

* Tweaks based on review

* Remove room list prompt, made redundant by the big fat `+`

* Fix edge cases around User Menu positioning and dnd

* Add missing import, bad merge?

* Update aria label in e2e test

* Fix room list space rooms context menu explore button behaviour

* Tweak copy

* Revert order of options in the UserMenu

* Tweak copy

* i18n
This commit is contained in:
Michael Telatynski 2021-11-30 18:08:46 +00:00 committed by GitHub
parent c09e0efdb9
commit 8fe582b094
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1433 additions and 1483 deletions

View file

@ -23,6 +23,7 @@ import ContextMenu, {
MenuItem,
MenuItemCheckbox, MenuItemRadio,
} from "../../structures/ContextMenu";
import { _t } from "../../../languageHandler";
interface IProps extends IContextMenuProps {
className?: string;
@ -41,6 +42,7 @@ interface IOptionProps extends React.ComponentProps<typeof MenuItem> {
interface ICheckboxProps extends React.ComponentProps<typeof MenuItemCheckbox> {
iconClassName: string;
words?: boolean;
}
interface IRadioProps extends React.ComponentProps<typeof MenuItemRadio> {
@ -73,8 +75,21 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
iconClassName,
active,
className,
words,
...props
}) => {
let marker: JSX.Element;
if (words) {
marker = <span className="mx_IconizedContextMenu_activeText">
{ active ? _t("On") : _t("Off") }
</span>;
} else {
marker = <span className={classNames("mx_IconizedContextMenu_icon", {
mx_IconizedContextMenu_checked: active,
mx_IconizedContextMenu_unchecked: !active,
})} />;
}
return <MenuItemCheckbox
{...props}
className={classNames(className, {
@ -85,10 +100,7 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
>
<span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />
<span className="mx_IconizedContextMenu_label">{ label }</span>
<span className={classNames("mx_IconizedContextMenu_icon", {
mx_IconizedContextMenu_checked: active,
mx_IconizedContextMenu_unchecked: !active,
})} />
{ marker }
</MenuItemCheckbox>;
};

View file

@ -26,7 +26,6 @@ import { _t } from "../../../languageHandler";
import {
leaveSpace,
shouldShowSpaceSettings,
showAddExistingRooms,
showCreateNewRoom,
showCreateNewSubspace,
showSpaceInvite,
@ -35,18 +34,16 @@ import {
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { ButtonEvent } from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import RoomViewStore from "../../../stores/RoomViewStore";
import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import { Action } from "../../../dispatcher/actions";
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { BetaPill } from "../beta/BetaCard";
import SettingsStore from "../../../settings/SettingsStore";
import { Action } from "../../../dispatcher/actions";
interface IProps extends IContextMenuProps {
space: Room;
hideHeader?: boolean;
}
const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => {
const SpaceContextMenu = ({ space, hideHeader, onFinished, ...props }: IProps) => {
const cli = useContext(MatrixClientContext);
const userId = cli.getUserId();
@ -64,14 +61,14 @@ const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => {
<IconizedContextMenuOption
className="mx_SpacePanel_contextMenu_inviteButton"
iconClassName="mx_SpacePanel_iconInvite"
label={_t("Invite people")}
label={_t("Invite")}
onClick={onInviteClick}
/>
);
}
let settingsOption;
let leaveSection;
let leaveOption;
if (shouldShowSpaceSettings(space)) {
const onSettingsClick = (ev: ButtonEvent) => {
ev.preventDefault();
@ -97,16 +94,17 @@ const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => {
onFinished();
};
leaveSection = <IconizedContextMenuOptionList red first>
leaveOption = (
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconLeave"
className="mx_IconizedContextMenu_option_red"
label={_t("Leave space")}
onClick={onLeaveClick}
/>
</IconizedContextMenuOptionList>;
);
}
let devtoolsSection;
let devtoolsOption;
if (SettingsStore.getValue("developerMode")) {
const onViewTimelineClick = (ev: ButtonEvent) => {
ev.preventDefault();
@ -120,13 +118,13 @@ const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => {
onFinished();
};
devtoolsSection = <IconizedContextMenuOptionList first>
devtoolsOption = (
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconSettings"
label={_t("See room timeline (devtools)")}
onClick={onViewTimelineClick}
/>
</IconizedContextMenuOptionList>;
);
}
const canAddRooms = space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
@ -141,14 +139,6 @@ const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => {
onFinished();
};
const onAddExistingRoomClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
showAddExistingRooms(space);
onFinished();
};
const onNewSubspaceClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
@ -157,46 +147,25 @@ const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => {
onFinished();
};
newRoomSection = <IconizedContextMenuOptionList first>
newRoomSection = <>
<div className="mx_SpacePanel_contextMenu_separatorLabel">
{ _t("Add") }
</div>
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconPlus"
label={_t("Create new room")}
label={_t("Room")}
onClick={onNewRoomClick}
/>
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconHash"
label={_t("Add existing room")}
onClick={onAddExistingRoomClick}
/>
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconPlus"
label={_t("Add space")}
label={_t("Space")}
onClick={onNewSubspaceClick}
>
<BetaPill />
</IconizedContextMenuOption>
</IconizedContextMenuOptionList>;
</>;
}
const onMembersClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
if (!RoomViewStore.getRoomId()) {
defaultDispatcher.dispatch({
action: "view_room",
room_id: space.roomId,
}, true);
}
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
action: Action.SetRightPanelPhase,
phase: RightPanelPhases.SpaceMemberList,
refireParams: { space },
});
onFinished();
};
const onExploreRoomsClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
@ -214,26 +183,21 @@ const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => {
className="mx_SpacePanel_contextMenu"
compact
>
<div className="mx_SpacePanel_contextMenu_header">
{ !hideHeader && <div className="mx_SpacePanel_contextMenu_header">
{ space.name }
</div>
</div> }
<IconizedContextMenuOptionList first>
{ inviteOption }
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconMembers"
label={_t("Members")}
onClick={onMembersClick}
/>
{ settingsOption }
<IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconExplore"
label={canAddRooms ? _t("Manage & explore rooms") : _t("Explore rooms")}
onClick={onExploreRoomsClick}
/>
{ settingsOption }
{ leaveOption }
{ devtoolsOption }
{ newRoomSection }
</IconizedContextMenuOptionList>
{ newRoomSection }
{ leaveSection }
{ devtoolsSection }
</IconizedContextMenu>;
};

View file

@ -1,155 +0,0 @@
/*
Copyright 2018 New Vector Ltd
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, { ChangeEvent } from 'react';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { User } from "matrix-js-sdk/src/models/user";
import Spinner from "../elements/Spinner";
interface IProps {
// js-sdk User object. Not required because it might not exist.
user?: User;
}
interface IState {
message: string;
waiting: boolean;
}
@replaceableComponent("views.context_menus.StatusMessageContextMenu")
export default class StatusMessageContextMenu extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
message: this.comittedStatusMessage,
waiting: false,
};
}
public componentDidMount(): void {
const { user } = this.props;
if (!user) {
return;
}
user.on("User._unstable_statusMessage", this.onStatusMessageCommitted);
}
public componentWillUnmount(): void {
const { user } = this.props;
if (!user) {
return;
}
user.removeListener(
"User._unstable_statusMessage",
this.onStatusMessageCommitted,
);
}
get comittedStatusMessage(): string {
return this.props.user ? this.props.user.unstable_statusMessage : "";
}
private onStatusMessageCommitted = (): void => {
// The `User` object has observed a status message change.
this.setState({
message: this.comittedStatusMessage,
waiting: false,
});
};
private onClearClick = (): void=> {
MatrixClientPeg.get()._unstable_setStatusMessage("");
this.setState({
waiting: true,
});
};
private onSubmit = (e: ButtonEvent): void => {
e.preventDefault();
MatrixClientPeg.get()._unstable_setStatusMessage(this.state.message);
this.setState({
waiting: true,
});
};
private onStatusChange = (e: ChangeEvent): void => {
// The input field's value was changed.
this.setState({
message: (e.target as HTMLInputElement).value,
});
};
public render(): JSX.Element {
let actionButton;
if (this.comittedStatusMessage) {
if (this.state.message === this.comittedStatusMessage) {
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_clear"
onClick={this.onClearClick}
>
<span>{ _t("Clear status") }</span>
</AccessibleButton>;
} else {
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit"
onClick={this.onSubmit}
>
<span>{ _t("Update status") }</span>
</AccessibleButton>;
}
} else {
actionButton = <AccessibleButton
className="mx_StatusMessageContextMenu_submit"
disabled={!this.state.message}
onClick={this.onSubmit}
>
<span>{ _t("Set status") }</span>
</AccessibleButton>;
}
let spinner = null;
if (this.state.waiting) {
spinner = <Spinner w={24} h={24} />;
}
const form = <form
className="mx_StatusMessageContextMenu_form"
autoComplete="off"
onSubmit={this.onSubmit}
>
<input
type="text"
className="mx_StatusMessageContextMenu_message"
key="message"
placeholder={_t("Set a new status...")}
autoFocus={true}
maxLength={60}
value={this.state.message}
onChange={this.onStatusChange}
/>
<div className="mx_StatusMessageContextMenu_actionContainer">
{ actionButton }
{ spinner }
</div>
</form>;
return <div className="mx_StatusMessageContextMenu">
{ form }
</div>;
}
}