Add user status in user info right panel card (#34212)

* Add user status in user info right panel card

Adds a very simple view component to do so, which will be reused
in the DM room header.

Also comment the other status view so the difference is clear.

* Add screenshot

* Add test

* Rename snapshot
This commit is contained in:
David Baker
2026-07-10 14:45:41 +00:00
committed by GitHub
parent efb3c0090d
commit f09b27db7c
8 changed files with 112 additions and 1 deletions
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX } from "react";
import { type User, type RoomMember } from "matrix-js-sdk/src/matrix";
import { Heading, Tooltip, Text } from "@vector-im/compound-web";
import { Flex } from "@element-hq/web-shared-components";
import { Flex, StatusTextView } from "@element-hq/web-shared-components";
import { useUserfoHeaderViewModel } from "../../../viewmodels/right_panel/user_info/UserInfoHeaderViewModel";
import MemberAvatar from "../../avatars/MemberAvatar";
@@ -16,6 +16,7 @@ import { Container, type Member, type IDevice } from "../UserInfo";
import PresenceLabel from "../../rooms/PresenceLabel";
import CopyableText from "../../elements/CopyableText";
import { UserInfoHeaderVerificationView } from "./UserInfoHeaderVerificationView";
import { useUserStatus } from "../../../../hooks/useUserStatus";
export interface UserInfoHeaderViewProps {
member: Member;
@@ -33,6 +34,7 @@ export const UserInfoHeaderView: React.FC<UserInfoHeaderViewProps> = ({
const vm = useUserfoHeaderViewModel({ member, roomId });
const avatarUrl = (member as User).avatarUrl;
const displayName = (member as RoomMember).rawDisplayName;
const userStatus = useUserStatus(member.userId);
let presenceLabel: JSX.Element | undefined;
@@ -73,6 +75,7 @@ export const UserInfoHeaderView: React.FC<UserInfoHeaderViewProps> = ({
{displayName}
</Flex>
</Heading>
{userStatus && <StatusTextView status={userStatus} />}
{presenceLabel}
{vm.timezoneInfo && (
<Tooltip label={vm.timezoneInfo?.timezone ?? ""}>
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+1
View File
@@ -92,5 +92,6 @@ export * from "./core/i18n/I18nApi";
export * from "./core/utils/linkify";
export type * from "./core/userStatus.ts";
export * from "./status/SetStatusView";
export * from "./status/StatusTextView";
// MVVM
export * from "./core/viewmodel";
@@ -12,6 +12,10 @@ import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t, type UserStatus } from "..";
import styles from "./StatusPillView.module.css";
/**
* Displays a user's status message in a pill format with a button that can be used
* to clear the status.
*/
export const StatusPillView = React.forwardRef<
HTMLDivElement,
{
@@ -0,0 +1,23 @@
/*
* Copyright 2026 Element Creations 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.
*/
.statusText {
display: inline-flex;
align-items: center;
gap: var(--cpd-space-1x);
span {
font: var(--cpd-font-body-md-medium);
color: var(--cpd-color-text-secondary);
}
.menuStatusText {
overflow: hidden;
min-width: 0;
text-overflow: ellipsis;
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2026 Element Creations 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 Meta, type StoryObj } from "@storybook/react-vite";
import { StatusTextView } from "./StatusTextView";
const meta = {
title: "Status/StatusTextView",
component: StatusTextView,
tags: ["autodocs"],
args: {
status: { emoji: "🦩", text: "Flamboyant" },
},
} satisfies Meta<typeof StatusTextView>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
@@ -0,0 +1,23 @@
/*
* Copyright 2026 Element Creations 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 { composeStories } from "@storybook/react-vite";
import React from "react";
import { describe, expect, it } from "vitest";
import { render, screen } from "@test-utils";
import * as stories from "./StatusTextView.stories";
const { Default } = composeStories(stories);
describe("StatusTextView", () => {
it("should display the status text and emoji", () => {
render(<Default />);
expect(screen.getByText("Flamboyant")).toBeInTheDocument();
expect(screen.getByText("🦩")).toBeInTheDocument();
});
});
@@ -0,0 +1,33 @@
/*
* Copyright 2026 Element Creations 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 } from "react";
import { Text } from "@vector-im/compound-web";
import { type UserStatus } from "..";
import styles from "./StatusTextView.module.css";
/**
* Displays a user's status message and emoji in simple text format
*/
export const StatusTextView = React.forwardRef<
HTMLDivElement,
{
status: UserStatus;
} & React.HTMLAttributes<HTMLDivElement>
>(function StatusTextView({ status, ...props }, ref): JSX.Element {
return (
<div ref={ref} {...props} className={styles.statusText}>
<Text as="span" className={styles.menuStatusEmoji}>
{status.emoji}
</Text>
<Text as="span" className={styles.menuStatusText}>
{status.text}
</Text>
</div>
);
});