Implement new memberlist header view

midhun/member-redesign-accessibility
R Midhun Suresh 2025-01-05 20:36:03 +05:30
parent 4f50174aa9
commit 6eb51b34bf
No known key found for this signature in database
2 changed files with 174 additions and 0 deletions

View File

@ -0,0 +1,37 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
.mx_MemberListHeaderView {
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-gray-400);
max-height: 112px;
.mx_MemberListHeaderView_container {
margin-top: var(--cpd-space-6x);
width: 100%;
}
.mx_MemberListHeaderView_invite_small {
margin-left: var(--cpd-space-3x);
}
.mx_MemberListHeaderView_invite_large {
width: 288px;
height: 36px;
}
.mx_MemberListHeaderView_label {
padding: var(--cpd-space-6x) 0 var(--cpd-space-2x) var(--cpd-space-4x);
box-sizing: border-box;
width: 100%;
color: var(--cpd-color-text-secondary);
font: var(--cpd-font-body-sm-semibold);
}
.mx_MemberListHeaderView_search {
width: 240px;
}
}

View File

@ -0,0 +1,137 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { Search, Text, Button, Tooltip, InlineSpinner } from "@vector-im/compound-web";
import React from "react";
import InviteIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add-solid";
import { UserAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Flex } from "../../../utils/Flex";
import { MemberListViewState } from "../../../viewmodels/memberlist/MemberListViewModel";
import { _t } from "../../../../languageHandler";
interface TooltipProps {
canInvite: boolean;
children: React.ReactNode;
}
const OptionalTooltip: React.FC<TooltipProps> = ({ canInvite, children }) => {
if (canInvite) return children;
// If the user isn't allowed to invite others to this room, wrap with a relevant tooltip.
return <Tooltip description={_t("member_list|invite_button_no_perms_tooltip")}>{children}</Tooltip>;
};
interface Props {
vm: MemberListViewState;
}
const InviteButton: React.FC<Props> = ({ vm }) => {
const shouldShowInvite = vm.shouldShowInvite;
const shouldShowSearch = vm.shouldShowSearch;
const disabled = !vm.canInvite;
if (!shouldShowInvite) {
// In this case, invite button should not be rendered.
return null;
}
if (shouldShowSearch) {
/// When rendered alongside a search box, the invite button is just an icon.
return (
<OptionalTooltip canInvite={vm.canInvite}>
<Button
className="mx_MemberListHeaderView_invite_small"
kind="primary"
onClick={vm.onInviteButtonClick}
size="sm"
iconOnly={true}
Icon={InviteIcon}
disabled={disabled}
aria-label={_t("action|invite")}
/>
</OptionalTooltip>
);
}
// Without a search box, invite button is a full size button.
return (
<OptionalTooltip canInvite={vm.canInvite}>
<Button
kind="secondary"
size="sm"
Icon={UserAddIcon}
className="mx_MemberListHeaderView_invite_large"
disabled={!vm.canInvite}
onClick={vm.onInviteButtonClick}
>
{_t("action|invite")}
</Button>
</OptionalTooltip>
);
};
/**
* This should be:
* A loading text with spinner while the memberlist loads.
* Member count of the room when there's nothing in the search field.
* Number of matching members during search or 'No result' if search found nothing.
*/
function getHeaderLabelJSX(vm: MemberListViewState): React.ReactNode {
if (vm.isLoading) {
return (
<Flex align="center" gap="8px">
<InlineSpinner /> {_t("common|loading")}
</Flex>
);
}
const filteredMemberCount = vm.members.length;
if (filteredMemberCount === 0) {
return _t("member_list|no_matches");
}
return _t("member_list|count", { count: filteredMemberCount });
}
export const MemberListHeaderView: React.FC<Props> = (props: Props) => {
const vm = props.vm;
let contentJSX: React.ReactNode;
if (vm.shouldShowSearch) {
// When we need to show the search box
contentJSX = (
<Flex justify="center" className="mx_MemberListHeaderView_container">
<Search
className="mx_MemberListHeaderView_search mx_no_textinput"
name="searchMembers"
placeholder={_t("member_list|filter_placeholder")}
onChange={(e) => vm.search((e as React.ChangeEvent<HTMLInputElement>).target.value)}
/>
<InviteButton vm={vm} />
</Flex>
);
} else if (!vm.shouldShowSearch && vm.shouldShowInvite) {
// When we don't need to show the search box but still need an invite button
contentJSX = (
<Flex justify="center" className="mx_MemberListHeaderView_container">
<InviteButton vm={vm} />
</Flex>
);
} else {
// No search box and no invite icon, so nothing to render!
contentJSX = null;
}
return (
<Flex className="mx_MemberListHeaderView" as="header" align="center" justify="space-between" direction="column">
{!vm.isLoading && contentJSX}
<Text as="div" size="sm" weight="semibold" className="mx_MemberListHeaderView_label">
{getHeaderLabelJSX(vm)}
</Text>
</Flex>
);
};