signal-desktop/ts/components/LeftPaneSearchInput.tsx

162 lines
4.6 KiB
TypeScript
Raw Normal View History

2022-02-14 17:57:11 +00:00
// Copyright 2021-2022 Signal Messenger, LLC
2021-11-01 18:43:02 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
2022-02-14 17:57:11 +00:00
import React, { useEffect, useRef } from 'react';
import type {
ConversationType,
OpenConversationInternalType,
} from '../state/ducks/conversations';
2021-11-01 18:43:02 +00:00
import type { LocalizerType } from '../types/Util';
import { Avatar, AvatarSize } from './Avatar';
2022-02-14 17:57:11 +00:00
import { SearchInput } from './SearchInput';
import { usePrevious } from '../hooks/usePrevious';
2021-11-01 18:43:02 +00:00
type PropsType = {
2022-02-14 17:57:11 +00:00
clearConversationSearch: () => void;
clearSearch: () => void;
2021-11-01 18:43:02 +00:00
disabled?: boolean;
i18n: LocalizerType;
searchConversation?: ConversationType;
2022-02-14 17:57:11 +00:00
searchTerm: string;
startSearchCounter: number;
updateSearchTerm: (searchTerm: string) => void;
openConversationInternal: OpenConversationInternalType;
onEnterKeyDown?: (
clearSearch: () => void,
openConversationInternal: OpenConversationInternalType
) => void;
2021-11-01 18:43:02 +00:00
};
2022-02-14 17:57:11 +00:00
export const LeftPaneSearchInput = ({
clearConversationSearch,
clearSearch,
disabled,
i18n,
searchConversation,
searchTerm,
startSearchCounter,
updateSearchTerm,
openConversationInternal,
onEnterKeyDown,
2022-02-14 17:57:11 +00:00
}: PropsType): JSX.Element => {
const inputRef = useRef<null | HTMLInputElement>(null);
2021-11-01 18:43:02 +00:00
2022-02-14 17:57:11 +00:00
const prevSearchConversationId = usePrevious(
undefined,
searchConversation?.id
);
const prevSearchCounter = usePrevious(startSearchCounter, startSearchCounter);
2021-11-01 18:43:02 +00:00
2022-02-14 17:57:11 +00:00
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,
]);
2021-11-01 18:43:02 +00:00
2022-02-14 17:57:11 +00:00
const changeValue = (nextSearchTerm: string) => {
if (!nextSearchTerm) {
if (searchConversation) {
clearConversationSearch();
} else {
clearSearch();
}
return;
}
if (updateSearchTerm) {
updateSearchTerm(nextSearchTerm);
}
};
const clearAndFocus = () => {
clearSearch();
inputRef.current?.focus();
};
2021-11-01 18:43:02 +00:00
2022-02-14 17:57:11 +00:00
const label = i18n(searchConversation ? 'searchIn' : 'search');
return (
<SearchInput
disabled={disabled}
label={label}
hasSearchIcon={!searchConversation}
i18n={i18n}
moduleClassName="LeftPaneSearchInput"
onBlur={() => {
if (!searchConversation && !searchTerm) {
clearSearch();
}
}}
onKeyDown={event => {
if (onEnterKeyDown && event.key === 'Enter') {
onEnterKeyDown(clearSearch, openConversationInternal);
event.preventDefault();
event.stopPropagation();
}
}}
2022-02-14 17:57:11 +00:00
onChange={event => {
changeValue(event.currentTarget.value);
}}
onClear={() => {
if (searchConversation && searchTerm) {
changeValue('');
} else {
clearAndFocus();
}
}}
ref={inputRef}
placeholder={label}
value={searchTerm}
>
{searchConversation && (
// Clicking the non-X part of the pill should focus the input but have a normal
// cursor. This effectively simulates `pointer-events: none` while still
// letting us change the cursor.
// eslint-disable-next-line max-len
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div
className="LeftPaneSearchInput__in-conversation-pill"
onClick={() => {
inputRef.current?.focus();
2021-11-01 18:43:02 +00:00
}}
2022-02-14 17:57:11 +00:00
>
<Avatar
acceptedMessageRequest={searchConversation.acceptedMessageRequest}
avatarPath={searchConversation.avatarPath}
badge={undefined}
color={searchConversation.color}
conversationType={searchConversation.type}
i18n={i18n}
isMe={searchConversation.isMe}
noteToSelf={searchConversation.isMe}
sharedGroupNames={searchConversation.sharedGroupNames}
size={AvatarSize.SIXTEEN}
title={searchConversation.title}
unblurredAvatarPath={searchConversation.unblurredAvatarPath}
/>
2021-11-01 18:43:02 +00:00
<button
2022-02-14 17:57:11 +00:00
aria-label={i18n('clearSearch')}
className="LeftPaneSearchInput__in-conversation-pill__x-button"
onClick={clearAndFocus}
2021-11-01 18:43:02 +00:00
type="button"
/>
2022-02-14 17:57:11 +00:00
</div>
)}
</SearchInput>
);
};