Consolidates the search inputs
This commit is contained in:
parent
1b352531ca
commit
67209d8881
13 changed files with 263 additions and 319 deletions
|
@ -1,123 +1,144 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// Copyright 2021-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { FocusEventHandler } from 'react';
|
||||
import React, { forwardRef, useRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { refMerger } from '../util/refMerger';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { Avatar, AvatarSize } from './Avatar';
|
||||
import { SearchInput } from './SearchInput';
|
||||
import { usePrevious } from '../hooks/usePrevious';
|
||||
|
||||
type PropsType = {
|
||||
clearConversationSearch: () => void;
|
||||
clearSearch: () => void;
|
||||
disabled?: boolean;
|
||||
i18n: LocalizerType;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
onChangeValue: (newValue: string) => unknown;
|
||||
onClear: () => unknown;
|
||||
searchConversation?: ConversationType;
|
||||
value: string;
|
||||
searchTerm: string;
|
||||
startSearchCounter: number;
|
||||
updateSearchTerm: (searchTerm: string) => void;
|
||||
};
|
||||
|
||||
// TODO DESKTOP-3068: merge with <SearchInput />
|
||||
export const LeftPaneSearchInput = forwardRef<HTMLInputElement, PropsType>(
|
||||
(
|
||||
{
|
||||
disabled,
|
||||
i18n,
|
||||
onBlur,
|
||||
onChangeValue,
|
||||
onClear,
|
||||
searchConversation,
|
||||
value,
|
||||
},
|
||||
outerRef
|
||||
) => {
|
||||
const inputRef = useRef<null | HTMLInputElement>(null);
|
||||
export const LeftPaneSearchInput = ({
|
||||
clearConversationSearch,
|
||||
clearSearch,
|
||||
disabled,
|
||||
i18n,
|
||||
searchConversation,
|
||||
searchTerm,
|
||||
startSearchCounter,
|
||||
updateSearchTerm,
|
||||
}: PropsType): JSX.Element => {
|
||||
const inputRef = useRef<null | HTMLInputElement>(null);
|
||||
|
||||
const emptyOrClear =
|
||||
searchConversation && value ? () => onChangeValue('') : onClear;
|
||||
const prevSearchConversationId = usePrevious(
|
||||
undefined,
|
||||
searchConversation?.id
|
||||
);
|
||||
const prevSearchCounter = usePrevious(startSearchCounter, startSearchCounter);
|
||||
|
||||
const label = i18n(searchConversation ? 'searchIn' : 'search');
|
||||
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 (
|
||||
<div className="LeftPaneSearchInput">
|
||||
{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();
|
||||
}}
|
||||
>
|
||||
<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}
|
||||
/>
|
||||
<button
|
||||
aria-label={i18n('clearSearch')}
|
||||
className="LeftPaneSearchInput__in-conversation-pill__x-button"
|
||||
onClick={onClear}
|
||||
type="button"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="LeftPaneSearchInput__icon" />
|
||||
)}
|
||||
<input
|
||||
aria-label={label}
|
||||
className={classNames(
|
||||
'LeftPaneSearchInput__input',
|
||||
value && 'LeftPaneSearchInput__input--with-text',
|
||||
searchConversation && 'LeftPaneSearchInput__input--in-conversation'
|
||||
)}
|
||||
dir="auto"
|
||||
disabled={disabled}
|
||||
onBlur={onBlur}
|
||||
onChange={event => {
|
||||
onChangeValue(event.currentTarget.value);
|
||||
const changeValue = (nextSearchTerm: string) => {
|
||||
if (!nextSearchTerm) {
|
||||
if (searchConversation) {
|
||||
clearConversationSearch();
|
||||
} else {
|
||||
clearSearch();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateSearchTerm) {
|
||||
updateSearchTerm(nextSearchTerm);
|
||||
}
|
||||
};
|
||||
|
||||
const clearAndFocus = () => {
|
||||
clearSearch();
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
const label = i18n(searchConversation ? 'searchIn' : 'search');
|
||||
|
||||
return (
|
||||
<SearchInput
|
||||
disabled={disabled}
|
||||
label={label}
|
||||
hasSearchIcon={!searchConversation}
|
||||
i18n={i18n}
|
||||
moduleClassName="LeftPaneSearchInput"
|
||||
onBlur={() => {
|
||||
if (!searchConversation && !searchTerm) {
|
||||
clearSearch();
|
||||
}
|
||||
}}
|
||||
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();
|
||||
}}
|
||||
onKeyDown={event => {
|
||||
const { ctrlKey, key } = event;
|
||||
|
||||
// On Linux, this key combo selects all text.
|
||||
if (window.platform === 'linux' && ctrlKey && key === '/') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else if (key === 'Escape') {
|
||||
emptyOrClear();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}}
|
||||
placeholder={label}
|
||||
ref={refMerger(inputRef, outerRef)}
|
||||
value={value}
|
||||
/>
|
||||
{value && (
|
||||
>
|
||||
<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}
|
||||
/>
|
||||
<button
|
||||
aria-label={i18n('cancel')}
|
||||
className="LeftPaneSearchInput__cancel"
|
||||
onClick={emptyOrClear}
|
||||
tabIndex={-1}
|
||||
aria-label={i18n('clearSearch')}
|
||||
className="LeftPaneSearchInput__in-conversation-pill__x-button"
|
||||
onClick={clearAndFocus}
|
||||
type="button"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
</div>
|
||||
)}
|
||||
</SearchInput>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue