diff --git a/stylesheets/components/LeftPaneSearchInput.scss b/stylesheets/components/LeftPaneSearchInput.scss index 30a26250b..4f829be8a 100644 --- a/stylesheets/components/LeftPaneSearchInput.scss +++ b/stylesheets/components/LeftPaneSearchInput.scss @@ -2,64 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only .LeftPaneSearchInput { - position: relative; - margin: 0 16px; - 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); - } + &__input--with-children.module-SearchInput__input--with-children { + padding-left: 50px; } &__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 & { - display: none; + &__container { + display: none; + } } } diff --git a/stylesheets/components/SearchInput.scss b/stylesheets/components/SearchInput.scss index a9c719924..e7e6708cd 100644 --- a/stylesheets/components/SearchInput.scss +++ b/stylesheets/components/SearchInput.scss @@ -3,40 +3,28 @@ .module-SearchInput { &__container { - border-radius: 8px; - border: none; - margin: 10px 16px; - padding: 5px 12px; + margin: { + left: 16px; + right: 16px; + bottom: 8px; + } 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 { - @include color-svg('../images/icons/v2/search-16.svg', $color-gray-45); - cursor: text; 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); + } } &__input { @@ -61,4 +49,55 @@ 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); + } + } } diff --git a/ts/components/ForwardMessageModal.tsx b/ts/components/ForwardMessageModal.tsx index 7d59d049c..96872531f 100644 --- a/ts/components/ForwardMessageModal.tsx +++ b/ts/components/ForwardMessageModal.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { FunctionComponent } from 'react'; @@ -372,6 +372,7 @@ export const ForwardMessageModal: FunctionComponent = ({
{ setSearchTerm(event.target.value); diff --git a/ts/components/LeftPaneMainSearchInput.tsx b/ts/components/LeftPaneMainSearchInput.tsx deleted file mode 100644 index e749b822a..000000000 --- a/ts/components/LeftPaneMainSearchInput.tsx +++ /dev/null @@ -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(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 ( - { - 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} - /> - ); -}; diff --git a/ts/components/LeftPaneSearchInput.tsx b/ts/components/LeftPaneSearchInput.tsx index b5d97e008..3762adf2f 100644 --- a/ts/components/LeftPaneSearchInput.tsx +++ b/ts/components/LeftPaneSearchInput.tsx @@ -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; - onChangeValue: (newValue: string) => unknown; - onClear: () => unknown; searchConversation?: ConversationType; - value: string; + searchTerm: string; + startSearchCounter: number; + updateSearchTerm: (searchTerm: string) => void; }; -// TODO DESKTOP-3068: merge with -export const LeftPaneSearchInput = forwardRef( - ( - { - disabled, - i18n, - onBlur, - onChangeValue, - onClear, - searchConversation, - value, - }, - outerRef - ) => { - const inputRef = useRef(null); +export const LeftPaneSearchInput = ({ + clearConversationSearch, + clearSearch, + disabled, + i18n, + searchConversation, + searchTerm, + startSearchCounter, + updateSearchTerm, +}: PropsType): JSX.Element => { + const inputRef = useRef(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 ( -
- {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 -
{ - inputRef.current?.focus(); - }} - > - -
- ) : ( -
- )} - { - 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 ( + { + 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 +
{ + 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 && ( + > +
- ); - } -); +
+ )} + + ); +}; diff --git a/ts/components/SearchInput.tsx b/ts/components/SearchInput.tsx index 64d78aead..642f287ca 100644 --- a/ts/components/SearchInput.tsx +++ b/ts/components/SearchInput.tsx @@ -1,13 +1,26 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // 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 classNames from 'classnames'; +import type { LocalizerType } from '../types/Util'; import { getClassNamesFor } from '../util/getClassNamesFor'; export type PropTypes = { + readonly children?: ReactNode; readonly disabled?: boolean; + readonly label?: string; + readonly hasSearchIcon?: boolean; + readonly i18n: LocalizerType; readonly moduleClassName?: string; + readonly onClear?: () => unknown; + readonly onBlur?: FocusEventHandler; readonly onChange: (ev: ChangeEvent) => unknown; readonly onKeyDown?: (ev: KeyboardEvent) => unknown; readonly placeholder: string; @@ -19,8 +32,14 @@ const BASE_CLASS_NAME = 'module-SearchInput'; export const SearchInput = forwardRef( ( { + children, disabled = false, + hasSearchIcon = true, + i18n, + label, moduleClassName, + onClear, + onBlur, onChange, onKeyDown, placeholder, @@ -31,18 +50,48 @@ export const SearchInput = forwardRef( const getClassName = getClassNamesFor(BASE_CLASS_NAME, moduleClassName); return (
- + {hasSearchIcon && } + {children} { + 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} ref={ref} type="text" value={value} /> + {value && onClear && ( +
); } diff --git a/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx b/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx index 7e435729f..967802f16 100644 --- a/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx +++ b/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.tsx @@ -1,4 +1,4 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { FunctionComponent } from 'react'; @@ -139,6 +139,7 @@ export const ChooseGroupMembersModal: FunctionComponent = ({ { setSearchTerm(event.target.value); diff --git a/ts/components/leftPane/LeftPaneArchiveHelper.tsx b/ts/components/leftPane/LeftPaneArchiveHelper.tsx index ad71787ee..1ec99e2e7 100644 --- a/ts/components/leftPane/LeftPaneArchiveHelper.tsx +++ b/ts/components/leftPane/LeftPaneArchiveHelper.tsx @@ -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'; @@ -13,7 +13,7 @@ import { RowType } from '../ConversationList'; import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem'; import type { LocalizerType } from '../../types/Util'; import type { ConversationType } from '../../state/ducks/conversations'; -import { LeftPaneMainSearchInput } from '../LeftPaneMainSearchInput'; +import { LeftPaneSearchInput } from '../LeftPaneSearchInput'; import type { LeftPaneSearchPropsType } from './LeftPaneSearchHelper'; import { LeftPaneSearchHelper } from './LeftPaneSearchHelper'; @@ -91,7 +91,7 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper): ReactChild { return ( ): ReactChild { return ( ; @@ -90,7 +90,7 @@ export class LeftPaneInboxHelper extends LeftPaneHelper updateSearchTerm: (searchTerm: string) => unknown; }>): ReactChild { return ( - unknown; }>): ReactChild { return ( - (null);", - "reasonCategory": "usageTrusted", - "updated": "2022-01-26T23:11:05.369Z" - }, { "rule": "React-useRef", "path": "ts/components/LeftPaneSearchInput.tsx", - "line": " const inputRef = useRef(null);", + "line": " const inputRef = useRef(null);", "reasonCategory": "usageTrusted", - "updated": "2021-10-29T22:48:58.354Z", - "reasonDetail": "Only used to focus the input." + "updated": "2022-02-11T20:49:03.879Z" }, { "rule": "React-useRef",