Consolidates the search inputs

This commit is contained in:
Josh Perez 2022-02-14 12:57:11 -05:00 committed by GitHub
parent 1b352531ca
commit 67209d8881
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 263 additions and 319 deletions

View File

@ -2,64 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
.LeftPaneSearchInput { .LeftPaneSearchInput {
position: relative; &__input--with-children.module-SearchInput__input--with-children {
margin: 0 16px; padding-left: 50px;
margin-bottom: 8px;
&__input {
@include font-body-2;
border: solid 1px transparent;
border-radius: 8px;
height: 28px;
padding-left: 30px;
padding-right: 5px;
width: 100%;
@include light-theme {
background-color: $color-black-alpha-08;
color: $color-gray-90;
&:placeholder {
color: $color-gray-45;
}
}
@include dark-theme {
background-color: $color-white-alpha-12;
color: $color-gray-05;
&:placeholder {
color: $color-gray-25;
}
}
&:focus {
border: solid 1px $color-ultramarine;
outline: none;
}
&--with-text {
padding-right: 30px;
}
&--in-conversation {
padding-left: 50px;
}
}
&__icon {
height: 16px;
left: 8px;
pointer-events: none;
position: absolute;
top: 6px;
width: 16px;
@include light-theme {
@include color-svg('../images/icons/v2/search-16.svg', $color-gray-45);
}
@include dark-theme {
@include color-svg('../images/icons/v2/search-16.svg', $color-gray-25);
}
} }
&__in-conversation-pill { &__in-conversation-pill {
@ -110,22 +54,9 @@
} }
} }
&__cancel {
height: 18px;
position: absolute;
right: 8px;
top: 5px;
width: 18px;
@include light-theme {
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-60);
}
@include dark-theme {
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-25);
}
}
.module-left-pane--width-narrow & { .module-left-pane--width-narrow & {
display: none; &__container {
display: none;
}
} }
} }

View File

@ -3,40 +3,28 @@
.module-SearchInput { .module-SearchInput {
&__container { &__container {
border-radius: 8px; margin: {
border: none; left: 16px;
margin: 10px 16px; right: 16px;
padding: 5px 12px; bottom: 8px;
}
position: relative; position: relative;
@include font-body-2;
@include light-theme {
background-color: $color-gray-15;
border: solid 1px $color-gray-15;
color: $color-gray-90;
}
@include dark-theme {
background-color: $color-gray-65;
border: solid 1px $color-gray-65;
color: $color-gray-05;
}
&:focus-within {
border: solid 1px $color-ultramarine;
outline: none;
}
} }
&__icon { &__icon {
@include color-svg('../images/icons/v2/search-16.svg', $color-gray-45);
cursor: text;
height: 16px; height: 16px;
left: 8px; left: 8px;
pointer-events: none;
position: absolute; position: absolute;
top: 6px; top: 6px;
width: 16px; width: 16px;
@include light-theme {
@include color-svg('../images/icons/v2/search-16.svg', $color-gray-45);
}
@include dark-theme {
@include color-svg('../images/icons/v2/search-16.svg', $color-gray-25);
}
} }
&__input { &__input {
@ -61,4 +49,55 @@
outline: none; outline: none;
} }
} }
&__input {
@include font-body-2;
border: solid 1px transparent;
border-radius: 8px;
height: 28px;
padding-left: 30px;
padding-right: 5px;
width: 100%;
@include light-theme {
background-color: $color-black-alpha-08;
color: $color-gray-90;
&:placeholder {
color: $color-gray-45;
}
}
@include dark-theme {
background-color: $color-white-alpha-12;
color: $color-gray-05;
&:placeholder {
color: $color-gray-25;
}
}
&:focus {
border: solid 1px $color-ultramarine;
outline: none;
}
&--with-text {
padding-right: 30px;
}
}
&__cancel {
height: 18px;
position: absolute;
right: 8px;
top: 5px;
width: 18px;
@include light-theme {
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-60);
}
@include dark-theme {
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-25);
}
}
} }

View File

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent } from 'react'; import type { FunctionComponent } from 'react';
@ -372,6 +372,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
<div className="module-ForwardMessageModal__main-body"> <div className="module-ForwardMessageModal__main-body">
<SearchInput <SearchInput
disabled={candidateConversations.length === 0} disabled={candidateConversations.length === 0}
i18n={i18n}
placeholder={i18n('contactSearchPlaceholder')} placeholder={i18n('contactSearchPlaceholder')}
onChange={event => { onChange={event => {
setSearchTerm(event.target.value); setSearchTerm(event.target.value);

View File

@ -1,92 +0,0 @@
// 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}
/>
);
};

View File

@ -1,123 +1,144 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { FocusEventHandler } from 'react'; import React, { useEffect, useRef } 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 { ConversationType } from '../state/ducks/conversations';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType } from '../types/Util';
import { Avatar, AvatarSize } from './Avatar'; import { Avatar, AvatarSize } from './Avatar';
import { SearchInput } from './SearchInput';
import { usePrevious } from '../hooks/usePrevious';
type PropsType = { type PropsType = {
clearConversationSearch: () => void;
clearSearch: () => void;
disabled?: boolean; disabled?: boolean;
i18n: LocalizerType; i18n: LocalizerType;
onBlur?: FocusEventHandler<HTMLInputElement>;
onChangeValue: (newValue: string) => unknown;
onClear: () => unknown;
searchConversation?: ConversationType; searchConversation?: ConversationType;
value: string; searchTerm: string;
startSearchCounter: number;
updateSearchTerm: (searchTerm: string) => void;
}; };
// TODO DESKTOP-3068: merge with <SearchInput /> export const LeftPaneSearchInput = ({
export const LeftPaneSearchInput = forwardRef<HTMLInputElement, PropsType>( clearConversationSearch,
( clearSearch,
{ disabled,
disabled, i18n,
i18n, searchConversation,
onBlur, searchTerm,
onChangeValue, startSearchCounter,
onClear, updateSearchTerm,
searchConversation, }: PropsType): JSX.Element => {
value, const inputRef = useRef<null | HTMLInputElement>(null);
},
outerRef
) => {
const inputRef = useRef<null | HTMLInputElement>(null);
const emptyOrClear = const prevSearchConversationId = usePrevious(
searchConversation && value ? () => onChangeValue('') : onClear; 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 ( const changeValue = (nextSearchTerm: string) => {
<div className="LeftPaneSearchInput"> if (!nextSearchTerm) {
{searchConversation ? ( if (searchConversation) {
// Clicking the non-X part of the pill should focus the input but have a normal clearConversationSearch();
// cursor. This effectively simulates `pointer-events: none` while still } else {
// letting us change the cursor. clearSearch();
// eslint-disable-next-line max-len }
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div return;
className="LeftPaneSearchInput__in-conversation-pill" }
onClick={() => {
inputRef.current?.focus(); if (updateSearchTerm) {
}} updateSearchTerm(nextSearchTerm);
> }
<Avatar };
acceptedMessageRequest={searchConversation.acceptedMessageRequest}
avatarPath={searchConversation.avatarPath} const clearAndFocus = () => {
badge={undefined} clearSearch();
color={searchConversation.color} inputRef.current?.focus();
conversationType={searchConversation.type} };
i18n={i18n}
isMe={searchConversation.isMe} const label = i18n(searchConversation ? 'searchIn' : 'search');
noteToSelf={searchConversation.isMe}
sharedGroupNames={searchConversation.sharedGroupNames} return (
size={AvatarSize.SIXTEEN} <SearchInput
title={searchConversation.title} disabled={disabled}
unblurredAvatarPath={searchConversation.unblurredAvatarPath} label={label}
/> hasSearchIcon={!searchConversation}
<button i18n={i18n}
aria-label={i18n('clearSearch')} moduleClassName="LeftPaneSearchInput"
className="LeftPaneSearchInput__in-conversation-pill__x-button" onBlur={() => {
onClick={onClear} if (!searchConversation && !searchTerm) {
type="button" clearSearch();
/> }
</div> }}
) : ( onChange={event => {
<div className="LeftPaneSearchInput__icon" /> changeValue(event.currentTarget.value);
)} }}
<input onClear={() => {
aria-label={label} if (searchConversation && searchTerm) {
className={classNames( changeValue('');
'LeftPaneSearchInput__input', } else {
value && 'LeftPaneSearchInput__input--with-text', clearAndFocus();
searchConversation && 'LeftPaneSearchInput__input--in-conversation' }
)} }}
dir="auto" ref={inputRef}
disabled={disabled} placeholder={label}
onBlur={onBlur} value={searchTerm}
onChange={event => { >
onChangeValue(event.currentTarget.value); {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; <Avatar
acceptedMessageRequest={searchConversation.acceptedMessageRequest}
// On Linux, this key combo selects all text. avatarPath={searchConversation.avatarPath}
if (window.platform === 'linux' && ctrlKey && key === '/') { badge={undefined}
event.preventDefault(); color={searchConversation.color}
event.stopPropagation(); conversationType={searchConversation.type}
} else if (key === 'Escape') { i18n={i18n}
emptyOrClear(); isMe={searchConversation.isMe}
event.preventDefault(); noteToSelf={searchConversation.isMe}
event.stopPropagation(); sharedGroupNames={searchConversation.sharedGroupNames}
} size={AvatarSize.SIXTEEN}
}} title={searchConversation.title}
placeholder={label} unblurredAvatarPath={searchConversation.unblurredAvatarPath}
ref={refMerger(inputRef, outerRef)} />
value={value}
/>
{value && (
<button <button
aria-label={i18n('cancel')} aria-label={i18n('clearSearch')}
className="LeftPaneSearchInput__cancel" className="LeftPaneSearchInput__in-conversation-pill__x-button"
onClick={emptyOrClear} onClick={clearAndFocus}
tabIndex={-1}
type="button" type="button"
/> />
)} </div>
</div> )}
); </SearchInput>
} );
); };

View File

@ -1,13 +1,26 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { ChangeEvent, KeyboardEvent } from 'react'; import type {
ChangeEvent,
FocusEventHandler,
KeyboardEvent,
ReactNode,
} from 'react';
import React, { forwardRef } from 'react'; import React, { forwardRef } from 'react';
import classNames from 'classnames';
import type { LocalizerType } from '../types/Util';
import { getClassNamesFor } from '../util/getClassNamesFor'; import { getClassNamesFor } from '../util/getClassNamesFor';
export type PropTypes = { export type PropTypes = {
readonly children?: ReactNode;
readonly disabled?: boolean; readonly disabled?: boolean;
readonly label?: string;
readonly hasSearchIcon?: boolean;
readonly i18n: LocalizerType;
readonly moduleClassName?: string; readonly moduleClassName?: string;
readonly onClear?: () => unknown;
readonly onBlur?: FocusEventHandler<HTMLInputElement>;
readonly onChange: (ev: ChangeEvent<HTMLInputElement>) => unknown; readonly onChange: (ev: ChangeEvent<HTMLInputElement>) => unknown;
readonly onKeyDown?: (ev: KeyboardEvent<HTMLInputElement>) => unknown; readonly onKeyDown?: (ev: KeyboardEvent<HTMLInputElement>) => unknown;
readonly placeholder: string; readonly placeholder: string;
@ -19,8 +32,14 @@ const BASE_CLASS_NAME = 'module-SearchInput';
export const SearchInput = forwardRef<HTMLInputElement, PropTypes>( export const SearchInput = forwardRef<HTMLInputElement, PropTypes>(
( (
{ {
children,
disabled = false, disabled = false,
hasSearchIcon = true,
i18n,
label,
moduleClassName, moduleClassName,
onClear,
onBlur,
onChange, onChange,
onKeyDown, onKeyDown,
placeholder, placeholder,
@ -31,18 +50,48 @@ export const SearchInput = forwardRef<HTMLInputElement, PropTypes>(
const getClassName = getClassNamesFor(BASE_CLASS_NAME, moduleClassName); const getClassName = getClassNamesFor(BASE_CLASS_NAME, moduleClassName);
return ( return (
<div className={getClassName('__container')}> <div className={getClassName('__container')}>
<i className={getClassName('__icon')} /> {hasSearchIcon && <i className={getClassName('__icon')} />}
{children}
<input <input
className={getClassName('__input')} aria-label={label || i18n('search')}
className={classNames(
getClassName('__input'),
value && getClassName('__input--with-text'),
children && getClassName('__input--with-children')
)}
dir="auto" dir="auto"
disabled={disabled} disabled={disabled}
onBlur={onBlur}
onChange={onChange} onChange={onChange}
onKeyDown={onKeyDown} 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' && onClear) {
onClear();
event.preventDefault();
event.stopPropagation();
}
onKeyDown?.(event);
}}
placeholder={placeholder} placeholder={placeholder}
ref={ref} ref={ref}
type="text" type="text"
value={value} value={value}
/> />
{value && onClear && (
<button
aria-label={i18n('cancel')}
className={getClassName('__cancel')}
onClick={onClear}
tabIndex={-1}
type="button"
/>
)}
</div> </div>
); );
} }

View File

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent } from 'react'; import type { FunctionComponent } from 'react';
@ -139,6 +139,7 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
</h1> </h1>
<SearchInput <SearchInput
disabled={candidateContacts.length === 0} disabled={candidateContacts.length === 0}
i18n={i18n}
placeholder={i18n('contactSearchPlaceholder')} placeholder={i18n('contactSearchPlaceholder')}
onChange={event => { onChange={event => {
setSearchTerm(event.target.value); setSearchTerm(event.target.value);

View File

@ -1,4 +1,4 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { ReactChild } from 'react'; import type { ReactChild } from 'react';
@ -13,7 +13,7 @@ import { RowType } from '../ConversationList';
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem'; import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
import type { ConversationType } from '../../state/ducks/conversations'; import type { ConversationType } from '../../state/ducks/conversations';
import { LeftPaneMainSearchInput } from '../LeftPaneMainSearchInput'; import { LeftPaneSearchInput } from '../LeftPaneSearchInput';
import type { LeftPaneSearchPropsType } from './LeftPaneSearchHelper'; import type { LeftPaneSearchPropsType } from './LeftPaneSearchHelper';
import { LeftPaneSearchHelper } from './LeftPaneSearchHelper'; import { LeftPaneSearchHelper } from './LeftPaneSearchHelper';
@ -91,7 +91,7 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
} }
return ( return (
<LeftPaneMainSearchInput <LeftPaneSearchInput
clearConversationSearch={clearConversationSearch} clearConversationSearch={clearConversationSearch}
clearSearch={clearSearch} clearSearch={clearSearch}
i18n={i18n} i18n={i18n}

View File

@ -116,6 +116,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
}>): ReactChild { }>): ReactChild {
return ( return (
<SearchInput <SearchInput
i18n={i18n}
moduleClassName="module-left-pane__compose-search-form" moduleClassName="module-left-pane__compose-search-form"
onChange={onChangeComposeSearchTerm} onChange={onChangeComposeSearchTerm}
placeholder={i18n('contactSearchPlaceholder')} placeholder={i18n('contactSearchPlaceholder')}

View File

@ -105,6 +105,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
}>): ReactChild { }>): ReactChild {
return ( return (
<SearchInput <SearchInput
i18n={i18n}
moduleClassName="module-left-pane__compose-search-form" moduleClassName="module-left-pane__compose-search-form"
onChange={onChangeComposeSearchTerm} onChange={onChangeComposeSearchTerm}
placeholder={i18n('contactSearchPlaceholder')} placeholder={i18n('contactSearchPlaceholder')}

View File

@ -15,7 +15,7 @@ import { RowType } from '../ConversationList';
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem'; import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
import { handleKeydownForSearch } from './handleKeydownForSearch'; import { handleKeydownForSearch } from './handleKeydownForSearch';
import { LeftPaneMainSearchInput } from '../LeftPaneMainSearchInput'; import { LeftPaneSearchInput } from '../LeftPaneSearchInput';
export type LeftPaneInboxPropsType = { export type LeftPaneInboxPropsType = {
conversations: ReadonlyArray<ConversationListItemPropsType>; conversations: ReadonlyArray<ConversationListItemPropsType>;
@ -90,7 +90,7 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
updateSearchTerm: (searchTerm: string) => unknown; updateSearchTerm: (searchTerm: string) => unknown;
}>): ReactChild { }>): ReactChild {
return ( return (
<LeftPaneMainSearchInput <LeftPaneSearchInput
clearConversationSearch={clearConversationSearch} clearConversationSearch={clearConversationSearch}
clearSearch={clearSearch} clearSearch={clearSearch}
disabled={this.searchDisabled} disabled={this.searchDisabled}

View File

@ -12,7 +12,7 @@ import { RowType } from '../ConversationList';
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem'; import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
import { handleKeydownForSearch } from './handleKeydownForSearch'; import { handleKeydownForSearch } from './handleKeydownForSearch';
import type { ConversationType } from '../../state/ducks/conversations'; import type { ConversationType } from '../../state/ducks/conversations';
import { LeftPaneMainSearchInput } from '../LeftPaneMainSearchInput'; import { LeftPaneSearchInput } from '../LeftPaneSearchInput';
import { Intl } from '../Intl'; import { Intl } from '../Intl';
import { Emojify } from '../conversation/Emojify'; import { Emojify } from '../conversation/Emojify';
@ -108,7 +108,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
updateSearchTerm: (searchTerm: string) => unknown; updateSearchTerm: (searchTerm: string) => unknown;
}>): ReactChild { }>): ReactChild {
return ( return (
<LeftPaneMainSearchInput <LeftPaneSearchInput
clearConversationSearch={clearConversationSearch} clearConversationSearch={clearConversationSearch}
clearSearch={clearSearch} clearSearch={clearSearch}
disabled={this.searchDisabled} disabled={this.searchDisabled}

View File

@ -7515,20 +7515,12 @@
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2020-07-21T18:34:59.251Z" "updated": "2020-07-21T18:34:59.251Z"
}, },
{
"rule": "React-useRef",
"path": "ts/components/LeftPaneMainSearchInput.tsx",
"line": " const inputRef = useRef<HTMLInputElement | null>(null);",
"reasonCategory": "usageTrusted",
"updated": "2022-01-26T23:11:05.369Z"
},
{ {
"rule": "React-useRef", "rule": "React-useRef",
"path": "ts/components/LeftPaneSearchInput.tsx", "path": "ts/components/LeftPaneSearchInput.tsx",
"line": " const inputRef = useRef<null | HTMLInputElement>(null);", "line": " const inputRef = useRef<null | HTMLInputElement>(null);",
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2021-10-29T22:48:58.354Z", "updated": "2022-02-11T20:49:03.879Z"
"reasonDetail": "Only used to focus the input."
}, },
{ {
"rule": "React-useRef", "rule": "React-useRef",