signal-desktop/ts/components/LeftPaneSearchInput.tsx
2021-11-01 11:43:02 -07:00

127 lines
4.1 KiB
TypeScript

// Copyright 2021 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 type { ConversationType } from '../state/ducks/conversations';
import type { LocalizerType } from '../types/Util';
import { Avatar, AvatarSize } from './Avatar';
type PropsType = {
disabled?: boolean;
i18n: LocalizerType;
onBlur?: FocusEventHandler<HTMLInputElement>;
onChangeValue: (newValue: string) => unknown;
onClear: () => unknown;
searchConversation?: ConversationType;
value: string;
};
export const LeftPaneSearchInput = forwardRef<HTMLInputElement, PropsType>(
(
{
disabled,
i18n,
onBlur,
onChangeValue,
onClear,
searchConversation,
value,
},
outerRef
) => {
const inputRef = useRef<null | HTMLInputElement>(null);
const emptyOrClear =
searchConversation && value ? () => onChangeValue('') : onClear;
const label = searchConversation
? i18n('searchIn', [
searchConversation.isMe
? i18n('noteToSelf')
: searchConversation.title,
])
: i18n('search');
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}
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);
}}
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 && (
<button
aria-label={i18n('cancel')}
className="LeftPaneSearchInput__cancel"
onClick={emptyOrClear}
tabIndex={-1}
type="button"
/>
)}
</div>
);
}
);