New compose UX for usernames/e164
Co-authored-by: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
parent
e69826dcc6
commit
a329189489
42 changed files with 19223 additions and 142 deletions
231
ts/components/leftPane/LeftPaneFindByPhoneNumberHelper.tsx
Normal file
231
ts/components/leftPane/LeftPaneFindByPhoneNumberHelper.tsx
Normal file
|
@ -0,0 +1,231 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ReactChild } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { LeftPaneHelper } from './LeftPaneHelper';
|
||||
import type { Row } from '../ConversationList';
|
||||
import { RowType } from '../ConversationList';
|
||||
import { SearchInput } from '../SearchInput';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { ShowConversationType } from '../../state/ducks/conversations';
|
||||
import type { ParsedE164Type } from '../../util/libphonenumberInstance';
|
||||
import { parseAndFormatPhoneNumber } from '../../util/libphonenumberInstance';
|
||||
import type { UUIDFetchStateType } from '../../util/uuidFetchState';
|
||||
import type { CountryDataType } from '../../util/getCountryData';
|
||||
import { isFetchingByE164 } from '../../util/uuidFetchState';
|
||||
import type { LookupConversationWithoutServiceIdActionsType } from '../../util/lookupConversationWithoutServiceId';
|
||||
import { Spinner } from '../Spinner';
|
||||
import { Button } from '../Button';
|
||||
import { CountryCodeSelect } from '../CountryCodeSelect';
|
||||
|
||||
export type LeftPaneFindByPhoneNumberPropsType = {
|
||||
searchTerm: string;
|
||||
regionCode: string | undefined;
|
||||
uuidFetchState: UUIDFetchStateType;
|
||||
selectedRegion: string;
|
||||
countries: ReadonlyArray<CountryDataType>;
|
||||
};
|
||||
|
||||
export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFindByPhoneNumberPropsType> {
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly phoneNumber: ParsedE164Type | undefined;
|
||||
|
||||
private readonly regionCode: string | undefined;
|
||||
|
||||
private readonly uuidFetchState: UUIDFetchStateType;
|
||||
|
||||
private readonly countries: ReadonlyArray<CountryDataType>;
|
||||
|
||||
private readonly selectedRegion: string;
|
||||
|
||||
constructor({
|
||||
searchTerm,
|
||||
regionCode,
|
||||
uuidFetchState,
|
||||
countries,
|
||||
selectedRegion,
|
||||
}: Readonly<LeftPaneFindByPhoneNumberPropsType>) {
|
||||
super();
|
||||
|
||||
this.searchTerm = searchTerm;
|
||||
this.uuidFetchState = uuidFetchState;
|
||||
this.regionCode = regionCode;
|
||||
this.countries = countries;
|
||||
this.selectedRegion = selectedRegion;
|
||||
|
||||
this.phoneNumber = parseAndFormatPhoneNumber(
|
||||
this.searchTerm,
|
||||
selectedRegion || regionCode
|
||||
);
|
||||
}
|
||||
|
||||
override getHeaderContents({
|
||||
i18n,
|
||||
startComposing,
|
||||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
startComposing: () => void;
|
||||
}>): ReactChild {
|
||||
const backButtonLabel = i18n('icu:setGroupMetadata__back-button');
|
||||
|
||||
return (
|
||||
<div className="module-left-pane__header__contents">
|
||||
<button
|
||||
aria-label={backButtonLabel}
|
||||
className="module-left-pane__header__contents__back-button"
|
||||
disabled={this.isFetching()}
|
||||
onClick={this.getBackAction({ startComposing })}
|
||||
title={backButtonLabel}
|
||||
type="button"
|
||||
/>
|
||||
<div className="module-left-pane__header__contents__text">
|
||||
{i18n('icu:LeftPaneFindByHelper__title--findByPhoneNumber')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
override getBackAction({
|
||||
startComposing,
|
||||
}: {
|
||||
startComposing: () => void;
|
||||
}): undefined | (() => void) {
|
||||
return this.isFetching() ? undefined : startComposing;
|
||||
}
|
||||
|
||||
override getSearchInput({
|
||||
i18n,
|
||||
onChangeComposeSearchTerm,
|
||||
onChangeComposeSelectedRegion,
|
||||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
onChangeComposeSearchTerm: (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => unknown;
|
||||
onChangeComposeSelectedRegion: (newRegion: string) => void;
|
||||
}>): ReactChild {
|
||||
const placeholder = i18n(
|
||||
'icu:LeftPaneFindByHelper__placeholder--findByPhoneNumber'
|
||||
);
|
||||
return (
|
||||
<div className="LeftPaneFindByPhoneNumberHelper__container">
|
||||
<CountryCodeSelect
|
||||
countries={this.countries}
|
||||
i18n={i18n}
|
||||
defaultRegion={this.regionCode ?? ''}
|
||||
value={this.selectedRegion}
|
||||
onChange={onChangeComposeSelectedRegion}
|
||||
/>
|
||||
|
||||
<SearchInput
|
||||
hasSearchIcon={false}
|
||||
disabled={this.isFetching()}
|
||||
i18n={i18n}
|
||||
moduleClassName="LeftPaneFindByPhoneNumberHelper__search-input"
|
||||
onChange={onChangeComposeSearchTerm}
|
||||
placeholder={placeholder}
|
||||
ref={focusRef}
|
||||
value={this.searchTerm}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
override getFooterContents({
|
||||
i18n,
|
||||
lookupConversationWithoutServiceId,
|
||||
showUserNotFoundModal,
|
||||
setIsFetchingUUID,
|
||||
showInbox,
|
||||
showConversation,
|
||||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
showInbox: () => void;
|
||||
showConversation: ShowConversationType;
|
||||
}> &
|
||||
LookupConversationWithoutServiceIdActionsType): ReactChild {
|
||||
return (
|
||||
<Button
|
||||
disabled={this.isLookupDisabled()}
|
||||
onClick={async () => {
|
||||
if (!this.phoneNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
const conversationId = await lookupConversationWithoutServiceId({
|
||||
showUserNotFoundModal,
|
||||
setIsFetchingUUID,
|
||||
type: 'e164',
|
||||
e164: this.phoneNumber.e164,
|
||||
phoneNumber: this.searchTerm,
|
||||
});
|
||||
|
||||
if (conversationId != null) {
|
||||
showConversation({ conversationId });
|
||||
showInbox();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{this.isFetching() ? (
|
||||
<span aria-label={i18n('icu:loading')} role="status">
|
||||
<Spinner size="20px" svgSize="small" direction="on-avatar" />
|
||||
</span>
|
||||
) : (
|
||||
i18n('icu:next2')
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
getRowCount(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getRow(): Row {
|
||||
// This puts a blank row for the footer.
|
||||
return { type: RowType.Blank };
|
||||
}
|
||||
|
||||
// This is deliberately unimplemented because these keyboard shortcuts shouldn't work in
|
||||
// the composer. The same is true for the "in direction" function below.
|
||||
getConversationAndMessageAtIndex(
|
||||
..._args: ReadonlyArray<unknown>
|
||||
): undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getConversationAndMessageInDirection(
|
||||
..._args: ReadonlyArray<unknown>
|
||||
): undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
shouldRecomputeRowHeights(_old: unknown): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
private isFetching(): boolean {
|
||||
if (this.phoneNumber != null) {
|
||||
return isFetchingByE164(this.uuidFetchState, this.phoneNumber.e164);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private isLookupDisabled(): boolean {
|
||||
if (this.isFetching()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !this.phoneNumber?.isValid;
|
||||
}
|
||||
}
|
||||
|
||||
function focusRef(el: HTMLElement | null) {
|
||||
if (el) {
|
||||
el.focus();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue