Move room list search to shared components (#31502)

* refactor: move room list search to shared components

* refactor: add view model

* refactor: use view and vm in room list search component

* refactor: use room list id instead of class for landmark navigation

* refactor: remove old room list search css

* test: add screenshots test for room list search view

* test: fix e2e test using class as selector...
This commit is contained in:
Florian Duros
2025-12-11 16:43:20 +01:00
committed by GitHub
parent 23fbe9cef6
commit 5b900ab6e2
22 changed files with 901 additions and 397 deletions

View File

@@ -75,7 +75,7 @@ const landmarkToDomElementMap: Record<Landmark, () => HTMLElement | null | undef
[Landmark.ROOM_SEARCH]: () =>
SettingsStore.getValue("feature_new_room_list")
? document.querySelector<HTMLElement>(".mx_RoomListSearch_search")
? document.querySelector<HTMLElement>("#room-list-search-button")
: document.querySelector<HTMLElement>(".mx_RoomSearch"),
[Landmark.ROOM_LIST]: () =>
SettingsStore.getValue("feature_new_room_list")

View File

@@ -5,24 +5,10 @@
* Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX } from "react";
import { Button } from "@vector-im/compound-web";
import ExploreIcon from "@vector-im/compound-design-tokens/assets/web/icons/explore";
import SearchIcon from "@vector-im/compound-design-tokens/assets/web/icons/search";
import DialPadIcon from "@vector-im/compound-design-tokens/assets/web/icons/dial-pad";
import { Flex } from "@element-hq/web-shared-components";
import React, { useEffect, type JSX } from "react";
import { RoomListSearchView, useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components";
import { IS_MAC, Key } from "../../../../Keyboard";
import { _t } from "../../../../languageHandler";
import { ALTERNATE_KEY_NAME } from "../../../../accessibility/KeyboardShortcuts";
import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents";
import { UIComponent } from "../../../../settings/UIFeature";
import { MetaSpace } from "../../../../stores/spaces";
import { Action } from "../../../../dispatcher/actions";
import PosthogTrackers from "../../../../PosthogTrackers";
import defaultDispatcher from "../../../../dispatcher/dispatcher";
import { useTypedEventEmitterState } from "../../../../hooks/useEventEmitter";
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../../LegacyCallHandler";
import { RoomListSearchViewModel } from "../../../../viewmodels/room-list/RoomListSearchViewModel";
type RoomListSearchProps = {
/**
@@ -37,53 +23,10 @@ type RoomListSearchProps = {
* The `Explore` button is displayed only in the Home meta space and when UIComponent.ExploreRooms is enabled.
*/
export function RoomListSearch({ activeSpace }: RoomListSearchProps): JSX.Element {
const displayExploreButton = activeSpace === MetaSpace.Home && shouldShowComponent(UIComponent.ExploreRooms);
// We only display the dial button if the user is can make PSTN calls
const displayDialButton = useTypedEventEmitterState(
LegacyCallHandler.instance,
LegacyCallHandlerEvent.ProtocolSupport,
() => LegacyCallHandler.instance.getSupportsPstnProtocol(),
);
const vm = useCreateAutoDisposedViewModel(() => new RoomListSearchViewModel({ activeSpace }));
useEffect(() => {
vm.setActiveSpace(activeSpace);
}, [activeSpace, vm]);
return (
<Flex className="mx_RoomListSearch" role="search" gap="var(--cpd-space-2x)" align="center">
<Button
className="mx_RoomListSearch_search"
kind="secondary"
size="sm"
Icon={SearchIcon}
onClick={() => defaultDispatcher.fire(Action.OpenSpotlight)}
>
<Flex as="span" justify="space-between">
<span className="mx_RoomListSearch_search_text">{_t("action|search")}</span>
<kbd>{IS_MAC ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K"}</kbd>
</Flex>
</Button>
{displayDialButton && (
<Button
kind="secondary"
size="sm"
Icon={DialPadIcon}
iconOnly={true}
aria-label={_t("left_panel|open_dial_pad")}
onClick={(ev) => {
defaultDispatcher.fire(Action.OpenDialPad);
}}
/>
)}
{displayExploreButton && (
<Button
kind="secondary"
size="sm"
Icon={ExploreIcon}
iconOnly={true}
aria-label={_t("action|explore_rooms")}
onClick={(ev) => {
defaultDispatcher.fire(Action.ViewRoomDirectory);
PosthogTrackers.trackInteraction("WebLeftPanelExploreRoomsButton", ev);
}}
/>
)}
</Flex>
);
return <RoomListSearchView vm={vm} />;
}

View File

@@ -0,0 +1,117 @@
/*
* 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 { type MouseEvent } from "react";
import {
BaseViewModel,
type RoomListSearchViewSnapshot,
type RoomListSearchViewModel as RoomListSearchViewModelInterface,
} from "@element-hq/web-shared-components";
import { IS_MAC, Key } from "../../Keyboard";
import { _t } from "../../languageHandler";
import { ALTERNATE_KEY_NAME } from "../../accessibility/KeyboardShortcuts";
import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
import { UIComponent } from "../../settings/UIFeature";
import { MetaSpace } from "../../stores/spaces";
import { Action } from "../../dispatcher/actions";
import PosthogTrackers from "../../PosthogTrackers";
import defaultDispatcher from "../../dispatcher/dispatcher";
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../LegacyCallHandler";
export interface Props {
/**
* Current active space
* The explore button is only displayed in the Home meta space
*/
activeSpace: string;
}
/**
* ViewModel for the room list search component.
* Manages the state and actions for the search bar, dial pad, and explore buttons.
*/
export class RoomListSearchViewModel
extends BaseViewModel<RoomListSearchViewSnapshot, Props>
implements RoomListSearchViewModelInterface
{
private displayDialButton = false;
/**
* Computes the snapshot based on the current props and PSTN support state.
*/
private static readonly computeSnapshot = (
activeSpace: string,
supportsPstn: boolean,
): RoomListSearchViewSnapshot => {
const displayExploreButton = activeSpace === MetaSpace.Home && shouldShowComponent(UIComponent.ExploreRooms);
const searchShortcut = IS_MAC ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K";
return {
displayExploreButton,
displayDialButton: supportsPstn,
searchShortcut,
};
};
public constructor(props: Props) {
const supportsPstn = LegacyCallHandler.instance.getSupportsPstnProtocol();
super(props, RoomListSearchViewModel.computeSnapshot(props.activeSpace, supportsPstn));
this.displayDialButton = supportsPstn;
// Listen for changes in PSTN protocol support
this.disposables.trackListener(
LegacyCallHandler.instance,
LegacyCallHandlerEvent.ProtocolSupport,
this.onProtocolSupportChange,
);
}
/**
* Handles changes in protocol support (PSTN).
*/
private readonly onProtocolSupportChange = (): void => {
const supportsPstn = LegacyCallHandler.instance.getSupportsPstnProtocol();
this.displayDialButton = supportsPstn;
this.snapshot.set(RoomListSearchViewModel.computeSnapshot(this.props.activeSpace, supportsPstn));
};
/**
* Handles the search button click event.
* Opens the spotlight search dialog.
*/
public onSearchClick = (): void => {
defaultDispatcher.fire(Action.OpenSpotlight);
};
/**
* Handles the dial pad button click event.
* Opens the dial pad dialog.
*/
public onDialPadClick = (): void => {
defaultDispatcher.fire(Action.OpenDialPad);
};
/**
* Handles the explore button click event.
* Opens the room directory and tracks the interaction.
*/
public onExploreClick = (ev: MouseEvent<HTMLButtonElement>): void => {
defaultDispatcher.fire(Action.ViewRoomDirectory);
PosthogTrackers.trackInteraction("WebLeftPanelExploreRoomsButton", ev);
};
/**
* Sets the active space and updates the snapshot accordingly.
* @param activeSpace - The new active space ID.
*/
public setActiveSpace(activeSpace: string): void {
if (activeSpace === this.props.activeSpace) return;
this.props.activeSpace = activeSpace;
this.snapshot.set(RoomListSearchViewModel.computeSnapshot(activeSpace, this.displayDialButton));
}
}