Improve invite dialog ui - Part 2 (#30836)

* feat: add `Pill` component

* chore: add `react-merge-refs` lib

* feat: add `PillInput` component

* feat: use new pills component in invite dialog

* test: update invite dialog selector

* test(e2e): update test locators

* test(e2e): update screenshot
This commit is contained in:
Florian Duros
2025-10-01 11:03:43 +02:00
committed by GitHub
parent 3d5749bfc7
commit 9cecd52477
29 changed files with 520 additions and 151 deletions

View File

@@ -12,7 +12,6 @@ import { KnownMembership } from "matrix-js-sdk/src/types";
import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import { logger } from "matrix-js-sdk/src/logger";
import { uniqBy } from "lodash";
import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Icon as EmailPillAvatarIcon } from "../../../../res/img/icon-email-pill-avatar.svg";
import { _t, _td } from "../../../languageHandler";
@@ -66,6 +65,8 @@ import { type UserProfilesStore } from "../../../stores/UserProfilesStore";
import InviteProgressBody from "./InviteProgressBody.tsx";
import { RichList } from "../../../shared-components/rich-list/RichList";
import { RichItem } from "../../../shared-components/rich-list/RichItem";
import { PillInput } from "../../../shared-components/pill-input/PillInput";
import { Pill } from "../../../shared-components/pill-input/Pill";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */
@@ -121,27 +122,10 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
const avatarSize = "20px";
const avatar = <SearchResultAvatar user={this.props.member} size={avatarSize} />;
let closeButton;
if (this.props.onRemove) {
closeButton = (
<AccessibleButton
className="mx_InviteDialog_userTile_remove"
onClick={this.onRemove}
aria-label={_t("action|remove")}
>
<CloseIcon width="16px" height="16px" />
</AccessibleButton>
);
}
return (
<span className="mx_InviteDialog_userTile">
<span className="mx_InviteDialog_userTile_pill">
{avatar}
<span className="mx_InviteDialog_userTile_name">{this.props.member.name}</span>
</span>
{closeButton}
</span>
<Pill label={this.props.member.name} onClick={this.onRemove}>
{avatar}
</Pill>
);
}
}
@@ -609,13 +593,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Backspace:
if (value || this.state.targets.length <= 0) break;
// when the field is empty and the user hits backspace remove the right-most target
this.removeMember(this.state.targets[this.state.targets.length - 1]);
handled = true;
break;
case KeyBindingAction.Space:
if (!value || !value.includes("@") || value.includes(" ")) break;
@@ -908,16 +885,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
}
};
private onClickInputArea = (e: React.MouseEvent): void => {
// Stop the browser from highlighting text
e.preventDefault();
e.stopPropagation();
if (this.editorRef && this.editorRef.current) {
this.editorRef.current.focus();
}
};
private onUseDefaultIdentityServerClick = (e: ButtonEvent): void => {
e.preventDefault();
@@ -1041,35 +1008,33 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
}
private renderEditor(): JSX.Element {
const hasPlaceholder =
this.props.kind == InviteKind.CallTransfer &&
this.state.targets.length === 0 &&
this.state.filterText.length === 0;
const targets = this.state.targets.map((t) => (
<DMUserTile member={t} onRemove={this.state.busy ? undefined : this.removeMember} key={t.userId} />
));
const input = (
<input
type="text"
onKeyDown={this.onKeyDown}
onChange={this.updateFilter}
value={this.state.filterText}
ref={this.editorRef}
onPaste={this.onPaste}
autoFocus={true}
disabled={
this.state.busy || (this.props.kind == InviteKind.CallTransfer && this.state.targets.length > 0)
}
autoComplete="off"
placeholder={hasPlaceholder ? _t("action|search") : undefined}
data-testid="invite-dialog-input"
/>
);
return (
<div className="mx_InviteDialog_editor" onClick={this.onClickInputArea}>
<PillInput
data-testid="invite-dialog-input-wrapper"
className="mx_InviteDialog_editor"
inputProps={{
"ref": this.editorRef,
"value": this.state.filterText,
"onKeyDown": this.onKeyDown,
"onChange": this.updateFilter,
"onPaste": this.onPaste,
"placeholder": _t("action|search"),
"autoFocus": true,
"disabled":
this.state.busy ||
(this.props.kind == InviteKind.CallTransfer && this.state.targets.length > 0),
"data-testid": "invite-dialog-input",
}}
onRemoveChildren={() =>
!this.state.busy && this.removeMember(this.state.targets[this.state.targets.length - 1])
}
>
{targets}
{input}
</div>
</PillInput>
);
}