Skip to content

Commit 7a39c88

Browse files
committed
feat: add room list search to search components
1 parent 82896fc commit 7a39c88

File tree

5 files changed

+263
-0
lines changed

5 files changed

+263
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
/*
9+
* Copyright 2025 New Vector Ltd.
10+
*
11+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
12+
* Please see LICENSE files in the repository root for full details.
13+
*/
14+
15+
.container {
16+
/* From figma, this should be aligned with the room header */
17+
flex: 0 0 64px;
18+
box-sizing: border-box;
19+
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary);
20+
padding: 0 var(--cpd-space-3x);
21+
22+
svg {
23+
fill: var(--cpd-color-icon-secondary);
24+
}
25+
}
26+
27+
.searchBar {
28+
/* The search button should take all the remaining space */
29+
flex: 1;
30+
font: var(--cpd-font-body-md-regular);
31+
color: var(--cpd-color-text-secondary);
32+
min-width: 0;
33+
34+
span {
35+
flex: 1;
36+
37+
kbd {
38+
font-family: inherit;
39+
}
40+
41+
/* Shrink and truncate the search text */
42+
white-space: nowrap;
43+
overflow: hidden;
44+
45+
}
46+
}
47+
48+
.searchText {
49+
min-width: 0;
50+
white-space: nowrap;
51+
overflow: hidden;
52+
text-overflow: ellipsis;
53+
text-align: start;
54+
}
55+
56+
.button:hover {
57+
svg {
58+
fill: var(--cpd-color-icon-primary);
59+
}
60+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import React, { type JSX, type MouseEventHandler, useMemo } from "react";
9+
import { fn } from "storybook/test";
10+
import { type Meta, type StoryFn } from "@storybook/react-vite";
11+
12+
import {
13+
RoomListSearch as RoomListSearchComponent,
14+
type RoomListSearchSnapshot,
15+
type RoomListSearchViewModel,
16+
} from "./RoomListSearch";
17+
import { MockViewModel } from "../MockViewModel";
18+
19+
interface Props extends RoomListSearchSnapshot {
20+
onSearchClick: MouseEventHandler<HTMLButtonElement>;
21+
onDialPadClick: MouseEventHandler<HTMLButtonElement>;
22+
onExploreClick: MouseEventHandler<HTMLButtonElement>;
23+
}
24+
25+
function Wrapper(props: Props): JSX.Element {
26+
const vm = useMemo(() => {
27+
const { displayExploreButton, displayDialButton, ...actions } = props;
28+
const viewModel = new MockViewModel({
29+
displayExploreButton,
30+
displayDialButton,
31+
}) as unknown as RoomListSearchViewModel;
32+
Object.assign(viewModel, actions);
33+
34+
return viewModel;
35+
}, [props]);
36+
return <RoomListSearchComponent vm={vm} />;
37+
}
38+
39+
export default {
40+
title: "RoomList/RoomListSearch",
41+
component: Wrapper,
42+
tags: ["autodocs"],
43+
args: {
44+
displayExploreButton: true,
45+
displayDialButton: true,
46+
onSearchClick: fn(),
47+
onDialPadClick: fn(),
48+
onExploreClick: fn(),
49+
},
50+
decorators: [
51+
(Story) => (
52+
<div style={{ display: "flex", flexDirection: "column" }}>
53+
<Story />
54+
</div>
55+
),
56+
],
57+
} satisfies Meta<typeof Wrapper>;
58+
59+
const Template: StoryFn<typeof Wrapper> = (args) => <Wrapper {...args} />;
60+
61+
export const Default = Template.bind({});
62+
63+
export const HideExploreButton = Template.bind({});
64+
HideExploreButton.args = {
65+
displayExploreButton: false,
66+
};
67+
68+
export const HideDialButton = Template.bind({});
69+
HideDialButton.args = {
70+
displayDialButton: false,
71+
};
72+
73+
export const HideAllButtons = Template.bind({});
74+
HideAllButtons.args = {
75+
displayExploreButton: false,
76+
displayDialButton: false,
77+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { composeStories } from "@storybook/react-vite";
9+
import { render } from "jest-matrix-react";
10+
import React from "react";
11+
12+
import * as stories from "./RoomListSearch.stories.tsx";
13+
14+
const { Default, HideExploreButton, HideDialButton, HideAllButtons } = composeStories(stories);
15+
16+
describe("RoomListSearch", () => {
17+
it("renders the search bar with all the buttons", () => {
18+
const { container } = render(<Default />);
19+
expect(container).toMatchSnapshot();
20+
});
21+
22+
it("renders the search bar with the explore button hidden", () => {
23+
const { container } = render(<HideExploreButton />);
24+
expect(container).toMatchSnapshot();
25+
});
26+
27+
it("renders the search bar with the dial button hidden", () => {
28+
const { container } = render(<HideDialButton />);
29+
expect(container).toMatchSnapshot();
30+
});
31+
32+
it("renders the search bar with all the button hidden", () => {
33+
const { container } = render(<HideAllButtons />);
34+
expect(container).toMatchSnapshot();
35+
});
36+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import React, { type JSX, type MouseEventHandler } from "react";
9+
import { Button } from "@vector-im/compound-web";
10+
import ExploreIcon from "@vector-im/compound-design-tokens/assets/web/icons/explore";
11+
import SearchIcon from "@vector-im/compound-design-tokens/assets/web/icons/search";
12+
import DialPadIcon from "@vector-im/compound-design-tokens/assets/web/icons/dial-pad";
13+
14+
import { Flex } from "../Flex/Flex";
15+
import { IS_MAC, Key } from "../Keyboard";
16+
import styles from "./RoomListSearch.module.css";
17+
import { _t } from "../i18n";
18+
import type { ViewModel } from "../ViewModel";
19+
import { useViewModel } from "../useViewModel";
20+
import { ALTERNATE_KEY_NAME } from "../KeyboardShortcuts";
21+
22+
export interface RoomListSearchSnapshot {
23+
displayExploreButton: boolean;
24+
displayDialButton: boolean;
25+
}
26+
27+
export interface RoomListSearchViewModel extends ViewModel<RoomListSearchSnapshot> {
28+
onSearchClick: MouseEventHandler<HTMLButtonElement>;
29+
onDialPadClick: MouseEventHandler<HTMLButtonElement>;
30+
onExploreClick: MouseEventHandler<HTMLButtonElement>;
31+
}
32+
33+
interface RoomListSearchProps {
34+
vm: RoomListSearchViewModel;
35+
}
36+
37+
/**
38+
* A search component to be displayed at the top of the room list
39+
* The `Explore` button is displayed only in the Home meta space and when UIComponent.ExploreRooms is enabled.
40+
*/
41+
export function RoomListSearch({ vm }: RoomListSearchProps): JSX.Element {
42+
const { displayExploreButton, displayDialButton } = useViewModel(vm);
43+
44+
return (
45+
<Flex className={styles.container} role="search" gap="var(--cpd-space-2x)" align="center">
46+
<Button
47+
className={styles.searchBar}
48+
kind="secondary"
49+
size="sm"
50+
Icon={SearchIcon}
51+
onClick={vm.onSearchClick}
52+
>
53+
<Flex as="span" justify="space-between">
54+
<span className={styles.searchText}>{_t("action|search")}</span>
55+
<kbd>{IS_MAC ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K"}</kbd>
56+
</Flex>
57+
</Button>
58+
{displayDialButton && (
59+
<Button
60+
className={styles.button}
61+
kind="secondary"
62+
size="sm"
63+
Icon={DialPadIcon}
64+
iconOnly={true}
65+
aria-label={_t("left_panel|open_dial_pad")}
66+
onClick={vm.onDialPadClick}
67+
/>
68+
)}
69+
{displayExploreButton && (
70+
<Button
71+
className={styles.button}
72+
kind="secondary"
73+
size="sm"
74+
Icon={ExploreIcon}
75+
iconOnly={true}
76+
aria-label={_t("action|explore_rooms")}
77+
onClick={vm.onExploreClick}
78+
/>
79+
)}
80+
</Flex>
81+
);
82+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
export { RoomListSearch } from "./RoomListSearch";

0 commit comments

Comments
 (0)