Add tabs to the right panel (#12672)
* Create new method for header button behaviour With the introduction of tabs, the behaviour of the header buttons is changed as follows: - Close any right panel if open - Open the correct right panel if no panel was open before The old method (and behaviour) is retained as showOrHidePhase. * Implement tabs in the right panel There are three tabs: Info, People and Threads * Remove unwanted code from RoomSummaryCard - Remove the menu item for opening the memberlist since that is now taken of by the tabs. - Remove the close button * Remove code for focusing close button from tac item See https://github.com/matrix-org/matrix-react-sdk/pull/12410 There's no longer a close button to focus so we instead focus the thread tab. This is done in RightPaneltabs.tsx so we just need to remove this code. * Introduce a room info icon to the header This was previously present in the legacy room header but not in the new header. * BaseCard changes - Adds id, ariaLabelledBy and role props to implement tab accessibility. - Adds hideHeaderButtons prop to hide header buttons (think back and close buttons). - Change confusing header rendering code: header is not rendered ONLY when no header is passed AND hideHeaderButtons is true. * Refactor repeated code into function Created a new function createSpaceScopeHeader which returns the component if the room is a space room. Previously this code was duplicated in every component that uses SpaceScopeHeader component. * Pass BaseCard attributes and use helper function Actually using the code from the last two commits * Add, update and remove tests/screenshots/snapshots * Fix distance between search bar and tabs * Update compound * Update screenshots/snapshots
This commit is contained in:
parent
cd39d91c15
commit
cf8b87fd14
41 changed files with 501 additions and 294 deletions
|
@ -42,6 +42,7 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
|||
import { IRightPanelCard, IRightPanelCardState } from "../../stores/right-panel/RightPanelStoreIPanelState";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { XOR } from "../../@types/common";
|
||||
import { RightPanelTabs } from "../views/right_panel/RightPanelTabs";
|
||||
|
||||
interface BaseProps {
|
||||
overwriteCard?: IRightPanelCard; // used to display a custom card and ignoring the RightPanelStore (used for UserView)
|
||||
|
@ -171,6 +172,7 @@ export default class RightPanel extends React.Component<Props, IState> {
|
|||
<MemberList
|
||||
roomId={roomId}
|
||||
key={roomId}
|
||||
hideHeaderButtons
|
||||
onClose={this.onClose}
|
||||
searchQuery={this.state.searchQuery}
|
||||
onSearchQueryChanged={this.onSearchQueryChanged}
|
||||
|
@ -294,7 +296,6 @@ export default class RightPanel extends React.Component<Props, IState> {
|
|||
card = (
|
||||
<RoomSummaryCard
|
||||
room={this.props.room}
|
||||
onClose={this.onClose}
|
||||
// whenever RightPanel is passed a room it is passed a permalinkcreator
|
||||
permalinkCreator={this.props.permalinkCreator!}
|
||||
onSearchChange={this.props.onSearchChange}
|
||||
|
@ -314,6 +315,7 @@ export default class RightPanel extends React.Component<Props, IState> {
|
|||
|
||||
return (
|
||||
<aside className="mx_RightPanel" id="mx_RightPanel">
|
||||
{phase && <RightPanelTabs phase={phase} />}
|
||||
{card}
|
||||
</aside>
|
||||
);
|
||||
|
|
|
@ -1287,7 +1287,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
]);
|
||||
}
|
||||
} else {
|
||||
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.RoomMemberList);
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomMemberList);
|
||||
}
|
||||
break;
|
||||
case Action.View3pidInvite:
|
||||
|
|
|
@ -37,9 +37,6 @@ import { ButtonEvent } from "../views/elements/AccessibleButton";
|
|||
import Spinner from "../views/elements/Spinner";
|
||||
import Heading from "../views/typography/Heading";
|
||||
import { clearRoomNotification } from "../../utils/notifications";
|
||||
import { useDispatcher } from "../../hooks/useDispatcher";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
|
@ -259,14 +256,6 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
}
|
||||
}, [timelineSet, timelinePanel]);
|
||||
|
||||
useDispatcher(dis, (payload) => {
|
||||
// This actually foucses the close button on the threads panel, as its the only interactive element,
|
||||
// but at least it puts the user in the right area of the app.
|
||||
if (payload.action === Action.FocusThreadsPanel) {
|
||||
closeButonRef.current?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
|
@ -277,6 +266,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
}}
|
||||
>
|
||||
<BaseCard
|
||||
hideHeaderButtons
|
||||
header={
|
||||
<ThreadPanelHeader
|
||||
filterOption={filterOption}
|
||||
|
@ -284,7 +274,10 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
|||
empty={!hasThreads}
|
||||
/>
|
||||
}
|
||||
id="thread-panel"
|
||||
className="mx_ThreadPanel"
|
||||
ariaLabelledBy="thread-panel-tab"
|
||||
role="tabpanel"
|
||||
onClose={onClose}
|
||||
withoutScrollContainer={true}
|
||||
ref={card}
|
||||
|
|
|
@ -26,8 +26,12 @@ import { CardContext } from "./context";
|
|||
|
||||
interface IProps {
|
||||
header?: ReactNode | null;
|
||||
hideHeaderButtons?: boolean;
|
||||
footer?: ReactNode;
|
||||
className?: string;
|
||||
id?: string;
|
||||
role?: "tabpanel";
|
||||
ariaLabelledBy?: string;
|
||||
withoutScrollContainer?: boolean;
|
||||
closeLabel?: string;
|
||||
onClose?(ev: ButtonEvent): void;
|
||||
|
@ -62,6 +66,10 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
|
|||
onClose,
|
||||
onBack,
|
||||
className,
|
||||
id,
|
||||
ariaLabelledBy,
|
||||
role,
|
||||
hideHeaderButtons,
|
||||
header,
|
||||
footer,
|
||||
withoutScrollContainer,
|
||||
|
@ -100,13 +108,31 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
|
|||
children = <AutoHideScrollbar>{children}</AutoHideScrollbar>;
|
||||
}
|
||||
|
||||
let headerButtons: React.ReactElement | undefined;
|
||||
if (!hideHeaderButtons) {
|
||||
headerButtons = (
|
||||
<>
|
||||
{backButton}
|
||||
{closeButton}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const shouldRenderHeader = header || !hideHeaderButtons;
|
||||
|
||||
return (
|
||||
<CardContext.Provider value={{ isCard: true }}>
|
||||
<div className={classNames("mx_BaseCard", className)} ref={ref} onKeyDown={onKeyDown}>
|
||||
{header !== null && (
|
||||
<div
|
||||
id={id}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
role={role}
|
||||
className={classNames("mx_BaseCard", className)}
|
||||
ref={ref}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
{shouldRenderHeader && (
|
||||
<div className="mx_BaseCard_header">
|
||||
{backButton}
|
||||
{closeButton}
|
||||
{headerButtons}
|
||||
<div className="mx_BaseCard_headerProp">{header}</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -214,27 +214,27 @@ export default class LegacyRoomHeaderButtons extends HeaderButtons<IProps> {
|
|||
const currentPhase = RightPanelStore.instance.currentCard.phase;
|
||||
if (currentPhase && ROOM_INFO_PHASES.includes(currentPhase)) {
|
||||
if (this.state.phase === currentPhase) {
|
||||
RightPanelStore.instance.showOrHidePanel(currentPhase);
|
||||
RightPanelStore.instance.showOrHidePhase(currentPhase);
|
||||
} else {
|
||||
RightPanelStore.instance.showOrHidePanel(currentPhase, RightPanelStore.instance.currentCard.state);
|
||||
RightPanelStore.instance.showOrHidePhase(currentPhase, RightPanelStore.instance.currentCard.state);
|
||||
}
|
||||
} else {
|
||||
// This toggles for us, if needed
|
||||
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.RoomSummary);
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.RoomSummary);
|
||||
}
|
||||
};
|
||||
|
||||
private onNotificationsClicked = (): void => {
|
||||
// This toggles for us, if needed
|
||||
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.NotificationPanel);
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.NotificationPanel);
|
||||
};
|
||||
|
||||
private onPinnedMessagesClicked = (): void => {
|
||||
// This toggles for us, if needed
|
||||
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.PinnedMessages);
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.PinnedMessages);
|
||||
};
|
||||
private onTimelineCardClicked = (): void => {
|
||||
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.Timeline);
|
||||
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.Timeline);
|
||||
};
|
||||
|
||||
private onThreadsPanelClicked = (ev: ButtonEvent): void => {
|
||||
|
|
86
src/components/views/right_panel/RightPanelTabs.tsx
Normal file
86
src/components/views/right_panel/RightPanelTabs.tsx
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Copyright 2024 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.
|
||||
*/
|
||||
|
||||
import React, { useRef } from "react";
|
||||
import { NavBar, NavItem } from "@vector-im/compound-web";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { useDispatcher } from "../../../hooks/useDispatcher";
|
||||
import dispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
|
||||
function shouldShowTabsForPhase(phase?: RightPanelPhases): boolean {
|
||||
const tabs = [RightPanelPhases.RoomSummary, RightPanelPhases.RoomMemberList, RightPanelPhases.ThreadPanel];
|
||||
return !!phase && tabs.includes(phase);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
phase: RightPanelPhases;
|
||||
};
|
||||
|
||||
export const RightPanelTabs: React.FC<Props> = ({ phase }): JSX.Element | null => {
|
||||
const threadsTabRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
useDispatcher(dispatcher, (payload) => {
|
||||
// This actually focuses the threads tab, as its the only interactive element,
|
||||
// but at least it puts the user in the right area of the app.
|
||||
if (payload.action === Action.FocusThreadsPanel) {
|
||||
threadsTabRef.current?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
if (!shouldShowTabsForPhase(phase)) return null;
|
||||
|
||||
return (
|
||||
<NavBar className="mx_RightPanelTabs" aria-label="right panel" role="tablist">
|
||||
<NavItem
|
||||
aria-controls="room-summary-panel"
|
||||
id="room-summary-panel-tab"
|
||||
onClick={() => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomSummary }, true);
|
||||
}}
|
||||
active={phase === RightPanelPhases.RoomSummary}
|
||||
>
|
||||
{_t("right_panel|info")}
|
||||
</NavItem>
|
||||
<NavItem
|
||||
aria-controls="memberlist-panel"
|
||||
id="memberlist-panel-tab"
|
||||
onClick={(ev: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, true);
|
||||
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoPeopleButton", ev);
|
||||
}}
|
||||
active={phase === RightPanelPhases.RoomMemberList}
|
||||
>
|
||||
{_t("common|people")}
|
||||
</NavItem>
|
||||
<NavItem
|
||||
aria-controls="thread-panel"
|
||||
id="thread-panel-tab"
|
||||
onClick={() => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.ThreadPanel }, true);
|
||||
}}
|
||||
active={phase === RightPanelPhases.ThreadPanel}
|
||||
ref={threadsTabRef}
|
||||
>
|
||||
{_t("common|threads")}
|
||||
</NavItem>
|
||||
</NavBar>
|
||||
);
|
||||
};
|
|
@ -39,7 +39,6 @@ import {
|
|||
} from "@vector-im/compound-web";
|
||||
import { Icon as FavouriteIcon } from "@vector-im/compound-design-tokens/icons/favourite.svg";
|
||||
import { Icon as UserAddIcon } from "@vector-im/compound-design-tokens/icons/user-add.svg";
|
||||
import { Icon as UserProfileSolidIcon } from "@vector-im/compound-design-tokens/icons/user-profile-solid.svg";
|
||||
import { Icon as LinkIcon } from "@vector-im/compound-design-tokens/icons/link.svg";
|
||||
import { Icon as SettingsIcon } from "@vector-im/compound-design-tokens/icons/settings.svg";
|
||||
import { Icon as ExportArchiveIcon } from "@vector-im/compound-design-tokens/icons/export-archive.svg";
|
||||
|
@ -106,7 +105,6 @@ import { useTransition } from "../../../hooks/useTransition";
|
|||
interface IProps {
|
||||
room: Room;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
onClose(): void;
|
||||
onSearchChange?: (e: ChangeEvent) => void;
|
||||
onSearchCancel?: () => void;
|
||||
focusRoomSearch?: boolean;
|
||||
|
@ -382,7 +380,6 @@ const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null
|
|||
const RoomSummaryCard: React.FC<IProps> = ({
|
||||
room,
|
||||
permalinkCreator,
|
||||
onClose,
|
||||
onSearchChange,
|
||||
onSearchCancel,
|
||||
focusRoomSearch,
|
||||
|
@ -416,11 +413,6 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||
});
|
||||
};
|
||||
|
||||
const onRoomMembersClick = (ev: Event): void => {
|
||||
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, true);
|
||||
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoPeopleButton", ev);
|
||||
};
|
||||
|
||||
const isRoomEncrypted = useIsEncrypted(cli, room);
|
||||
const roomContext = useContext(RoomContext);
|
||||
const e2eStatus = roomContext.e2eStatus;
|
||||
|
@ -532,7 +524,13 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
|
||||
|
||||
return (
|
||||
<BaseCard header={null} className="mx_RoomSummaryCard" onClose={onClose}>
|
||||
<BaseCard
|
||||
hideHeaderButtons
|
||||
id="room-summary-panel"
|
||||
className="mx_RoomSummaryCard"
|
||||
ariaLabelledBy="room-summary-panel-tab"
|
||||
role="tabpanel"
|
||||
>
|
||||
<Flex
|
||||
as="header"
|
||||
className="mx_RoomSummaryCard_header"
|
||||
|
@ -558,12 +556,6 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||
/>
|
||||
</Form.Root>
|
||||
)}
|
||||
<AccessibleButton
|
||||
data-testid="base-card-close-button"
|
||||
className="mx_BaseCard_close"
|
||||
onClick={onClose}
|
||||
title={_t("action|close")}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
{header}
|
||||
|
@ -589,13 +581,6 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={onRoomSettingsClick} />
|
||||
|
||||
<Separator />
|
||||
<MenuItem
|
||||
// this icon matches the legacy implementation
|
||||
// and is a short term solution until legacy room header is removed
|
||||
Icon={UserProfileSolidIcon}
|
||||
label={_t("common|people")}
|
||||
onSelect={onRoomMembersClick}
|
||||
/>
|
||||
{!isVideoRoom && (
|
||||
<>
|
||||
<MenuItem Icon={FilesIcon} label={_t("right_panel|files_button")} onSelect={onRoomFilesClick} />
|
||||
|
|
|
@ -80,7 +80,7 @@ import { DirectoryMember, startDmOnFirstMessage } from "../../../utils/direct-me
|
|||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
import { asyncSome } from "../../../utils/arrays";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
import { SpaceScopeHeader } from "../rooms/SpaceScopeHeader";
|
||||
import { createSpaceScopeHeader } from "../rooms/SpaceScopeHeader";
|
||||
|
||||
export interface IDevice extends Device {
|
||||
ambiguous?: boolean;
|
||||
|
@ -1774,10 +1774,11 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
|||
<UserInfoHeader member={member} e2eStatus={e2eStatus} roomId={room?.roomId} />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseCard
|
||||
className={classes.join(" ")}
|
||||
header={room ? <SpaceScopeHeader room={room} /> : undefined}
|
||||
header={createSpaceScopeHeader(room)}
|
||||
onClose={onClose}
|
||||
closeLabel={closeLabel}
|
||||
cardState={cardState}
|
||||
|
|
|
@ -55,7 +55,7 @@ import { SDKContext } from "../../../contexts/SDKContext";
|
|||
import { canInviteTo } from "../../../utils/room/canInviteTo";
|
||||
import { inviteToRoom } from "../../../utils/room/inviteToRoom";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { SpaceScopeHeader } from "./SpaceScopeHeader";
|
||||
import { createSpaceScopeHeader } from "./SpaceScopeHeader";
|
||||
|
||||
const INITIAL_LOAD_NUM_MEMBERS = 30;
|
||||
const INITIAL_LOAD_NUM_INVITED = 5;
|
||||
|
@ -64,6 +64,7 @@ const SHOW_MORE_INCREMENT = 100;
|
|||
interface IProps {
|
||||
roomId: string;
|
||||
searchQuery: string;
|
||||
hideHeaderButtons?: boolean;
|
||||
onClose(): void;
|
||||
onSearchQueryChanged: (query: string) => void;
|
||||
}
|
||||
|
@ -358,7 +359,14 @@ export default class MemberList extends React.Component<IProps, IState> {
|
|||
public render(): React.ReactNode {
|
||||
if (this.state.loading) {
|
||||
return (
|
||||
<BaseCard className="mx_MemberList" onClose={this.props.onClose}>
|
||||
<BaseCard
|
||||
id="memberlist-panel"
|
||||
className="mx_MemberList"
|
||||
ariaLabelledBy="memberlist-panel-tab"
|
||||
role="tabpanel"
|
||||
hideHeaderButtons={this.props.hideHeaderButtons}
|
||||
onClose={this.props.onClose}
|
||||
>
|
||||
<Spinner />
|
||||
</BaseCard>
|
||||
);
|
||||
|
@ -415,12 +423,14 @@ export default class MemberList extends React.Component<IProps, IState> {
|
|||
/>
|
||||
);
|
||||
|
||||
const scopeHeader = room ? <SpaceScopeHeader room={room} /> : undefined;
|
||||
|
||||
return (
|
||||
<BaseCard
|
||||
id="memberlist-panel"
|
||||
className="mx_MemberList"
|
||||
header={<React.Fragment>{scopeHeader}</React.Fragment>}
|
||||
ariaLabelledBy="memberlist-panel-tab"
|
||||
role="tabpanel"
|
||||
header={createSpaceScopeHeader(room)}
|
||||
hideHeaderButtons={this.props.hideHeaderButtons}
|
||||
footer={footer}
|
||||
onClose={this.props.onClose}
|
||||
>
|
||||
|
|
|
@ -20,6 +20,7 @@ import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/v
|
|||
import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/voice-call.svg";
|
||||
import { Icon as CloseCallIcon } from "@vector-im/compound-design-tokens/icons/close.svg";
|
||||
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
|
||||
import { Icon as RoomInfoIcon } from "@vector-im/compound-design-tokens/icons/info-solid.svg";
|
||||
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications-solid.svg";
|
||||
import { Icon as VerifiedIcon } from "@vector-im/compound-design-tokens/icons/verified.svg";
|
||||
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
|
||||
|
@ -336,6 +337,17 @@ export default function RoomHeader({
|
|||
</>
|
||||
)}
|
||||
|
||||
<Tooltip label={_t("right_panel|room_summary_card|title")}>
|
||||
<IconButton
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
RightPanelStore.instance.showOrHidePanel(RightPanelPhases.RoomSummary);
|
||||
}}
|
||||
aria-label={_t("right_panel|room_summary_card|title")}
|
||||
>
|
||||
<RoomInfoIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip label={_t("common|threads")}>
|
||||
<IconButton
|
||||
indicator={notificationLevelToIndicator(threadNotifications)}
|
||||
|
|
|
@ -21,18 +21,23 @@ import { Text } from "@vector-im/compound-web";
|
|||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import { useRoomName } from "../../../hooks/useRoomName";
|
||||
|
||||
/**
|
||||
* Returns a space scope header if needed
|
||||
* @param room The room object
|
||||
* @returns rendered component if the room is a space room, otherwise returns null
|
||||
*/
|
||||
export function createSpaceScopeHeader(room?: Room | null): React.JSX.Element | null {
|
||||
if (room?.isSpaceRoom()) return <SpaceScopeHeader room={room} />;
|
||||
else return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope header used to decorate right panels that are scoped to a space.
|
||||
* When room is not a space renders nothing.
|
||||
* Otherwise renders room avatar and name.
|
||||
* It renders room avatar and name.
|
||||
*/
|
||||
export const SpaceScopeHeader: React.FC<{ room: Room }> = ({ room }) => {
|
||||
const roomName = useRoomName(room);
|
||||
|
||||
if (!room.isSpaceRoom()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
as="div"
|
||||
|
|
|
@ -28,7 +28,7 @@ import { Action } from "../../../dispatcher/actions";
|
|||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import BaseCard from "../right_panel/BaseCard";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import { SpaceScopeHeader } from "./SpaceScopeHeader";
|
||||
import { createSpaceScopeHeader } from "./SpaceScopeHeader";
|
||||
|
||||
interface IProps {
|
||||
event: MatrixEvent;
|
||||
|
@ -133,10 +133,8 @@ export default class ThirdPartyMemberInfo extends React.Component<IProps, IState
|
|||
);
|
||||
}
|
||||
|
||||
const scopeHeader: JSX.Element | undefined = this.room ? <SpaceScopeHeader room={this.room} /> : undefined;
|
||||
|
||||
return (
|
||||
<BaseCard header={scopeHeader} onClose={this.props.onClose}>
|
||||
<BaseCard header={createSpaceScopeHeader(this.room)} onClose={this.props.onClose}>
|
||||
<Flex className="mx_ThirdPartyMemberInfo" direction="column" gap="var(--cpd-space-4x)">
|
||||
<Flex direction="column" as="section" justify="start" gap="var(--cpd-space-2x)">
|
||||
{/* same as userinfo name style */}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue