New right panel visual language (#11664)

* New right panel visual language

* Upgrade Compound

* Align old room header with right panel

* Rigth panel look and feel

* Fix linting and e2e tests

* Update snapshot

* Add test

* Lint

* Remove screenshot local script

* Update snapshots and UI based on feedback

* fix i18n key

* Update right panel visuals

* Fix tests

* lintfixes

* fix tests

* fix tests

* Add tests for search icon

* Fix invite dialog spec
This commit is contained in:
Germain 2023-10-20 14:30:37 +01:00 committed by GitHub
parent a63b99f687
commit f784a085fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 331 additions and 342 deletions

View file

@ -53,7 +53,7 @@ describe("Invite dialog", function () {
cy.findByRole("button", { name: /People/ }).click(); cy.findByRole("button", { name: /People/ }).click();
}); });
cy.get(".mx_BaseCard_header").within(() => { cy.get(".mx_BaseCard").within(() => {
// Click "Invite to this room" button // Click "Invite to this room" button
// Regex pattern due to "mx_MemberList_invite span::before" // Regex pattern due to "mx_MemberList_invite span::before"
cy.findByRole("button", { name: /Invite to this room/ }).click(); cy.findByRole("button", { name: /Invite to this room/ }).click();

View file

@ -30,7 +30,7 @@ const viewRoomSummaryByName = (name: string): Chainable<JQuery<HTMLElement>> =>
const checkRoomSummaryCard = (name: string): Chainable<JQuery<HTMLElement>> => { const checkRoomSummaryCard = (name: string): Chainable<JQuery<HTMLElement>> => {
cy.get(".mx_RoomSummaryCard").should("have.length", 1); cy.get(".mx_RoomSummaryCard").should("have.length", 1);
return cy.get(".mx_BaseCard_header").should("contain", name); return cy.get(".mx_RoomSummaryCard").should("contain", name);
}; };
const uploadFile = (file: string) => { const uploadFile = (file: string) => {

View file

@ -43,7 +43,7 @@ const viewRoomSummaryByName = (name: string): Chainable<JQuery<HTMLElement>> =>
const checkRoomSummaryCard = (name: string): Chainable<JQuery<HTMLElement>> => { const checkRoomSummaryCard = (name: string): Chainable<JQuery<HTMLElement>> => {
cy.get(".mx_RoomSummaryCard").should("have.length", 1); cy.get(".mx_RoomSummaryCard").should("have.length", 1);
return cy.get(".mx_BaseCard_header").should("contain", name); return cy.get(".mx_RoomSummaryCard").should("contain", name);
}; };
describe("RightPanel", () => { describe("RightPanel", () => {

View file

@ -70,7 +70,7 @@
"@sentry/tracing": "^7.0.0", "@sentry/tracing": "^7.0.0",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
"@vector-im/compound-design-tokens": "^0.0.6", "@vector-im/compound-design-tokens": "^0.0.6",
"@vector-im/compound-web": "^0.5.0", "@vector-im/compound-web": "0.5.4",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2", "@zxcvbn-ts/language-en": "^3.0.2",

View file

@ -292,29 +292,6 @@ legend {
} }
} }
/*** panels ***/
.dark-panel {
background-color: $dark-panel-bg-color;
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="text"],
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="search"],
.mx_textinput {
color: $input-darker-fg-color;
background-color: $background;
border: none;
}
}
.light-panel {
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="text"],
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type="search"],
.mx_textinput {
color: $input-darker-fg-color;
background-color: $input-lighter-bg-color;
border: none;
}
}
/* Prevent ugly dotted highlight around selected elements in Firefox */ /* Prevent ugly dotted highlight around selected elements in Firefox */
::-moz-focus-inner { ::-moz-focus-inner {
border: 0; border: 0;
@ -891,3 +868,10 @@ legend {
} }
} }
} }
.mx_lineClamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: var(--mx-line-clamp, 1);
overflow: hidden;
}

View file

@ -23,17 +23,11 @@ limitations under the License.
} }
.mx_MainSplit > .mx_RightPanel_ResizeWrapper { .mx_MainSplit > .mx_RightPanel_ResizeWrapper {
padding: var(--container-gap-width);
/* The resizer should be centered: only half of the gap-width is handled by the right panel. */
/* The other half by the RoomView. */
padding-left: calc(var(--container-gap-width) / 2);
height: calc(100vh - 51px); /* height of .mx_LegacyRoomHeader.light-panel */
&:hover .mx_ResizeHandle--horizontal::before { &:hover .mx_ResizeHandle--horizontal::before {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-150%, -50%);
height: 64px; /* to match width of the ones on roomlist */ height: 64px; /* to match width of the ones on roomlist */
width: 4px; width: 4px;

View file

@ -76,24 +76,6 @@ limitations under the License.
height: 100%; height: 100%;
} }
/* We'd like to remove this, but this makes matrixchat's resizehandle's */
/* negative margin greater than its positive padding. If it's the same */
/* or less, Safari and other WebKit based browsers get confused about overflows somehow and */
/* https://github.com/vector-im/element-web/issues/19863 happens. */
.mx_MatrixChat > .mx_ResizeHandle.mx_ResizeHandle--horizontal {
margin: 0 calc(-5.5px - var(--container-gap-width) / 2) 0 calc(-6.5px + var(--container-gap-width) / 2);
/* The condition to prevent bleeding is: (margin-left + margin-right < -11px) */
/* (IF there is NO margin on the leftPanel_wrapper) */
/* The resizeHandle does not change the gap between the left panel and the room view: */
/* the resizeHandle width is: */
/* 11px = 10px (padding) + 1px (width) */
/* and the total negative margin is -12px -> */
/* the handle requires no space */
/* right: -6px left: -6px positions the element exactly on the edge of leftPanel. */
/* left+=1 and right-=1 => resizeHandle moves 1px to the right closer to the center of the gap. */
/* We want the handle to be in the middle of the gap so it is shifted by (var(--container-gap-width) / 2) */
}
.mx_MatrixChat > .mx_ResizeHandle--horizontal:hover { .mx_MatrixChat > .mx_ResizeHandle--horizontal:hover {
position: relative; position: relative;

View file

@ -21,8 +21,7 @@ limitations under the License.
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-radius: 8px; border-left: 1px solid $separator;
padding: var(--container-border-width) 0;
box-sizing: border-box; box-sizing: border-box;
height: 100%; height: 100%;
contain: strict; contain: strict;

View file

@ -15,12 +15,10 @@ limitations under the License.
*/ */
.mx_BaseCard { .mx_BaseCard {
--BaseCard_padding-inline: $spacing-8;
--BaseCard_EventTile_line-padding-block: 2px; --BaseCard_EventTile_line-padding-block: 2px;
--BaseCard_EventTile-spacing-inline: 36px; --BaseCard_EventTile-spacing-inline: 36px;
--BaseCard_header-button-size: 24px; --BaseCard_header-button-size: 28px;
padding: 0 var(--BaseCard_padding-inline);
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -28,9 +26,18 @@ limitations under the License.
font-size: var(--cpd-font-size-body-md); font-size: var(--cpd-font-size-body-md);
.mx_BaseCard_header { .mx_BaseCard_header {
--BaseCard_header_button-margin: $spacing-12; height: 64px;
padding: var(--cpd-space-3x);
margin: $spacing-4 0 $spacing-12; box-sizing: border-box;
/* changing the color from $separator to transparent as it is
the best visual output during the transition period. This will be
reintroduced at a later stage. */
border-bottom: 1px solid transparent;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--cpd-space-2x);
flex-shrink: 0;
> h2 { > h2 {
margin: 0 44px; margin: 0 44px;
@ -40,60 +47,6 @@ limitations under the License.
white-space: nowrap; white-space: nowrap;
} }
.mx_BaseCard_back,
.mx_BaseCard_close {
position: absolute;
background-color: rgba(141, 151, 165, 0.2);
width: var(--BaseCard_header-button-size);
height: var(--BaseCard_header-button-size);
margin: var(--BaseCard_header_button-margin);
top: 0;
border-radius: 50%;
&::before {
content: "";
position: absolute;
height: inherit;
width: inherit;
top: 0;
left: 0;
mask-repeat: no-repeat;
mask-position: center;
background-color: $icon-button-color;
}
}
.mx_BaseCard_back {
left: 0;
margin-inline-start: calc(var(--BaseCard_header_button-margin) - $spacing-4);
&::before {
transform: rotate(90deg);
mask-size: 22px;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
}
/* Header title with the back button */
~ .mx_BaseCard_header_title {
width: calc(100% - 60px);
margin-inline-start: var(--BaseCard_header-button-size);
.mx_BaseCard_header_title_heading {
margin-inline-start: 6px;
}
}
}
.mx_BaseCard_close {
right: 0;
margin-inline-end: calc(var(--BaseCard_header_button-margin) - $spacing-4);
&::before {
mask-image: url("$(res)/img/icons-close.svg");
mask-size: 8px;
}
}
.mx_BaseCard_header_title { .mx_BaseCard_header_title {
display: flex; display: flex;
align-items: center; align-items: center;
@ -103,7 +56,7 @@ limitations under the License.
flex: 1; flex: 1;
.mx_BaseCard_header_title_heading { .mx_BaseCard_header_title_heading {
color: $icon-button-color; color: $primary-content;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -164,7 +117,6 @@ limitations under the License.
position: relative; position: relative;
font: var(--cpd-font-heading-sm-medium); font: var(--cpd-font-heading-sm-medium);
height: 20px; height: 20px;
border-radius: 8px;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -206,17 +158,41 @@ limitations under the License.
} }
} }
.mx_FilePanel, .mx_BaseCard_back,
.mx_UserInfo, .mx_BaseCard_close {
.mx_MemberList { position: relative;
&.mx_BaseCard { background-color: var(--cpd-color-bg-subtle-secondary);
padding: $spacing-32 0 0; width: var(--BaseCard_header-button-size);
height: var(--BaseCard_header-button-size);
border-radius: 50%;
.mx_AutoHideScrollbar { &::before {
margin-right: unset; content: "";
padding-right: unset; position: absolute;
height: inherit;
width: inherit;
top: 0;
left: 0;
mask-repeat: no-repeat;
mask-position: center;
background-color: var(--cpd-color-icon-secondary);
} }
} }
.mx_BaseCard_back {
order: 0; /* always first! */
&::before {
transform: rotate(90deg);
mask-size: 22px;
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
}
}
.mx_BaseCard_close {
order: 999; /* always last */
&::before {
mask-image: url("$(res)/img/icons-close.svg");
}
} }
.mx_ContextualMenu_wrapper.mx_BaseCard_header_title { .mx_ContextualMenu_wrapper.mx_BaseCard_header_title {
@ -230,7 +206,7 @@ limitations under the License.
} }
font: var(--cpd-font-body-sm-regular); font: var(--cpd-font-body-sm-regular);
color: $secondary-content; color: $primary-content;
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;

View file

@ -15,9 +15,10 @@ limitations under the License.
*/ */
.mx_RoomSummaryCard { .mx_RoomSummaryCard {
.mx_BaseCard_header { .mx_RoomSummaryCard_container {
text-align: center; text-align: center;
margin-top: $spacing-20; margin-top: $spacing-20;
}
.mx_RoomSummaryCard_roomName, .mx_RoomSummaryCard_roomName,
.mx_RoomSummaryCard_alias { .mx_RoomSummaryCard_alias {
@ -91,7 +92,6 @@ limitations under the License.
} }
} }
} }
}
.mx_RoomSummaryCard_aboutGroup { .mx_RoomSummaryCard_aboutGroup {
.mx_RoomSummaryCard_Button { .mx_RoomSummaryCard_Button {
@ -244,6 +244,21 @@ limitations under the License.
} }
} }
.mx_RoomSummaryCard_header {
padding: 15px 12px;
.mx_BaseCard_close {
flex-shrink: 0;
}
}
.mx_RoomSummaryCard_search input {
/* Overriding very broad CSS rules */
border: 0 !important;
margin: 0 !important;
cursor: pointer;
}
.mx_RoomSummaryCard_icon_people::before { .mx_RoomSummaryCard_icon_people::before {
mask-image: url("$(res)/img/element-icons/room/members.svg"); mask-image: url("$(res)/img/element-icons/room/members.svg");
} }
@ -279,3 +294,19 @@ limitations under the License.
.mx_RoomSummaryCard_icon_search::before { .mx_RoomSummaryCard_icon_search::before {
mask-image: url("$(res)/img/element-icons/room/search-inset.svg"); mask-image: url("$(res)/img/element-icons/room/search-inset.svg");
} }
.mx_RoomSummaryCard_searchBtn {
background: var(--cpd-color-bg-canvas-default);
color: var(--cpd-color-icon-primary);
border: 1px solid var(--cpd-color-gray-400);
border-radius: 50%;
width: 36px;
height: 36px;
padding: var(--cpd-space-2x);
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: var(--cpd-color-bg-subtle-primary);
}
}

View file

@ -138,7 +138,7 @@ limitations under the License.
&.mx_EventTile_info .mx_MessageActionBar { &.mx_EventTile_info .mx_MessageActionBar {
/* 1px: border width */ /* 1px: border width */
inset-inline-end: calc(var(--container-gap-width) + var(--BaseCard_padding-inline) + 1px); inset-inline-end: calc(var(--container-gap-width) + 1px);
} }
.mx_ReactionsRow { .mx_ReactionsRow {

View file

@ -55,8 +55,7 @@ limitations under the License.
display: flex; display: flex;
align-items: center; align-items: center;
min-width: 0; min-width: 0;
margin: 0 20px 0 16px; padding: 10px 20px 9px 16px;
padding-top: 6px;
border-bottom: 1px solid $separator; border-bottom: 1px solid $separator;
.mx_InviteOnlyIcon_large { .mx_InviteOnlyIcon_large {

View file

@ -77,36 +77,6 @@ limitations under the License.
} }
.mx_MemberList_invite { .mx_MemberList_invite {
flex: 0 0 auto; margin: 0 var(--cpd-space-2x);
position: relative; width: calc(100% - var(--cpd-space-2x));
background-color: $accent;
border-radius: 4px;
margin: 5px 9px 9px;
display: flex;
justify-content: center;
color: $button-fg-color;
font-weight: var(--cpd-font-weight-semibold);
}
.mx_MemberList_invite.mx_AccessibleButton_disabled {
background-color: $info-plinth-fg-color;
cursor: not-allowed;
}
.mx_MemberList_invite span {
padding: 8px 0;
display: inline-flex;
&::before {
content: "";
display: inline-block;
background-color: $button-fg-color;
mask-image: url("$(res)/img/element-icons/room/invite.svg");
mask-position: center;
mask-repeat: no-repeat;
mask-size: 20px;
width: 20px;
height: 20px;
margin-right: 5px;
}
} }

View file

@ -16,6 +16,7 @@ limitations under the License.
.mx_RoomHeader { .mx_RoomHeader {
height: 64px; height: 64px;
box-sizing: border-box;
padding: 0 var(--cpd-space-3x); padding: 0 var(--cpd-space-3x);
border-bottom: 1px solid $separator; border-bottom: 1px solid $separator;
background-color: $background; background-color: $background;

View file

@ -308,7 +308,7 @@ export default class RightPanel extends React.Component<Props, IState> {
} }
return ( return (
<aside className="mx_RightPanel dark-panel" id="mx_RightPanel"> <aside className="mx_RightPanel" id="mx_RightPanel">
{card} {card}
</aside> </aside>
); );

View file

@ -2587,6 +2587,19 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
<EffectsOverlay roomWidth={this.roomView.current.offsetWidth} /> <EffectsOverlay roomWidth={this.roomView.current.offsetWidth} />
)} )}
<ErrorBoundary> <ErrorBoundary>
<MainSplit
panel={rightPanel}
resizeNotifier={this.props.resizeNotifier}
// Override defaults when a thread is being shown to allow persisting a separate
// right panel width for thread panels as they tend to want to be wider.
sizeKey={this.state.threadRightPanel ? "thread" : undefined}
defaultSize={this.state.threadRightPanel ? 500 : undefined}
>
<div
className={mainSplitContentClasses}
ref={this.roomViewBody}
data-layout={this.state.layout}
>
{SettingsStore.getValue("feature_new_room_decoration_ui") ? ( {SettingsStore.getValue("feature_new_room_decoration_ui") ? (
<RoomHeader room={this.state.room} /> <RoomHeader room={this.state.room} />
) : ( ) : (
@ -2608,19 +2621,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
activeCall={this.state.activeCall} activeCall={this.state.activeCall}
/> />
)} )}
<MainSplit
panel={rightPanel}
resizeNotifier={this.props.resizeNotifier}
// Override defaults when a thread is being shown to allow persisting a separate
// right panel width for thread panels as they tend to want to be wider.
sizeKey={this.state.threadRightPanel ? "thread" : undefined}
defaultSize={this.state.threadRightPanel ? 500 : undefined}
>
<div
className={mainSplitContentClasses}
ref={this.roomViewBody}
data-layout={this.state.layout}
>
{mainSplitBody} {mainSplitBody}
</div> </div>
</MainSplit> </MainSplit>

View file

@ -46,7 +46,7 @@ type FlexProps = {
* The justification of the flex children * The justification of the flex children
* @default start * @default start
*/ */
justify?: "start" | "center" | "end" | "between"; justify?: "start" | "center" | "end" | "space-between";
/** /**
* The spacing between the flex children, expressed with the CSS unit * The spacing between the flex children, expressed with the CSS unit
* @default 0 * @default 0

View file

@ -25,7 +25,7 @@ import { backLabelForPhase } from "../../../stores/right-panel/RightPanelStorePh
import { CardContext } from "./context"; import { CardContext } from "./context";
interface IProps { interface IProps {
header?: ReactNode; header?: ReactNode | null;
footer?: ReactNode; footer?: ReactNode;
className?: string; className?: string;
withoutScrollContainer?: boolean; withoutScrollContainer?: boolean;
@ -86,11 +86,13 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
return ( return (
<CardContext.Provider value={{ isCard: true }}> <CardContext.Provider value={{ isCard: true }}>
<div className={classNames("mx_BaseCard", className)} ref={ref} onKeyDown={onKeyDown}> <div className={classNames("mx_BaseCard", className)} ref={ref} onKeyDown={onKeyDown}>
{header !== null && (
<div className="mx_BaseCard_header"> <div className="mx_BaseCard_header">
{backButton} {backButton}
{closeButton} {closeButton}
{header} {header}
</div> </div>
)}
{children} {children}
{footer && <div className="mx_BaseCard_footer">{footer}</div>} {footer && <div className="mx_BaseCard_footer">{footer}</div>}
</div> </div>

View file

@ -17,6 +17,8 @@ limitations under the License.
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
import { Tooltip } from "@vector-im/compound-web";
import { Icon as SearchIcon } from "@vector-im/compound-design-tokens/icons/search.svg";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useIsEncrypted } from "../../../hooks/useIsEncrypted"; import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
@ -53,6 +55,7 @@ import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import PosthogTrackers from "../../../PosthogTrackers"; import PosthogTrackers from "../../../PosthogTrackers";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
import { PollHistoryDialog } from "../dialogs/PollHistoryDialog"; import { PollHistoryDialog } from "../dialogs/PollHistoryDialog";
import { Flex } from "../../utils/Flex";
interface IProps { interface IProps {
room: Room; room: Room;
@ -306,7 +309,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
const header = ( const header = (
<React.Fragment> <header className="mx_RoomSummaryCard_container">
<div className="mx_RoomSummaryCard_avatar" role="presentation"> <div className="mx_RoomSummaryCard_avatar" role="presentation">
<RoomAvatar room={room} size="54px" viewAvatarOnClick /> <RoomAvatar room={room} size="54px" viewAvatarOnClick />
<TextWithTooltip <TextWithTooltip
@ -329,7 +332,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
<div className="mx_RoomSummaryCard_alias" title={alias}> <div className="mx_RoomSummaryCard_alias" title={alias}>
{alias} {alias}
</div> </div>
</React.Fragment> </header>
); );
const memberCount = useRoomMemberCount(room); const memberCount = useRoomMemberCount(room);
@ -337,22 +340,40 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
const pinCount = usePinnedEvents(pinningEnabled ? room : undefined)?.length; const pinCount = usePinnedEvents(pinningEnabled ? room : undefined)?.length;
return ( return (
<BaseCard header={header} className="mx_RoomSummaryCard" onClose={onClose}> <BaseCard header={null} className="mx_RoomSummaryCard" onClose={onClose}>
<Flex
as="header"
className="mx_RoomSummaryCard_header"
gap="var(--cpd-space-3x)"
align="center"
justify="space-between"
>
<Tooltip label={_t("action|search")} side="right">
<button
className="mx_RoomSummaryCard_searchBtn"
data-testid="summary-search"
onClick={() => {
onSearchClick?.();
}}
aria-label={_t("action|search")}
>
<SearchIcon width="100%" height="100%" />
</button>
</Tooltip>
<AccessibleButton
data-testid="base-card-close-button"
className="mx_BaseCard_close"
onClick={onClose}
title={_t("action|close")}
/>
</Flex>
{header}
<Group title={_t("common|about")} className="mx_RoomSummaryCard_aboutGroup"> <Group title={_t("common|about")} className="mx_RoomSummaryCard_aboutGroup">
<Button className="mx_RoomSummaryCard_icon_people" onClick={onRoomMembersClick}> <Button className="mx_RoomSummaryCard_icon_people" onClick={onRoomMembersClick}>
{_t("common|people")} {_t("common|people")}
<span className="mx_BaseCard_Button_sublabel">{memberCount}</span> <span className="mx_BaseCard_Button_sublabel">{memberCount}</span>
</Button> </Button>
{SettingsStore.getValue("feature_new_room_decoration_ui") && (
<Button
className="mx_RoomSummaryCard_icon_search"
onClick={() => {
onSearchClick?.();
}}
>
{_t("right_panel|search_button")}
</Button>
)}
{!isVideoRoom && ( {!isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}> <Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
{_t("right_panel|files_button")} {_t("right_panel|files_button")}

View file

@ -1763,7 +1763,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
return ( return (
<BaseCard <BaseCard
className={classes.join(" ")} className={classes.join(" ")}
header={header} header={<span />}
onClose={onClose} onClose={onClose}
closeLabel={closeLabel} closeLabel={closeLabel}
cardState={cardState} cardState={cardState}
@ -1773,6 +1773,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
} }
}} }}
> >
{header}
{content} {content}
</BaseCard> </BaseCard>
); );

View file

@ -33,6 +33,8 @@ import {
ClientEvent, ClientEvent,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { throttle } from "lodash"; import { throttle } from "lodash";
import { Button, Tooltip } from "@vector-im/compound-web";
import { Icon as UserAddIcon } from "@vector-im/compound-design-tokens/icons/user-add-solid.svg";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@ -44,7 +46,7 @@ import RoomName from "../elements/RoomName";
import TruncatedList from "../elements/TruncatedList"; import TruncatedList from "../elements/TruncatedList";
import Spinner from "../elements/Spinner"; import Spinner from "../elements/Spinner";
import SearchBox from "../../structures/SearchBox"; import SearchBox from "../../structures/SearchBox";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import { ButtonEvent } from "../elements/AccessibleButton";
import EntityTile from "./EntityTile"; import EntityTile from "./EntityTile";
import MemberTile from "./MemberTile"; import MemberTile from "./MemberTile";
import BaseAvatar from "../avatars/BaseAvatar"; import BaseAvatar from "../avatars/BaseAvatar";
@ -52,7 +54,6 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent
import { UIComponent } from "../../../settings/UIFeature"; import { UIComponent } from "../../../settings/UIFeature";
import PosthogTrackers from "../../../PosthogTrackers"; import PosthogTrackers from "../../../PosthogTrackers";
import { SDKContext } from "../../../contexts/SDKContext"; import { SDKContext } from "../../../contexts/SDKContext";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5; const INITIAL_LOAD_NUM_INVITED = 5;
@ -361,20 +362,24 @@ export default class MemberList extends React.Component<IProps, IState> {
if (this.state.canInvite) { if (this.state.canInvite) {
inviteButton = ( inviteButton = (
<AccessibleButton className="mx_MemberList_invite" onClick={this.onInviteButtonClick}> <Button
<span>{inviteButtonText}</span> size="sm"
</AccessibleButton> kind="secondary"
className="mx_MemberList_invite"
onClick={this.onInviteButtonClick}
>
<UserAddIcon width="1em" height="1em" />
{inviteButtonText}
</Button>
); );
} else { } else {
inviteButton = ( inviteButton = (
<AccessibleTooltipButton <Tooltip label={_t("member_list|invite_button_no_perms_tooltip")}>
className="mx_MemberList_invite" <Button size="sm" kind="secondary" className="mx_MemberList_invite" onClick={() => {}}>
onClick={null} <UserAddIcon width="1em" height="1em" />
disabled {inviteButtonText}
tooltip={_t("member_list|invite_button_no_perms_tooltip")} </Button>
> </Tooltip>
<span>{inviteButtonText}</span>
</AccessibleTooltipButton>
); );
} }
} }
@ -416,15 +421,11 @@ export default class MemberList extends React.Component<IProps, IState> {
return ( return (
<BaseCard <BaseCard
className="mx_MemberList" className="mx_MemberList"
header={ header={<React.Fragment>{scopeHeader}</React.Fragment>}
<React.Fragment>
{scopeHeader}
{inviteButton}
</React.Fragment>
}
footer={footer} footer={footer}
onClose={this.props.onClose} onClose={this.props.onClose}
> >
{inviteButton}
<div className="mx_MemberList_wrapper"> <div className="mx_MemberList_wrapper">
<TruncatedList <TruncatedList
className="mx_MemberList_section mx_MemberList_joined" className="mx_MemberList_section mx_MemberList_joined"

View file

@ -127,7 +127,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
aria-level={1} aria-level={1}
className="mx_RoomHeader_heading" className="mx_RoomHeader_heading"
> >
<span className="mx_RoomHeader_truncated">{roomName}</span> <span className="mx_RoomHeader_truncated mx_lineClamp">{roomName}</span>
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && ( {!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
<Tooltip label={_t("common|public_room")} side="right"> <Tooltip label={_t("common|public_room")} side="right">
@ -163,7 +163,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
)} )}
</BodyText> </BodyText>
{roomTopic && ( {roomTopic && (
<BodyText as="div" size="sm" className="mx_RoomHeader_topic mx_RoomHeader_truncated"> <BodyText as="div" size="sm" className="mx_RoomHeader_topic mx_RoomHeader_truncated mx_lineClamp">
<Linkify>{roomTopicBody}</Linkify> <Linkify>{roomTopicBody}</Linkify>
</BodyText> </BodyText>
)} )}

View file

@ -696,7 +696,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
const isPanel = this.props.canPreview; const isPanel = this.props.canPreview;
const classes = classNames("mx_RoomPreviewBar", "dark-panel", `mx_RoomPreviewBar_${messageCase}`, { const classes = classNames("mx_RoomPreviewBar", `mx_RoomPreviewBar_${messageCase}`, {
mx_RoomPreviewBar_panel: isPanel, mx_RoomPreviewBar_panel: isPanel,
mx_RoomPreviewBar_dialog: !isPanel, mx_RoomPreviewBar_dialog: !isPanel,
}); });

View file

@ -1850,7 +1850,6 @@
"room_summary_card": { "room_summary_card": {
"title": "Room info" "title": "Room info"
}, },
"search_button": "Search",
"settings_button": "Room settings", "settings_button": "Room settings",
"share_button": "Share room", "share_button": "Share room",
"thread_list": { "thread_list": {

View file

@ -739,7 +739,7 @@ exports[`RoomView should show error view if failed to look up room alias 1`] = `
data-room-header="legacy" data-room-header="legacy"
> >
<div <div
class="mx_RoomPreviewBar dark-panel mx_RoomPreviewBar_RoomNotFound mx_RoomPreviewBar_dialog" class="mx_RoomPreviewBar mx_RoomPreviewBar_RoomNotFound mx_RoomPreviewBar_dialog"
> >
<div <div
class="mx_RoomPreviewBar_message" class="mx_RoomPreviewBar_message"

View file

@ -3,7 +3,7 @@
exports[`AppTile destroys non-persisted right panel widget on room change 1`] = ` exports[`AppTile destroys non-persisted right panel widget on room change 1`] = `
<DocumentFragment> <DocumentFragment>
<aside <aside
class="mx_RightPanel dark-panel" class="mx_RightPanel"
id="mx_RightPanel" id="mx_RightPanel"
> >
<div <div

View file

@ -31,6 +31,7 @@ import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelS
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils"; import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";
import { PollHistoryDialog } from "../../../../src/components/views/dialogs/PollHistoryDialog"; import { PollHistoryDialog } from "../../../../src/components/views/dialogs/PollHistoryDialog";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import { _t } from "../../../../src/languageHandler";
describe("<RoomSummaryCard />", () => { describe("<RoomSummaryCard />", () => {
const userId = "@alice:domain.org"; const userId = "@alice:domain.org";
@ -84,6 +85,17 @@ describe("<RoomSummaryCard />", () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
it("opens the search", async () => {
const onSearchClick = jest.fn();
const { getByLabelText } = getComponent({
onSearchClick,
});
const searchBtn = getByLabelText(_t("action|search"));
fireEvent.click(searchBtn);
expect(onSearchClick).toHaveBeenCalled();
});
it("opens room members list on button click", () => { it("opens room members list on button click", () => {
const { getByText } = getComponent(); const { getByText } = getComponent();

View file

@ -6,8 +6,24 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
class="mx_BaseCard mx_RoomSummaryCard" class="mx_BaseCard mx_RoomSummaryCard"
> >
<div <div
class="mx_BaseCard_header" class="mx_AutoHideScrollbar"
tabindex="-1"
> >
<header
class="mx_Flex mx_RoomSummaryCard_header"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: space-between; --mx-flex-gap: var(--cpd-space-3x);"
>
<button
aria-label="Search"
class="mx_RoomSummaryCard_searchBtn"
data-state="closed"
data-testid="summary-search"
>
<div
height="100%"
width="100%"
/>
</button>
<div <div
class="mx_AccessibleButton mx_BaseCard_close" class="mx_AccessibleButton mx_BaseCard_close"
data-testid="base-card-close-button" data-testid="base-card-close-button"
@ -15,6 +31,10 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
tabindex="0" tabindex="0"
title="Close" title="Close"
/> />
</header>
<header
class="mx_RoomSummaryCard_container"
>
<div <div
class="mx_RoomSummaryCard_avatar" class="mx_RoomSummaryCard_avatar"
role="presentation" role="presentation"
@ -45,11 +65,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
class="mx_RoomSummaryCard_alias" class="mx_RoomSummaryCard_alias"
title="" title=""
/> />
</div> </header>
<div
class="mx_AutoHideScrollbar"
tabindex="-1"
>
<div <div
class="mx_BaseCard_Group mx_RoomSummaryCard_aboutGroup" class="mx_BaseCard_Group mx_RoomSummaryCard_aboutGroup"
> >

View file

@ -84,6 +84,12 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
tabindex="0" tabindex="0"
title="Close" title="Close"
/> />
<span />
</div>
<div
class="mx_AutoHideScrollbar"
tabindex="-1"
>
<div <div
class="mx_UserInfo_avatar" class="mx_UserInfo_avatar"
> >
@ -141,11 +147,6 @@ exports[`<UserInfo /> with crypto enabled renders <BasicUserInfo /> 1`] = `
</div> </div>
</div> </div>
</div> </div>
</div>
<div
class="mx_AutoHideScrollbar"
tabindex="-1"
>
<div <div
class="mx_UserInfo_container" class="mx_UserInfo_container"
> >

View file

@ -27,7 +27,7 @@ exports[`RoomHeader does not show the face pile for DMs 1`] = `
role="heading" role="heading"
> >
<span <span
class="mx_RoomHeader_truncated" class="mx_RoomHeader_truncated mx_lineClamp"
> >
!1:example.org !1:example.org
</span> </span>

View file

@ -3016,7 +3016,7 @@
dependencies: dependencies:
svg2vectordrawable "^2.9.1" svg2vectordrawable "^2.9.1"
"@vector-im/compound-web@^0.5.0": "@vector-im/compound-web@0.5.4":
version "0.5.4" version "0.5.4"
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-0.5.4.tgz#a99b346fe8de34a6f8bcbf9e1040ce1d79615dbc" resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-0.5.4.tgz#a99b346fe8de34a6f8bcbf9e1040ce1d79615dbc"
integrity sha512-w4Nwzid5Y89Dt9GaxKO+kWPTjSitLpkmoAjMYHVUajNMCfUxluzu4eOgjPRCpubPH5lZUB6/95y43wOI+pEC1Q== integrity sha512-w4Nwzid5Y89Dt9GaxKO+kWPTjSitLpkmoAjMYHVUajNMCfUxluzu4eOgjPRCpubPH5lZUB6/95y43wOI+pEC1Q==