* refactor: move `humanize` in shared components * feat: add `RichItem` component * feat: add `RichList` component * refactor: use `RichList` and `RichItem` in `InviteDialog` * fix: exclude `InviteDialog` button to css override * test: update selector in invite dialog * test(e2e): update crypto test to use correct selector * test(e2e): update invite dialog * test: add test for `humanize.ts` * fix: add space between the list and the input when the list is scrollable * test(e2e): update screenshots
97 lines
2.7 KiB
TypeScript
97 lines
2.7 KiB
TypeScript
/*
|
|
* 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 HTMLAttributes, type JSX, memo } from "react";
|
|
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
|
|
|
|
import styles from "./RichItem.module.css";
|
|
import { humanizeTime } from "../../utils/humanize";
|
|
import { Flex } from "../../utils/Flex";
|
|
|
|
export interface RichItemProps extends HTMLAttributes<HTMLButtonElement> {
|
|
/**
|
|
* Avatar to display at the start of the item
|
|
*/
|
|
avatar: React.ReactNode;
|
|
/**
|
|
* Title to display at the top of the item
|
|
*/
|
|
title: string;
|
|
/**
|
|
* Description to display below the title
|
|
*/
|
|
description: string;
|
|
/**
|
|
* Timestamp to display at the end of the item
|
|
* The value is humanized (e.g. "5 minutes ago")
|
|
*/
|
|
timestamp?: number;
|
|
/**
|
|
* Whether the item is selected
|
|
* This will replace the avatar with a checkmark
|
|
* @default false
|
|
*/
|
|
selected?: boolean;
|
|
}
|
|
|
|
/**
|
|
* A rich item to display in a list, with an avatar, title, description and optional timestamp.
|
|
* If selected, the avatar is replaced with a checkmark.
|
|
* A separator is added between items in a list.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <RichItem
|
|
* avatar={<AvatarComponent />}
|
|
* title="Rich Item Title"
|
|
* description="This is a description of the rich item."
|
|
* timestamp={Date.now() - 5 * 60 * 1000} // 5 minutes ago
|
|
* selected={true}
|
|
* onClick={() => console.log("Item clicked")}
|
|
* />
|
|
* ```
|
|
*/
|
|
export const RichItem = memo(function RichItem({
|
|
avatar,
|
|
title,
|
|
description,
|
|
timestamp,
|
|
selected,
|
|
...props
|
|
}: RichItemProps): JSX.Element {
|
|
return (
|
|
<button
|
|
className={styles.richItem}
|
|
type="button"
|
|
role="option"
|
|
aria-selected={selected}
|
|
aria-label={title}
|
|
{...props}
|
|
>
|
|
{selected ? <Checkmark /> : <Flex className={styles.avatar}>{avatar}</Flex>}
|
|
<span className={styles.title}>{title}</span>
|
|
<span className={styles.description}>{description}</span>
|
|
{timestamp && (
|
|
<span role="timer" className={styles.timestamp}>
|
|
{humanizeTime(timestamp)}
|
|
</span>
|
|
)}
|
|
</button>
|
|
);
|
|
});
|
|
|
|
/**
|
|
* A checkmark icon inside a circle, used to indicate selection.
|
|
*/
|
|
function Checkmark(): JSX.Element {
|
|
return (
|
|
<Flex align="center" justify="center" aria-hidden="true" className={styles.checkmark}>
|
|
<CheckIcon width="24px" height="24px" color="var(--cpd-color-icon-on-solid-primary)" />
|
|
</Flex>
|
|
);
|
|
}
|