83d732d60e
* Refactor className? to component property in EncryptionEventView * Refactor extraClassNames to default react className as component property for DecryptionFailureBodyView * Refactor className to component property for MessageTimestampView * Refactor className and children to component properties for ReactionsRowButton * Refactor className to component property for DisambiguatedProfile * Refactor className to a component property in DateSeparatorView * Fix for lint errors and EncryptionEventView unsupported icon color * EncryptionEventView fix for icon color css specificity/order
145 lines
4.4 KiB
TypeScript
145 lines
4.4 KiB
TypeScript
/*
|
|
* 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, type MouseEventHandler, type KeyboardEvent, type MouseEvent } from "react";
|
|
import classNames from "classnames";
|
|
import { Tooltip } from "@vector-im/compound-web";
|
|
|
|
import styles from "./MessageTimestampView.module.css";
|
|
import { type ViewModel } from "../../viewmodel/ViewModel";
|
|
import { useViewModel } from "../../viewmodel/useViewModel";
|
|
import { useI18n } from "../../utils/i18nContext";
|
|
|
|
export interface MessageTimestampViewSnapshot {
|
|
/**
|
|
* The localized timestamp to render in the component
|
|
*/
|
|
ts: string;
|
|
/**
|
|
* The localized sent timestamp formatted as full date
|
|
*/
|
|
tsSentAt: string;
|
|
/**
|
|
* The localized received timestamp formatted as full date
|
|
* If specified will render both the sent-at and received-at timestamps in the tooltip
|
|
*/
|
|
tsReceivedAt?: string;
|
|
/**
|
|
* If set to true then no tooltip will be shown
|
|
*/
|
|
inhibitTooltip?: boolean;
|
|
/**
|
|
* If specified, will be rendered as an anchor bearing the href, a `span` element will be used otherwise
|
|
*/
|
|
href?: string;
|
|
}
|
|
|
|
export interface MessageTimestampViewActions {
|
|
/**
|
|
* Optional onClick handler to attach to the DOM element
|
|
*/
|
|
onClick?: MouseEventHandler<HTMLElement>;
|
|
/**
|
|
* Optional onContextMenu handler to attach to the DOM element
|
|
*/
|
|
onContextMenu?: MouseEventHandler<HTMLElement>;
|
|
}
|
|
|
|
/**
|
|
* The view model for the message timestamp.
|
|
*
|
|
* Snapshot data describes timestamp content and rendering behavior, while
|
|
* container styling is supplied via component props.
|
|
*/
|
|
export type MessageTimestampViewModel = ViewModel<MessageTimestampViewSnapshot> & MessageTimestampViewActions;
|
|
|
|
interface MessageTimestampViewProps {
|
|
/**
|
|
* The view model for the message timestamp.
|
|
*/
|
|
vm: MessageTimestampViewModel;
|
|
/**
|
|
* Optional CSS class name to apply to the component.
|
|
*/
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* Displays a message timestamp with optional tooltip details.
|
|
*
|
|
* The view model provides the timestamp values and display options. The component
|
|
* can render as a link when `href` is set, and can show both sent-at and received-at
|
|
* times in the tooltip when `tsReceivedAt` is provided. Use `className` for
|
|
* host-level styling.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <MessageTimestampView vm={messageTimestampViewModel} className="mx_MessageTimestamp" />
|
|
* ```
|
|
*/
|
|
export function MessageTimestampView({ vm, className }: Readonly<MessageTimestampViewProps>): JSX.Element {
|
|
const { translate: _t } = useI18n();
|
|
|
|
const { ts, tsSentAt, tsReceivedAt, inhibitTooltip, href } = useViewModel(vm);
|
|
|
|
const onKeyDown = (event: KeyboardEvent<HTMLElement>): void => {
|
|
if (vm.onClick) {
|
|
if (event.key === "Enter" || event.key === " ") {
|
|
event.preventDefault();
|
|
vm.onClick?.(event as unknown as MouseEvent<HTMLElement>);
|
|
}
|
|
}
|
|
};
|
|
|
|
let label = tsSentAt;
|
|
let caption: string | undefined;
|
|
if (tsReceivedAt && tsReceivedAt?.length > 0) {
|
|
label = _t("timeline|message_timestamp_sent_at", { dateTime: label });
|
|
caption = _t("timeline|message_timestamp_received_at", {
|
|
dateTime: tsReceivedAt,
|
|
});
|
|
}
|
|
|
|
let content;
|
|
if (href) {
|
|
content = (
|
|
<a
|
|
href={href}
|
|
onClick={vm.onClick}
|
|
onKeyDown={onKeyDown}
|
|
onContextMenu={vm.onContextMenu}
|
|
className={classNames(className, styles.content)}
|
|
aria-live="off"
|
|
>
|
|
{ts}
|
|
</a>
|
|
);
|
|
} else {
|
|
content = (
|
|
<span
|
|
onClick={vm.onClick}
|
|
onKeyDown={onKeyDown}
|
|
onContextMenu={vm.onContextMenu}
|
|
className={classNames(className, styles.content)}
|
|
role={vm.onClick ? "link" : undefined}
|
|
aria-live="off"
|
|
tabIndex={vm.onClick || !inhibitTooltip ? 0 : undefined}
|
|
>
|
|
{ts}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
if (inhibitTooltip) return content;
|
|
|
|
return (
|
|
<Tooltip description={label} caption={caption}>
|
|
{content}
|
|
</Tooltip>
|
|
);
|
|
}
|