First step to add header to new room list (#29320)
* feat: create new header * test: add tests to view model * test: add tests to view * feat: add header to new room list * test(e2e): update RoomListView snapshot * test(e2e): add tests for room list header * refactor: minor code improvement
This commit is contained in:
128
src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx
Normal file
128
src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||
import { UIComponent } from "../../../settings/UIFeature";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { useEventEmitterState, useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import {
|
||||
getMetaSpaceName,
|
||||
type MetaSpace,
|
||||
type SpaceKey,
|
||||
UPDATE_HOME_BEHAVIOUR,
|
||||
UPDATE_SELECTED_SPACE,
|
||||
} from "../../../stores/spaces";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
|
||||
/**
|
||||
* Hook to get the active space and its title.
|
||||
*/
|
||||
function useSpace(): { activeSpace: Room | null; title: string } {
|
||||
const [spaceKey, activeSpace] = useEventEmitterState<[SpaceKey, Room | null]>(
|
||||
SpaceStore.instance,
|
||||
UPDATE_SELECTED_SPACE,
|
||||
() => [SpaceStore.instance.activeSpace, SpaceStore.instance.activeSpaceRoom],
|
||||
);
|
||||
const spaceName = useTypedEventEmitterState(activeSpace ?? undefined, RoomEvent.Name, () => activeSpace?.name);
|
||||
const allRoomsInHome = useEventEmitterState(
|
||||
SpaceStore.instance,
|
||||
UPDATE_HOME_BEHAVIOUR,
|
||||
() => SpaceStore.instance.allRoomsInHome,
|
||||
);
|
||||
|
||||
const title = spaceName ?? getMetaSpaceName(spaceKey as MetaSpace, allRoomsInHome);
|
||||
|
||||
return {
|
||||
activeSpace,
|
||||
title,
|
||||
};
|
||||
}
|
||||
|
||||
export interface RoomListHeaderViewState {
|
||||
/**
|
||||
* The title of the room list
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Whether to display the compose menu
|
||||
* True if the user can create rooms and is not in a Space
|
||||
*/
|
||||
displayComposeMenu: boolean;
|
||||
/**
|
||||
* Whether the user can create rooms
|
||||
*/
|
||||
canCreateRoom: boolean;
|
||||
/**
|
||||
* Whether the user can create video rooms
|
||||
*/
|
||||
canCreateVideoRoom: boolean;
|
||||
/**
|
||||
* Create a chat room
|
||||
* @param e - The click event
|
||||
*/
|
||||
createChatRoom: (e: Event) => void;
|
||||
/**
|
||||
* Create a room
|
||||
* @param e - The click event
|
||||
*/
|
||||
createRoom: (e: Event) => void;
|
||||
/**
|
||||
* Create a video room
|
||||
*/
|
||||
createVideoRoom: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* View model for the RoomListHeader.
|
||||
* The actions don't work when called in a space yet.
|
||||
*/
|
||||
export function useRoomListHeaderViewModel(): RoomListHeaderViewState {
|
||||
const { activeSpace, title } = useSpace();
|
||||
|
||||
const canCreateRoom = shouldShowComponent(UIComponent.CreateRooms);
|
||||
const canCreateVideoRoom = useFeatureEnabled("feature_video_rooms");
|
||||
// Temporary: don't display the compose menu when in a Space
|
||||
const displayComposeMenu = canCreateRoom && !activeSpace;
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createChatRoom = useCallback((e: Event) => {
|
||||
defaultDispatcher.fire(Action.CreateChat);
|
||||
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e);
|
||||
}, []);
|
||||
|
||||
const createRoom = useCallback((e: Event) => {
|
||||
defaultDispatcher.fire(Action.CreateRoom);
|
||||
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e);
|
||||
}, []);
|
||||
|
||||
const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms");
|
||||
const createVideoRoom = useCallback(
|
||||
() =>
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.CreateRoom,
|
||||
type: elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo,
|
||||
}),
|
||||
[elementCallVideoRoomsEnabled],
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
displayComposeMenu,
|
||||
canCreateRoom,
|
||||
canCreateVideoRoom,
|
||||
createChatRoom,
|
||||
createRoom,
|
||||
createVideoRoom,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
import React, { type JSX, useState } from "react";
|
||||
import { IconButton, Menu, MenuItem } from "@vector-im/compound-web";
|
||||
import ComposeIcon from "@vector-im/compound-design-tokens/assets/web/icons/compose";
|
||||
import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add";
|
||||
import RoomIcon from "@vector-im/compound-design-tokens/assets/web/icons/room";
|
||||
import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call";
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { Flex } from "../../../utils/Flex";
|
||||
import {
|
||||
type RoomListHeaderViewState,
|
||||
useRoomListHeaderViewModel,
|
||||
} from "../../../viewmodels/roomlist/RoomListHeaderViewModel";
|
||||
|
||||
/**
|
||||
* The header view for the room list
|
||||
* The space name is displayed and a compose menu is shown if the user can create rooms
|
||||
*/
|
||||
export function RoomListHeaderView(): JSX.Element {
|
||||
const vm = useRoomListHeaderViewModel();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
as="header"
|
||||
className="mx_RoomListHeaderView"
|
||||
aria-label={_t("room|context_menu|title")}
|
||||
justify="space-between"
|
||||
align="center"
|
||||
data-testid="room-list-header"
|
||||
>
|
||||
<h1>{vm.title}</h1>
|
||||
{vm.displayComposeMenu && <ComposeMenu vm={vm} />}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
interface ComposeMenuProps {
|
||||
/**
|
||||
* The view model for the room list header
|
||||
*/
|
||||
vm: RoomListHeaderViewState;
|
||||
}
|
||||
|
||||
/**
|
||||
* The compose menu for the room list header
|
||||
*/
|
||||
function ComposeMenu({ vm }: ComposeMenuProps): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
showTitle={false}
|
||||
title={_t("action|open_menu")}
|
||||
side="right"
|
||||
align="start"
|
||||
trigger={
|
||||
<IconButton aria-label={_t("action|add")}>
|
||||
<ComposeIcon />
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<MenuItem Icon={UserAddIcon} label={_t("action|new_message")} onSelect={vm.createChatRoom} />
|
||||
{vm.canCreateRoom && <MenuItem Icon={RoomIcon} label={_t("action|new_room")} onSelect={vm.createRoom} />}
|
||||
{vm.canCreateVideoRoom && (
|
||||
<MenuItem Icon={VideoCallIcon} label={_t("action|new_video_room")} onSelect={vm.createVideoRoom} />
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import React from "react";
|
||||
import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents";
|
||||
import { UIComponent } from "../../../../settings/UIFeature";
|
||||
import { RoomListSearch } from "./RoomListSearch";
|
||||
import { RoomListHeaderView } from "./RoomListHeaderView";
|
||||
|
||||
type RoomListViewProps = {
|
||||
/**
|
||||
@@ -26,8 +27,9 @@ export const RoomListView: React.FC<RoomListViewProps> = ({ activeSpace }) => {
|
||||
const displayRoomSearch = shouldShowComponent(UIComponent.FilterContainer);
|
||||
|
||||
return (
|
||||
<div className="mx_RoomListView" data-testid="room-list-view">
|
||||
<section className="mx_RoomListView" data-testid="room-list-view">
|
||||
{displayRoomSearch && <RoomListSearch activeSpace={activeSpace} />}
|
||||
</div>
|
||||
<RoomListHeaderView />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -80,12 +80,14 @@
|
||||
"maximise": "Maximise",
|
||||
"mention": "Mention",
|
||||
"minimise": "Minimise",
|
||||
"new_message": "New message",
|
||||
"new_room": "New room",
|
||||
"new_video_room": "New video room",
|
||||
"next": "Next",
|
||||
"no": "No",
|
||||
"ok": "OK",
|
||||
"open": "Open",
|
||||
"open_menu": "Open menu",
|
||||
"pause": "Pause",
|
||||
"pin": "Pin",
|
||||
"play": "Play",
|
||||
|
||||
Reference in New Issue
Block a user