From 4147f22519f643751b8b54c1e1ef2ca4004cef40 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Tue, 10 Mar 2026 14:56:49 -0400 Subject: [PATCH 1/2] add tabIndex to stop going from hidden to showing through widget causing the search to be focused --- shared/chat/inbox/filter-row.tsx | 143 ++++++++++-------- shared/common-adapters/plain-input.d.ts | 1 + .../common-adapters/plain-input.desktop.tsx | 3 +- shared/common-adapters/search-filter.tsx | 2 + 4 files changed, 81 insertions(+), 68 deletions(-) diff --git a/shared/chat/inbox/filter-row.tsx b/shared/chat/inbox/filter-row.tsx index 8a5651be6445..819d7eaf69d7 100644 --- a/shared/chat/inbox/filter-row.tsx +++ b/shared/chat/inbox/filter-row.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/constants/chat2' +import * as Chat from '@/stores/chat' import * as React from 'react' import * as Kb from '@/common-adapters' @@ -14,7 +14,7 @@ type OwnProps = { } -const ConversationFilterInput = React.memo(function ConversationFilterInput(ownProps: OwnProps) { +function ConversationFilterInput(ownProps: OwnProps) { const {onEnsureSelection, onSelectDown, onSelectUp, showSearch} = ownProps const {onQueryChanged: onSetFilter, query: filter} = ownProps @@ -22,58 +22,49 @@ const ConversationFilterInput = React.memo(function ConversationFilterInput(ownP const appendNewChatBuilder = C.useRouterState(s => s.appendNewChatBuilder) const toggleInboxSearch = Chat.useChatState(s => s.dispatch.toggleInboxSearch) - const onStartSearch = React.useCallback(() => { + const onStartSearch = () => { toggleInboxSearch(true) - }, [toggleInboxSearch]) - const onStopSearch = React.useCallback(() => { + } + const onStopSearch = () => { toggleInboxSearch(false) - }, [toggleInboxSearch]) + } const inputRef = React.useRef(null) - const onKeyDown = React.useCallback( - (e: React.KeyboardEvent) => { - if (e.key === 'Escape') { - onStopSearch() - } else if (e.key === 'ArrowDown') { - e.preventDefault() - e.stopPropagation() - onSelectDown() - } else if (e.key === 'ArrowUp') { + const onKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + onStopSearch() + } else if (e.key === 'ArrowDown') { + e.preventDefault() + e.stopPropagation() + onSelectDown() + } else if (e.key === 'ArrowUp') { + e.preventDefault() + e.stopPropagation() + onSelectUp() + } + } + + const onEnterKeyDown = (e?: React.BaseSyntheticEvent) => { + if (!Kb.Styles.isMobile) { + if (e) { e.preventDefault() e.stopPropagation() - onSelectUp() - } - }, - [onStopSearch, onSelectDown, onSelectUp] - ) - - const onEnterKeyDown = React.useCallback( - (e?: React.BaseSyntheticEvent) => { - if (!Kb.Styles.isMobile) { - if (e) { - e.preventDefault() - e.stopPropagation() - } - onEnsureSelection() - inputRef.current?.blur() } - }, - [onEnsureSelection] - ) + onEnsureSelection() + inputRef.current?.blur() + } + } - const onChange = React.useCallback( - (q: string) => { - if (q !== filter) { - onSetFilter(q) - } - }, - [onSetFilter, filter] - ) + const onChange = (q: string) => { + if (q !== filter) { + onSetFilter(q) + } + } - const onHotKeys = React.useCallback(() => { + const onHotKeys = () => { appendNewChatBuilder() - }, [appendNewChatBuilder]) + } Kb.useHotKey('mod+n', onHotKeys) React.useEffect(() => { @@ -82,29 +73,36 @@ const ConversationFilterInput = React.memo(function ConversationFilterInput(ownP } }, [isSearching]) - const searchInput = ( - - ) + // On mobile, SearchFilter is re-mounted when toggling isSearching + // (see chat/inbox/index.native.tsx). To avoid keyboard flicker, render + // a simple placeholder that triggers search on tap when not searching. + const searchInput = + Kb.Styles.isMobile && !isSearching ? ( + + + + Search + + + ) : ( + + ) return ( ) -}) +} const styles = Kb.Styles.styleSheetCreate( () => @@ -148,6 +146,17 @@ const styles = Kb.Styles.styleSheetCreate( // hacky, redo the layout of this component later isTablet: {maxWidth: 270 - 16 * 2}, }), + searchPlaceholder: { + backgroundColor: Kb.Styles.globalColors.black_10, + borderRadius: Kb.Styles.borderRadius, + height: 32, + justifyContent: 'center', + marginBottom: Kb.Styles.globalMargins.tiny, + marginTop: Kb.Styles.globalMargins.tiny, + paddingLeft: Kb.Styles.globalMargins.xsmall, + paddingRight: Kb.Styles.globalMargins.xsmall, + }, + searchPlaceholderText: {color: Kb.Styles.globalColors.black_50}, whiteBg: {backgroundColor: Kb.Styles.globalColors.white}, }) as const ) diff --git a/shared/common-adapters/plain-input.d.ts b/shared/common-adapters/plain-input.d.ts index eb59d90d6bb1..45d5ce06f9f3 100644 --- a/shared/common-adapters/plain-input.d.ts +++ b/shared/common-adapters/plain-input.d.ts @@ -98,6 +98,7 @@ export type Props = { onKeyDown?: (event: React.KeyboardEvent) => void onKeyUp?: (event: React.KeyboardEvent) => void spellCheck?: boolean + tabIndex?: number // desktop only // Mobile only children?: React.ReactNode allowFontScaling?: boolean diff --git a/shared/common-adapters/plain-input.desktop.tsx b/shared/common-adapters/plain-input.desktop.tsx index acbffba61cbc..2ee05c107220 100644 --- a/shared/common-adapters/plain-input.desktop.tsx +++ b/shared/common-adapters/plain-input.desktop.tsx @@ -40,7 +40,7 @@ const PlainInput = React.memo( const {growAndScroll, multiline, onFocus: _onFocus, selectTextOnFocus, onChangeText} = p const {maxBytes, globalCaptureKeypress, onBlur, onClick, style, resize, maxLength} = p const {rowsMin, rowsMax, textType, padding, flexable = true, type} = p - const {autoFocus, allowKeyboardEvents, placeholder, spellCheck, disabled, value, className} = p + const {autoFocus, allowKeyboardEvents, placeholder, spellCheck, disabled, value, className, tabIndex} = p const inputRef = React.useRef(null) const isComposingIMERef = React.useRef(false) const mountedRef = React.useRef(true) @@ -216,6 +216,7 @@ const PlainInput = React.memo( placeholder, ref: inputRef, spellCheck, + tabIndex, value, ...(maxLength ? {maxLength} : {}), ...(disabled ? {readOnly: true} : {}), diff --git a/shared/common-adapters/search-filter.tsx b/shared/common-adapters/search-filter.tsx index 0590aedf9182..44ba9eeebb52 100644 --- a/shared/common-adapters/search-filter.tsx +++ b/shared/common-adapters/search-filter.tsx @@ -57,6 +57,7 @@ type Props = { onKeyDown?: (event: React.KeyboardEvent) => void onKeyUp?: (event: React.KeyboardEvent) => void onKeyPress?: (event: NativeSyntheticEvent<{key: string}>) => void + tabIndex?: number // desktop only measureRef?: React.RefObject } @@ -201,6 +202,7 @@ const SearchFilter = React.forwardRef(function SearchFil onKeyUp={props.onKeyUp} onKeyPress={props.onKeyPress} onEnterKeyDown={props.onEnterKeyDown} + tabIndex={props.tabIndex} ref={inputRef} hideBorder={true} containerStyle={styles.inputContainer} From 63f551d53b9d1bf00be3baf3b2827df60613ab28 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Tue, 10 Mar 2026 14:59:16 -0400 Subject: [PATCH 2/2] fix --- shared/chat/inbox/filter-row.tsx | 143 +++++++++++++++---------------- 1 file changed, 67 insertions(+), 76 deletions(-) diff --git a/shared/chat/inbox/filter-row.tsx b/shared/chat/inbox/filter-row.tsx index 819d7eaf69d7..8a5651be6445 100644 --- a/shared/chat/inbox/filter-row.tsx +++ b/shared/chat/inbox/filter-row.tsx @@ -1,5 +1,5 @@ import * as C from '@/constants' -import * as Chat from '@/stores/chat' +import * as Chat from '@/constants/chat2' import * as React from 'react' import * as Kb from '@/common-adapters' @@ -14,7 +14,7 @@ type OwnProps = { } -function ConversationFilterInput(ownProps: OwnProps) { +const ConversationFilterInput = React.memo(function ConversationFilterInput(ownProps: OwnProps) { const {onEnsureSelection, onSelectDown, onSelectUp, showSearch} = ownProps const {onQueryChanged: onSetFilter, query: filter} = ownProps @@ -22,49 +22,58 @@ function ConversationFilterInput(ownProps: OwnProps) { const appendNewChatBuilder = C.useRouterState(s => s.appendNewChatBuilder) const toggleInboxSearch = Chat.useChatState(s => s.dispatch.toggleInboxSearch) - const onStartSearch = () => { + const onStartSearch = React.useCallback(() => { toggleInboxSearch(true) - } - const onStopSearch = () => { + }, [toggleInboxSearch]) + const onStopSearch = React.useCallback(() => { toggleInboxSearch(false) - } + }, [toggleInboxSearch]) const inputRef = React.useRef(null) - const onKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Escape') { - onStopSearch() - } else if (e.key === 'ArrowDown') { - e.preventDefault() - e.stopPropagation() - onSelectDown() - } else if (e.key === 'ArrowUp') { - e.preventDefault() - e.stopPropagation() - onSelectUp() - } - } - - const onEnterKeyDown = (e?: React.BaseSyntheticEvent) => { - if (!Kb.Styles.isMobile) { - if (e) { + const onKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + onStopSearch() + } else if (e.key === 'ArrowDown') { + e.preventDefault() + e.stopPropagation() + onSelectDown() + } else if (e.key === 'ArrowUp') { e.preventDefault() e.stopPropagation() + onSelectUp() } - onEnsureSelection() - inputRef.current?.blur() - } - } + }, + [onStopSearch, onSelectDown, onSelectUp] + ) - const onChange = (q: string) => { - if (q !== filter) { - onSetFilter(q) - } - } + const onEnterKeyDown = React.useCallback( + (e?: React.BaseSyntheticEvent) => { + if (!Kb.Styles.isMobile) { + if (e) { + e.preventDefault() + e.stopPropagation() + } + onEnsureSelection() + inputRef.current?.blur() + } + }, + [onEnsureSelection] + ) - const onHotKeys = () => { + const onChange = React.useCallback( + (q: string) => { + if (q !== filter) { + onSetFilter(q) + } + }, + [onSetFilter, filter] + ) + + const onHotKeys = React.useCallback(() => { appendNewChatBuilder() - } + }, [appendNewChatBuilder]) Kb.useHotKey('mod+n', onHotKeys) React.useEffect(() => { @@ -73,36 +82,29 @@ function ConversationFilterInput(ownProps: OwnProps) { } }, [isSearching]) - // On mobile, SearchFilter is re-mounted when toggling isSearching - // (see chat/inbox/index.native.tsx). To avoid keyboard flicker, render - // a simple placeholder that triggers search on tap when not searching. - const searchInput = - Kb.Styles.isMobile && !isSearching ? ( - - - - Search - - - ) : ( - - ) + const searchInput = ( + + ) return ( ) -} +}) const styles = Kb.Styles.styleSheetCreate( () => @@ -146,17 +148,6 @@ const styles = Kb.Styles.styleSheetCreate( // hacky, redo the layout of this component later isTablet: {maxWidth: 270 - 16 * 2}, }), - searchPlaceholder: { - backgroundColor: Kb.Styles.globalColors.black_10, - borderRadius: Kb.Styles.borderRadius, - height: 32, - justifyContent: 'center', - marginBottom: Kb.Styles.globalMargins.tiny, - marginTop: Kb.Styles.globalMargins.tiny, - paddingLeft: Kb.Styles.globalMargins.xsmall, - paddingRight: Kb.Styles.globalMargins.xsmall, - }, - searchPlaceholderText: {color: Kb.Styles.globalColors.black_50}, whiteBg: {backgroundColor: Kb.Styles.globalColors.white}, }) as const )