New left pane search design
This commit is contained in:
parent
babd61377b
commit
bf45182a39
24 changed files with 500 additions and 359 deletions
|
@ -35,6 +35,13 @@ const defaultConversations: Array<ConversationType> = [
|
|||
}),
|
||||
];
|
||||
|
||||
const defaultSearchProps = {
|
||||
searchConversation: undefined,
|
||||
searchDisabled: false,
|
||||
searchTerm: 'hello',
|
||||
startSearchCounter: 0,
|
||||
};
|
||||
|
||||
const defaultGroups: Array<ConversationType> = [
|
||||
getDefaultConversation({
|
||||
id: 'biking-group',
|
||||
|
@ -72,18 +79,19 @@ const pinnedConversations: Array<ConversationType> = [
|
|||
];
|
||||
|
||||
const defaultModeSpecificProps = {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox as const,
|
||||
pinnedConversations,
|
||||
conversations: defaultConversations,
|
||||
archivedConversations: defaultArchivedConversations,
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
};
|
||||
|
||||
const emptySearchResultsGroup = { isLoading: false, results: [] };
|
||||
|
||||
const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||
cantAddContactToGroup: action('cantAddContactToGroup'),
|
||||
clearConversationSearch: action('clearConversationSearch'),
|
||||
clearGroupCreationError: action('clearGroupCreationError'),
|
||||
clearSearch: action('clearSearch'),
|
||||
closeCantAddContactToGroupModal: action('closeCantAddContactToGroupModal'),
|
||||
|
@ -177,12 +185,12 @@ story.add('Inbox: no conversations', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations: [],
|
||||
conversations: [],
|
||||
archivedConversations: [],
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
@ -192,12 +200,12 @@ story.add('Inbox: only pinned conversations', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations,
|
||||
conversations: [],
|
||||
archivedConversations: [],
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
@ -207,12 +215,12 @@ story.add('Inbox: only non-pinned conversations', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations: [],
|
||||
conversations: defaultConversations,
|
||||
archivedConversations: [],
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
@ -222,12 +230,12 @@ story.add('Inbox: only archived conversations', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations: [],
|
||||
conversations: [],
|
||||
archivedConversations: defaultArchivedConversations,
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
@ -237,12 +245,12 @@ story.add('Inbox: pinned and archived conversations', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations,
|
||||
conversations: [],
|
||||
archivedConversations: defaultArchivedConversations,
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
@ -252,12 +260,12 @@ story.add('Inbox: non-pinned and archived conversations', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations: [],
|
||||
conversations: defaultConversations,
|
||||
archivedConversations: defaultArchivedConversations,
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
@ -267,12 +275,12 @@ story.add('Inbox: pinned and non-pinned conversations', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations,
|
||||
conversations: defaultConversations,
|
||||
archivedConversations: [],
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
@ -288,11 +296,11 @@ story.add('Search: no results when searching everywhere', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Search,
|
||||
conversationResults: emptySearchResultsGroup,
|
||||
contactResults: emptySearchResultsGroup,
|
||||
messageResults: emptySearchResultsGroup,
|
||||
searchTerm: 'foo bar',
|
||||
primarySendsSms: false,
|
||||
},
|
||||
})}
|
||||
|
@ -303,11 +311,11 @@ story.add('Search: no results when searching everywhere (SMS)', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Search,
|
||||
conversationResults: emptySearchResultsGroup,
|
||||
contactResults: emptySearchResultsGroup,
|
||||
messageResults: emptySearchResultsGroup,
|
||||
searchTerm: 'foo bar',
|
||||
primarySendsSms: true,
|
||||
},
|
||||
})}
|
||||
|
@ -318,12 +326,12 @@ story.add('Search: no results when searching in a conversation', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Search,
|
||||
conversationResults: emptySearchResultsGroup,
|
||||
contactResults: emptySearchResultsGroup,
|
||||
messageResults: emptySearchResultsGroup,
|
||||
searchConversationName: 'Bing Bong',
|
||||
searchTerm: 'foo bar',
|
||||
primarySendsSms: false,
|
||||
},
|
||||
})}
|
||||
|
@ -334,11 +342,11 @@ story.add('Search: all results loading', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Search,
|
||||
conversationResults: { isLoading: true },
|
||||
contactResults: { isLoading: true },
|
||||
messageResults: { isLoading: true },
|
||||
searchTerm: 'foo bar',
|
||||
primarySendsSms: false,
|
||||
},
|
||||
})}
|
||||
|
@ -349,6 +357,7 @@ story.add('Search: some results loading', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Search,
|
||||
conversationResults: {
|
||||
isLoading: false,
|
||||
|
@ -356,7 +365,6 @@ story.add('Search: some results loading', () => (
|
|||
},
|
||||
contactResults: { isLoading: true },
|
||||
messageResults: { isLoading: true },
|
||||
searchTerm: 'foo bar',
|
||||
primarySendsSms: false,
|
||||
},
|
||||
})}
|
||||
|
@ -367,6 +375,7 @@ story.add('Search: has conversations and contacts, but not messages', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Search,
|
||||
conversationResults: {
|
||||
isLoading: false,
|
||||
|
@ -374,7 +383,6 @@ story.add('Search: has conversations and contacts, but not messages', () => (
|
|||
},
|
||||
contactResults: { isLoading: false, results: defaultConversations },
|
||||
messageResults: { isLoading: false, results: [] },
|
||||
searchTerm: 'foo bar',
|
||||
primarySendsSms: false,
|
||||
},
|
||||
})}
|
||||
|
@ -385,6 +393,7 @@ story.add('Search: all results', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Search,
|
||||
conversationResults: {
|
||||
isLoading: false,
|
||||
|
@ -398,7 +407,6 @@ story.add('Search: all results', () => (
|
|||
{ id: 'msg2', conversationId: 'bar' },
|
||||
],
|
||||
},
|
||||
searchTerm: 'foo bar',
|
||||
primarySendsSms: false,
|
||||
},
|
||||
})}
|
||||
|
@ -614,12 +622,13 @@ story.add('Captcha dialog: required', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations,
|
||||
conversations: defaultConversations,
|
||||
archivedConversations: [],
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
searchTerm: '',
|
||||
},
|
||||
challengeStatus: 'required',
|
||||
})}
|
||||
|
@ -630,12 +639,13 @@ story.add('Captcha dialog: pending', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations,
|
||||
conversations: defaultConversations,
|
||||
archivedConversations: [],
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
searchTerm: '',
|
||||
},
|
||||
challengeStatus: 'pending',
|
||||
})}
|
||||
|
@ -648,12 +658,13 @@ story.add('Crash report dialog', () => (
|
|||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations,
|
||||
conversations: defaultConversations,
|
||||
archivedConversations: [],
|
||||
isAboutToSearchInAConversation: false,
|
||||
startSearchCounter: 0,
|
||||
searchTerm: '',
|
||||
},
|
||||
crashReportCount: 42,
|
||||
})}
|
||||
|
@ -715,3 +726,20 @@ story.add('Group Metadata: Custom Timer', () => (
|
|||
})}
|
||||
/>
|
||||
));
|
||||
|
||||
story.add('Searching Conversation', () => (
|
||||
<LeftPane
|
||||
{...useProps({
|
||||
modeSpecificProps: {
|
||||
...defaultSearchProps,
|
||||
mode: LeftPaneMode.Inbox,
|
||||
pinnedConversations: [],
|
||||
conversations: defaultConversations,
|
||||
archivedConversations: [],
|
||||
isAboutToSearchInAConversation: false,
|
||||
searchConversation: getDefaultConversation(),
|
||||
searchTerm: '',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -96,6 +96,7 @@ export type PropsType = {
|
|||
|
||||
// Action Creators
|
||||
cantAddContactToGroup: (conversationId: string) => void;
|
||||
clearConversationSearch: () => void;
|
||||
clearGroupCreationError: () => void;
|
||||
clearSearch: () => void;
|
||||
closeCantAddContactToGroupModal: () => void;
|
||||
|
@ -151,6 +152,7 @@ export const LeftPane: React.FC<PropsType> = ({
|
|||
cantAddContactToGroup,
|
||||
challengeStatus,
|
||||
crashReportCount,
|
||||
clearConversationSearch,
|
||||
clearGroupCreationError,
|
||||
clearSearch,
|
||||
closeCantAddContactToGroupModal,
|
||||
|
@ -461,7 +463,9 @@ export const LeftPane: React.FC<PropsType> = ({
|
|||
]);
|
||||
|
||||
const preRowsNode = helper.getPreRowsNode({
|
||||
clearConversationSearch,
|
||||
clearGroupCreationError,
|
||||
clearSearch,
|
||||
closeCantAddContactToGroupModal,
|
||||
closeMaximumGroupSizeModal,
|
||||
closeRecommendedGroupSizeModal,
|
||||
|
@ -470,14 +474,11 @@ export const LeftPane: React.FC<PropsType> = ({
|
|||
composeSaveAvatarToDisk,
|
||||
createGroup,
|
||||
i18n,
|
||||
setComposeGroupAvatar,
|
||||
setComposeGroupName,
|
||||
setComposeGroupExpireTimer,
|
||||
toggleComposeEditingAvatar,
|
||||
onChangeComposeSearchTerm: event => {
|
||||
setComposeSearchTerm(event.target.value);
|
||||
},
|
||||
removeSelectedContact: toggleConversationInChooseMembers,
|
||||
setComposeGroupAvatar,
|
||||
setComposeGroupExpireTimer,
|
||||
setComposeGroupName,
|
||||
toggleComposeEditingAvatar,
|
||||
});
|
||||
const footerContents = helper.getFooterContents({
|
||||
createGroup,
|
||||
|
@ -550,14 +551,21 @@ export const LeftPane: React.FC<PropsType> = ({
|
|||
{/* eslint-enable jsx-a11y/no-static-element-interactions */}
|
||||
<div className="module-left-pane__header">
|
||||
{helper.getHeaderContents({
|
||||
clearSearch,
|
||||
i18n,
|
||||
showInbox,
|
||||
startComposing,
|
||||
showChooseGroupMembers,
|
||||
updateSearchTerm,
|
||||
}) || renderMainHeader()}
|
||||
</div>
|
||||
{helper.getSearchInput({
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
i18n,
|
||||
onChangeComposeSearchTerm: event => {
|
||||
setComposeSearchTerm(event.target.value);
|
||||
},
|
||||
updateSearchTerm,
|
||||
})}
|
||||
{renderExpiredBuildDialog({ containerWidthBreakpoint: widthBreakpoint })}
|
||||
{renderRelinkDialog({ containerWidthBreakpoint: widthBreakpoint })}
|
||||
{renderNetworkStatus({ containerWidthBreakpoint: widthBreakpoint })}
|
||||
|
|
92
ts/components/LeftPaneMainSearchInput.tsx
Normal file
92
ts/components/LeftPaneMainSearchInput.tsx
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import { LeftPaneSearchInput } from './LeftPaneSearchInput';
|
||||
import { usePrevious } from '../hooks/usePrevious';
|
||||
|
||||
export type PropsType = {
|
||||
clearConversationSearch: () => void;
|
||||
clearSearch: () => void;
|
||||
disabled?: boolean;
|
||||
i18n: LocalizerType;
|
||||
searchConversation: undefined | ConversationType;
|
||||
searchTerm: string;
|
||||
startSearchCounter: number;
|
||||
updateSearchTerm: (searchTerm: string) => void;
|
||||
};
|
||||
|
||||
export const LeftPaneMainSearchInput = ({
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
disabled,
|
||||
i18n,
|
||||
searchConversation,
|
||||
searchTerm,
|
||||
startSearchCounter,
|
||||
updateSearchTerm,
|
||||
}: PropsType): JSX.Element => {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const prevSearchConversationId = usePrevious(
|
||||
undefined,
|
||||
searchConversation?.id
|
||||
);
|
||||
const prevSearchCounter = usePrevious(startSearchCounter, startSearchCounter);
|
||||
|
||||
useEffect(() => {
|
||||
// When user chooses to search in a given conversation we focus the field for them
|
||||
if (
|
||||
searchConversation &&
|
||||
searchConversation.id !== prevSearchConversationId
|
||||
) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
// When user chooses to start a new search, we focus the field
|
||||
if (startSearchCounter !== prevSearchCounter) {
|
||||
inputRef.current?.select();
|
||||
}
|
||||
}, [
|
||||
prevSearchConversationId,
|
||||
prevSearchCounter,
|
||||
searchConversation,
|
||||
startSearchCounter,
|
||||
]);
|
||||
|
||||
return (
|
||||
<LeftPaneSearchInput
|
||||
disabled={disabled}
|
||||
i18n={i18n}
|
||||
onBlur={() => {
|
||||
if (!searchConversation && !searchTerm) {
|
||||
clearSearch();
|
||||
}
|
||||
}}
|
||||
onChangeValue={nextSearchTerm => {
|
||||
if (!nextSearchTerm) {
|
||||
if (searchConversation) {
|
||||
clearConversationSearch();
|
||||
} else {
|
||||
clearSearch();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateSearchTerm) {
|
||||
updateSearchTerm(nextSearchTerm);
|
||||
}
|
||||
}}
|
||||
onClear={() => {
|
||||
clearSearch();
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
ref={inputRef}
|
||||
searchConversation={searchConversation}
|
||||
value={searchTerm}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// Copyright 2021-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
|
@ -10,7 +10,6 @@ import { setupI18n } from '../util/setupI18n';
|
|||
import enMessages from '../../_locales/en/messages.json';
|
||||
import type { PropsType } from './MainHeader';
|
||||
import { MainHeader } from './MainHeader';
|
||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||
import { ThemeType } from '../types/Util';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
@ -23,10 +22,6 @@ const optionalText = (name: string, value: string | undefined) =>
|
|||
text(name, value || '') || undefined;
|
||||
|
||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||
searchTerm: requiredText('searchTerm', overrideProps.searchTerm),
|
||||
searchConversation: overrideProps.searchConversation,
|
||||
selectedConversation: undefined,
|
||||
startSearchCounter: 0,
|
||||
theme: ThemeType.light,
|
||||
|
||||
phoneNumber: optionalText('phoneNumber', overrideProps.phoneNumber),
|
||||
|
@ -37,9 +32,6 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
|||
|
||||
i18n,
|
||||
|
||||
updateSearchTerm: action('updateSearchTerm'),
|
||||
clearConversationSearch: action('clearConversationSearch'),
|
||||
clearSearch: action('clearSearch'),
|
||||
startUpdate: action('startUpdate'),
|
||||
|
||||
showArchivedConversations: action('showArchivedConversations'),
|
||||
|
@ -71,35 +63,6 @@ story.add('Phone Number', () => {
|
|||
return <MainHeader {...props} />;
|
||||
});
|
||||
|
||||
story.add('Search Term', () => {
|
||||
const props = createProps({
|
||||
name: 'John Smith',
|
||||
searchTerm: 'Hewwo?',
|
||||
title: 'John Smith',
|
||||
});
|
||||
|
||||
return <MainHeader {...props} />;
|
||||
});
|
||||
|
||||
story.add('Searching Conversation', () => {
|
||||
const props = createProps({
|
||||
name: 'John Smith',
|
||||
searchConversation: getDefaultConversation(),
|
||||
});
|
||||
|
||||
return <MainHeader {...props} />;
|
||||
});
|
||||
|
||||
story.add('Searching Conversation with Term', () => {
|
||||
const props = createProps({
|
||||
name: 'John Smith',
|
||||
searchTerm: 'address',
|
||||
searchConversation: getDefaultConversation(),
|
||||
});
|
||||
|
||||
return <MainHeader {...props} />;
|
||||
});
|
||||
|
||||
story.add('Update Available', () => {
|
||||
const props = createProps({ hasPendingUpdate: true });
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2018-2021 Signal Messenger, LLC
|
||||
// Copyright 2018-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
|
@ -10,39 +10,25 @@ import { Avatar } from './Avatar';
|
|||
import { AvatarPopup } from './AvatarPopup';
|
||||
import type { LocalizerType, ThemeType } from '../types/Util';
|
||||
import type { AvatarColorType } from '../types/Colors';
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import { LeftPaneSearchInput } from './LeftPaneSearchInput';
|
||||
import type { BadgeType } from '../badges/types';
|
||||
|
||||
export type PropsType = {
|
||||
searchTerm: string;
|
||||
searchConversation: undefined | ConversationType;
|
||||
startSearchCounter: number;
|
||||
selectedConversation: undefined | ConversationType;
|
||||
|
||||
// For display
|
||||
phoneNumber?: string;
|
||||
isMe?: boolean;
|
||||
name?: string;
|
||||
color?: AvatarColorType;
|
||||
disabled?: boolean;
|
||||
isVerified?: boolean;
|
||||
profileName?: string;
|
||||
title: string;
|
||||
avatarPath?: string;
|
||||
badge?: BadgeType;
|
||||
color?: AvatarColorType;
|
||||
hasPendingUpdate: boolean;
|
||||
theme: ThemeType;
|
||||
|
||||
i18n: LocalizerType;
|
||||
|
||||
updateSearchTerm: (searchTerm: string) => void;
|
||||
startUpdate: () => unknown;
|
||||
clearConversationSearch: () => void;
|
||||
clearSearch: () => void;
|
||||
isMe?: boolean;
|
||||
isVerified?: boolean;
|
||||
name?: string;
|
||||
phoneNumber?: string;
|
||||
profileName?: string;
|
||||
theme: ThemeType;
|
||||
title: string;
|
||||
|
||||
showArchivedConversations: () => void;
|
||||
startComposing: () => void;
|
||||
startUpdate: () => unknown;
|
||||
toggleProfileEditor: () => void;
|
||||
};
|
||||
|
||||
|
@ -52,35 +38,15 @@ type StateType = {
|
|||
};
|
||||
|
||||
export class MainHeader extends React.Component<PropsType, StateType> {
|
||||
private readonly inputRef: React.RefObject<HTMLInputElement>;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
|
||||
this.inputRef = React.createRef();
|
||||
|
||||
this.state = {
|
||||
showingAvatarPopup: false,
|
||||
popperRoot: null,
|
||||
};
|
||||
}
|
||||
|
||||
public override componentDidUpdate(prevProps: PropsType): void {
|
||||
const { searchConversation, startSearchCounter } = this.props;
|
||||
|
||||
// When user chooses to search in a given conversation we focus the field for them
|
||||
if (
|
||||
searchConversation &&
|
||||
searchConversation.id !== prevProps.searchConversation?.id
|
||||
) {
|
||||
this.setFocus();
|
||||
}
|
||||
// When user chooses to start a new search, we focus the field
|
||||
if (startSearchCounter !== prevProps.startSearchCounter) {
|
||||
this.setSelected();
|
||||
}
|
||||
}
|
||||
|
||||
public handleOutsideClick = ({ target }: MouseEvent): void => {
|
||||
const { popperRoot, showingAvatarPopup } = this.state;
|
||||
|
||||
|
@ -134,42 +100,6 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
}
|
||||
|
||||
private updateSearch = (searchTerm: string): void => {
|
||||
const {
|
||||
updateSearchTerm,
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
searchConversation,
|
||||
} = this.props;
|
||||
|
||||
if (!searchTerm) {
|
||||
if (searchConversation) {
|
||||
clearConversationSearch();
|
||||
} else {
|
||||
clearSearch();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateSearchTerm) {
|
||||
updateSearchTerm(searchTerm);
|
||||
}
|
||||
};
|
||||
|
||||
public clearSearch = (): void => {
|
||||
const { clearSearch } = this.props;
|
||||
clearSearch();
|
||||
this.setFocus();
|
||||
};
|
||||
|
||||
private handleInputBlur = (): void => {
|
||||
const { clearSearch, searchConversation, searchTerm } = this.props;
|
||||
if (!searchConversation && !searchTerm) {
|
||||
clearSearch();
|
||||
}
|
||||
};
|
||||
|
||||
public handleGlobalKeyDown = (event: KeyboardEvent): void => {
|
||||
const { showingAvatarPopup } = this.state;
|
||||
const { key } = event;
|
||||
|
@ -179,31 +109,16 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
};
|
||||
|
||||
public setFocus = (): void => {
|
||||
if (this.inputRef.current) {
|
||||
this.inputRef.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
public setSelected = (): void => {
|
||||
if (this.inputRef.current) {
|
||||
this.inputRef.current.select();
|
||||
}
|
||||
};
|
||||
|
||||
public override render(): JSX.Element {
|
||||
const {
|
||||
avatarPath,
|
||||
badge,
|
||||
color,
|
||||
disabled,
|
||||
hasPendingUpdate,
|
||||
i18n,
|
||||
name,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
searchConversation,
|
||||
searchTerm,
|
||||
showArchivedConversations,
|
||||
startComposing,
|
||||
startUpdate,
|
||||
|
@ -213,8 +128,6 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
} = this.props;
|
||||
const { showingAvatarPopup, popperRoot } = this.state;
|
||||
|
||||
const isSearching = Boolean(searchConversation || searchTerm.trim().length);
|
||||
|
||||
return (
|
||||
<div className="module-main-header">
|
||||
<Manager>
|
||||
|
@ -291,25 +204,13 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
|||
)
|
||||
: null}
|
||||
</Manager>
|
||||
<LeftPaneSearchInput
|
||||
disabled={disabled}
|
||||
i18n={i18n}
|
||||
onBlur={this.handleInputBlur}
|
||||
onChangeValue={this.updateSearch}
|
||||
onClear={this.clearSearch}
|
||||
ref={this.inputRef}
|
||||
searchConversation={searchConversation}
|
||||
value={searchTerm}
|
||||
<button
|
||||
aria-label={i18n('newConversation')}
|
||||
className="module-main-header__compose-icon"
|
||||
onClick={startComposing}
|
||||
title={i18n('newConversation')}
|
||||
type="button"
|
||||
/>
|
||||
{!isSearching && (
|
||||
<button
|
||||
aria-label={i18n('newConversation')}
|
||||
className="module-main-header__compose-icon"
|
||||
onClick={startComposing}
|
||||
title={i18n('newConversation')}
|
||||
type="button"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -49,15 +49,11 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
}
|
||||
|
||||
override getHeaderContents({
|
||||
clearSearch,
|
||||
i18n,
|
||||
showInbox,
|
||||
updateSearchTerm,
|
||||
}: Readonly<{
|
||||
clearSearch: () => void;
|
||||
i18n: LocalizerType;
|
||||
showInbox: () => void;
|
||||
updateSearchTerm: (query: string) => void;
|
||||
}>): ReactChild {
|
||||
return (
|
||||
<div className="module-left-pane__header__contents">
|
||||
|
@ -69,29 +65,44 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
type="button"
|
||||
/>
|
||||
<div className="module-left-pane__header__contents__text">
|
||||
{this.searchConversation ? (
|
||||
<LeftPaneSearchInput
|
||||
i18n={i18n}
|
||||
onChangeValue={newValue => {
|
||||
updateSearchTerm(newValue);
|
||||
}}
|
||||
onClear={() => {
|
||||
clearSearch();
|
||||
}}
|
||||
ref={el => {
|
||||
el?.focus();
|
||||
}}
|
||||
searchConversation={this.searchConversation}
|
||||
value={this.searchTerm}
|
||||
/>
|
||||
) : (
|
||||
i18n('archivedConversations')
|
||||
)}
|
||||
{i18n('archivedConversations')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
override getSearchInput({
|
||||
clearSearch,
|
||||
i18n,
|
||||
updateSearchTerm,
|
||||
}: Readonly<{
|
||||
clearConversationSearch: () => unknown;
|
||||
clearSearch: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
updateSearchTerm: (searchTerm: string) => unknown;
|
||||
}>): ReactChild | null {
|
||||
if (!this.searchConversation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LeftPaneSearchInput
|
||||
i18n={i18n}
|
||||
onChangeValue={newValue => {
|
||||
updateSearchTerm(newValue);
|
||||
}}
|
||||
onClear={() => {
|
||||
clearSearch();
|
||||
}}
|
||||
ref={el => {
|
||||
el?.focus();
|
||||
}}
|
||||
searchConversation={this.searchConversation}
|
||||
value={this.searchTerm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
override getBackAction({ showInbox }: { showInbox: () => void }): () => void {
|
||||
return showInbox;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// Copyright 2021-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ReactChild, ChangeEvent } from 'react';
|
||||
|
@ -105,21 +105,37 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
return startComposing;
|
||||
}
|
||||
|
||||
override getSearchInput({
|
||||
i18n,
|
||||
onChangeComposeSearchTerm,
|
||||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
onChangeComposeSearchTerm: (
|
||||
event: ChangeEvent<HTMLInputElement>
|
||||
) => unknown;
|
||||
}>): ReactChild {
|
||||
return (
|
||||
<SearchInput
|
||||
moduleClassName="module-left-pane__compose-search-form"
|
||||
onChange={onChangeComposeSearchTerm}
|
||||
placeholder={i18n('contactSearchPlaceholder')}
|
||||
ref={focusRef}
|
||||
value={this.searchTerm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
override getPreRowsNode({
|
||||
closeCantAddContactToGroupModal,
|
||||
closeMaximumGroupSizeModal,
|
||||
closeRecommendedGroupSizeModal,
|
||||
i18n,
|
||||
onChangeComposeSearchTerm,
|
||||
removeSelectedContact,
|
||||
}: Readonly<{
|
||||
closeCantAddContactToGroupModal: () => unknown;
|
||||
closeMaximumGroupSizeModal: () => unknown;
|
||||
closeRecommendedGroupSizeModal: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
onChangeComposeSearchTerm: (
|
||||
event: ChangeEvent<HTMLInputElement>
|
||||
) => unknown;
|
||||
removeSelectedContact: (conversationId: string) => unknown;
|
||||
}>): ReactChild {
|
||||
let modalNode: undefined | ReactChild;
|
||||
|
@ -154,14 +170,6 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
|
||||
return (
|
||||
<>
|
||||
<SearchInput
|
||||
moduleClassName="module-left-pane__compose-search-form"
|
||||
onChange={onChangeComposeSearchTerm}
|
||||
placeholder={i18n('contactSearchPlaceholder')}
|
||||
ref={focusRef}
|
||||
value={this.searchTerm}
|
||||
/>
|
||||
|
||||
{Boolean(this.selectedContacts.length) && (
|
||||
<ContactPills>
|
||||
{this.selectedContacts.map(contact => (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// Copyright 2021-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ReactChild, ChangeEvent } from 'react';
|
||||
|
@ -94,7 +94,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
return showInbox;
|
||||
}
|
||||
|
||||
override getPreRowsNode({
|
||||
override getSearchInput({
|
||||
i18n,
|
||||
onChangeComposeSearchTerm,
|
||||
}: Readonly<{
|
||||
|
@ -104,21 +104,25 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
) => unknown;
|
||||
}>): ReactChild {
|
||||
return (
|
||||
<>
|
||||
<SearchInput
|
||||
moduleClassName="module-left-pane__compose-search-form"
|
||||
onChange={onChangeComposeSearchTerm}
|
||||
placeholder={i18n('contactSearchPlaceholder')}
|
||||
ref={focusRef}
|
||||
value={this.searchTerm}
|
||||
/>
|
||||
<SearchInput
|
||||
moduleClassName="module-left-pane__compose-search-form"
|
||||
onChange={onChangeComposeSearchTerm}
|
||||
placeholder={i18n('contactSearchPlaceholder')}
|
||||
ref={focusRef}
|
||||
value={this.searchTerm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
{this.getRowCount() ? null : (
|
||||
<div className="module-left-pane__compose-no-contacts">
|
||||
{i18n('noConversationsFound')}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
override getPreRowsNode({
|
||||
i18n,
|
||||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
}>): ReactChild | null {
|
||||
return this.getRowCount() ? null : (
|
||||
<div className="module-left-pane__compose-no-contacts">
|
||||
{i18n('noConversationsFound')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// Copyright 2021-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ChangeEvent, ReactChild } from 'react';
|
||||
|
@ -24,12 +24,24 @@ export type ToFindType = {
|
|||
export abstract class LeftPaneHelper<T> {
|
||||
getHeaderContents(
|
||||
_: Readonly<{
|
||||
clearSearch: () => void;
|
||||
i18n: LocalizerType;
|
||||
showInbox: () => void;
|
||||
startComposing: () => void;
|
||||
showChooseGroupMembers: () => void;
|
||||
updateSearchTerm: (query: string) => void;
|
||||
}>
|
||||
): null | ReactChild {
|
||||
return null;
|
||||
}
|
||||
|
||||
getSearchInput(
|
||||
_: Readonly<{
|
||||
clearConversationSearch: () => unknown;
|
||||
clearSearch: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
onChangeComposeSearchTerm: (
|
||||
event: ChangeEvent<HTMLInputElement>
|
||||
) => unknown;
|
||||
updateSearchTerm: (searchTerm: string) => unknown;
|
||||
}>
|
||||
): null | ReactChild {
|
||||
return null;
|
||||
|
@ -47,7 +59,9 @@ export abstract class LeftPaneHelper<T> {
|
|||
|
||||
getPreRowsNode(
|
||||
_: Readonly<{
|
||||
clearConversationSearch: () => unknown;
|
||||
clearGroupCreationError: () => void;
|
||||
clearSearch: () => unknown;
|
||||
closeCantAddContactToGroupModal: () => unknown;
|
||||
closeMaximumGroupSizeModal: () => unknown;
|
||||
closeRecommendedGroupSizeModal: () => unknown;
|
||||
|
@ -56,13 +70,10 @@ export abstract class LeftPaneHelper<T> {
|
|||
composeSaveAvatarToDisk: SaveAvatarToDiskActionType;
|
||||
createGroup: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
setComposeGroupAvatar: (_: undefined | Uint8Array) => unknown;
|
||||
setComposeGroupName: (_: string) => unknown;
|
||||
setComposeGroupExpireTimer: (_: number) => void;
|
||||
onChangeComposeSearchTerm: (
|
||||
event: ChangeEvent<HTMLInputElement>
|
||||
) => unknown;
|
||||
removeSelectedContact: (_: string) => unknown;
|
||||
setComposeGroupAvatar: (_: undefined | Uint8Array) => unknown;
|
||||
setComposeGroupExpireTimer: (_: number) => void;
|
||||
setComposeGroupName: (_: string) => unknown;
|
||||
toggleComposeEditingAvatar: () => unknown;
|
||||
}>
|
||||
): null | ReactChild {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// Copyright 2021-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { last } from 'lodash';
|
||||
|
@ -7,6 +7,7 @@ import React from 'react';
|
|||
|
||||
import { Intl } from '../Intl';
|
||||
import type { ToFindType } from './LeftPaneHelper';
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import { LeftPaneHelper } from './LeftPaneHelper';
|
||||
import { getConversationInDirection } from './getConversationInDirection';
|
||||
import type { Row } from '../ConversationList';
|
||||
|
@ -14,6 +15,7 @@ import { RowType } from '../ConversationList';
|
|||
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import { handleKeydownForSearch } from './handleKeydownForSearch';
|
||||
import { LeftPaneMainSearchInput } from '../LeftPaneMainSearchInput';
|
||||
|
||||
export type LeftPaneInboxPropsType = {
|
||||
conversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
|
@ -21,6 +23,9 @@ export type LeftPaneInboxPropsType = {
|
|||
pinnedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
isAboutToSearchInAConversation: boolean;
|
||||
startSearchCounter: number;
|
||||
searchDisabled: boolean;
|
||||
searchTerm: string;
|
||||
searchConversation: undefined | ConversationType;
|
||||
};
|
||||
|
||||
export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType> {
|
||||
|
@ -34,12 +39,21 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
|
||||
private readonly startSearchCounter: number;
|
||||
|
||||
private readonly searchDisabled: boolean;
|
||||
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly searchConversation: undefined | ConversationType;
|
||||
|
||||
constructor({
|
||||
conversations,
|
||||
archivedConversations,
|
||||
pinnedConversations,
|
||||
isAboutToSearchInAConversation,
|
||||
startSearchCounter,
|
||||
searchDisabled,
|
||||
searchTerm,
|
||||
searchConversation,
|
||||
}: Readonly<LeftPaneInboxPropsType>) {
|
||||
super();
|
||||
|
||||
|
@ -48,6 +62,9 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
this.pinnedConversations = pinnedConversations;
|
||||
this.isAboutToSearchInAConversation = isAboutToSearchInAConversation;
|
||||
this.startSearchCounter = startSearchCounter;
|
||||
this.searchDisabled = searchDisabled;
|
||||
this.searchTerm = searchTerm;
|
||||
this.searchConversation = searchConversation;
|
||||
}
|
||||
|
||||
getRowCount(): number {
|
||||
|
@ -61,9 +78,36 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
);
|
||||
}
|
||||
|
||||
override getSearchInput({
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
i18n,
|
||||
updateSearchTerm,
|
||||
}: Readonly<{
|
||||
clearConversationSearch: () => unknown;
|
||||
clearSearch: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
updateSearchTerm: (searchTerm: string) => unknown;
|
||||
}>): ReactChild {
|
||||
return (
|
||||
<LeftPaneMainSearchInput
|
||||
clearConversationSearch={clearConversationSearch}
|
||||
clearSearch={clearSearch}
|
||||
disabled={this.searchDisabled}
|
||||
i18n={i18n}
|
||||
searchConversation={this.searchConversation}
|
||||
searchTerm={this.searchTerm}
|
||||
startSearchCounter={this.startSearchCounter}
|
||||
updateSearchTerm={updateSearchTerm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
override getPreRowsNode({
|
||||
i18n,
|
||||
}: Readonly<{ i18n: LocalizerType }>): null | ReactChild {
|
||||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
}>): ReactChild | null {
|
||||
if (this.getRowCount() === 0) {
|
||||
return (
|
||||
<div className="module-left-pane__empty">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// Copyright 2021-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ReactChild } from 'react';
|
||||
|
@ -11,6 +11,8 @@ import type { Row } from '../ConversationList';
|
|||
import { RowType } from '../ConversationList';
|
||||
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
|
||||
import { handleKeydownForSearch } from './handleKeydownForSearch';
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import { LeftPaneMainSearchInput } from '../LeftPaneMainSearchInput';
|
||||
|
||||
import { Intl } from '../Intl';
|
||||
import { Emojify } from '../conversation/Emojify';
|
||||
|
@ -37,6 +39,9 @@ export type LeftPaneSearchPropsType = {
|
|||
searchConversationName?: string;
|
||||
primarySendsSms: boolean;
|
||||
searchTerm: string;
|
||||
startSearchCounter: number;
|
||||
searchDisabled: boolean;
|
||||
searchConversation: undefined | ConversationType;
|
||||
};
|
||||
|
||||
const searchResultKeys: Array<
|
||||
|
@ -61,30 +66,70 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly startSearchCounter: number;
|
||||
|
||||
private readonly searchDisabled: boolean;
|
||||
|
||||
private readonly searchConversation: undefined | ConversationType;
|
||||
|
||||
constructor({
|
||||
conversationResults,
|
||||
contactResults,
|
||||
conversationResults,
|
||||
messageResults,
|
||||
searchConversationName,
|
||||
primarySendsSms,
|
||||
searchConversation,
|
||||
searchConversationName,
|
||||
searchDisabled,
|
||||
searchTerm,
|
||||
startSearchCounter,
|
||||
}: Readonly<LeftPaneSearchPropsType>) {
|
||||
super();
|
||||
|
||||
this.conversationResults = conversationResults;
|
||||
this.contactResults = contactResults;
|
||||
this.conversationResults = conversationResults;
|
||||
this.messageResults = messageResults;
|
||||
this.searchConversationName = searchConversationName;
|
||||
this.primarySendsSms = primarySendsSms;
|
||||
this.searchConversation = searchConversation;
|
||||
this.searchConversationName = searchConversationName;
|
||||
this.searchDisabled = searchDisabled;
|
||||
this.searchTerm = searchTerm;
|
||||
this.startSearchCounter = startSearchCounter;
|
||||
}
|
||||
|
||||
override getSearchInput({
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
i18n,
|
||||
updateSearchTerm,
|
||||
}: Readonly<{
|
||||
clearConversationSearch: () => unknown;
|
||||
clearSearch: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
updateSearchTerm: (searchTerm: string) => unknown;
|
||||
}>): ReactChild {
|
||||
return (
|
||||
<LeftPaneMainSearchInput
|
||||
clearConversationSearch={clearConversationSearch}
|
||||
clearSearch={clearSearch}
|
||||
disabled={this.searchDisabled}
|
||||
i18n={i18n}
|
||||
searchConversation={this.searchConversation}
|
||||
searchTerm={this.searchTerm}
|
||||
startSearchCounter={this.startSearchCounter}
|
||||
updateSearchTerm={updateSearchTerm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
override getPreRowsNode({
|
||||
i18n,
|
||||
}: Readonly<{ i18n: LocalizerType }>): null | ReactChild {
|
||||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
}>): ReactChild | null {
|
||||
const mightHaveSearchResults = this.allResults().some(
|
||||
searchResult => searchResult.isLoading || searchResult.results.length
|
||||
);
|
||||
|
||||
if (mightHaveSearchResults) {
|
||||
return null;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue