From 0496bd15b69690afd1a67bff71e1eac3dccd189b Mon Sep 17 00:00:00 2001 From: "Shawn Borton (via MelvinBot)" Date: Mon, 20 Jul 2026 15:42:14 +0000 Subject: [PATCH 1/6] Use compact search input size in lists, popovers, and emoji picker Co-authored-by: Shawn Borton --- .../EmojiPicker/EmojiPickerMenu/index.native.tsx | 5 ++++- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 5 ++++- .../SelectionList/components/TextInput.tsx | 12 +++++++++--- src/styles/index.ts | 5 +++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 59e4bde45078..57270657bf14 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -190,9 +190,12 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji, ref}: EmojiPickerMenuPro { setSearchText(text); filterEmojis(text); diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 87880c220e76..f56246c1d1c0 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -400,9 +400,12 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji, ref}: EmojiPickerMenuPro > { setSearchText(text); filterEmojis(text); diff --git a/src/components/SelectionList/components/TextInput.tsx b/src/components/SelectionList/components/TextInput.tsx index 42e7308c142a..a2cd2c1ec411 100644 --- a/src/components/SelectionList/components/TextInput.tsx +++ b/src/components/SelectionList/components/TextInput.tsx @@ -6,6 +6,7 @@ import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useDebouncedAccessibilityAnnouncement from '@hooks/useDebouncedAccessibilityAnnouncement'; import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode'; import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Accessibility from '@libs/Accessibility'; @@ -73,6 +74,7 @@ function TextInput({ }: TextInputProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const { label, value, @@ -155,12 +157,13 @@ function TextInput({ onKeyPress={onKeyPress} onFocus={handleFocus} onBlur={handleBlur} - label={label} - accessibilityLabel={accessibilityLabel} + // Use the smaller "above the table" search input size. The compact height cannot fit a + // floating label, so the label is rendered as a placeholder while a11y is preserved via accessibilityLabel. + accessibilityLabel={accessibilityLabel ?? label} hint={hint} role={CONST.ROLE.PRESENTATION} value={value} - placeholder={placeholder} + placeholder={placeholder ?? label} maxLength={maxLength} onChangeText={handleTextInputChange} inputMode={inputMode} @@ -173,6 +176,9 @@ function TextInput({ errorText={errorText} autoCorrect={!disableAutoCorrect} shouldInterceptSwipe={shouldInterceptSwipe ?? false} + touchableInputWrapperStyle={shouldUseNarrowLayout ? styles.listSearchInputNarrowWrapper : styles.listSearchInputWideWrapper} + textInputContainerStyles={[styles.pb0, shouldUseNarrowLayout ? styles.ph3 : styles.ph2]} + inputStyle={[styles.w100, styles.lineHeightUndefined, shouldUseNarrowLayout ? undefined : styles.fontSizeLabel]} /> {shouldShowHeaderMessage && ( diff --git a/src/styles/index.ts b/src/styles/index.ts index 1d6208ad6700..9227da0f80b8 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5220,6 +5220,11 @@ const staticStyles = (theme: ThemeColors) => searchPageInputWideTouchableWrapper: {height: 34, width: 202}, searchPageInputNarrowTouchableWrapper: {height: 46}, + // Compact search inputs that appear above lists/popovers. Matches the smaller + // "above the table" search input heights (34 on web/desktop, 46 on mobile). + listSearchInputWideWrapper: {height: 34}, + listSearchInputNarrowWrapper: {height: 46}, + walletStaticIllustration: { width: 262, height: 152, From 6cdd210b6eb1bd1c9d48a109b1e879425ebe6c10 Mon Sep 17 00:00:00 2001 From: "Shawn Borton (via MelvinBot)" Date: Mon, 20 Jul 2026 16:35:09 +0000 Subject: [PATCH 2/6] Fix SelectionList perf test crash from useResponsiveLayout navigation context Co-authored-by: Shawn Borton --- tests/perf-test/SelectionList.perf-test.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/perf-test/SelectionList.perf-test.tsx b/tests/perf-test/SelectionList.perf-test.tsx index 4b1b3c774530..fdf82f437f1a 100644 --- a/tests/perf-test/SelectionList.perf-test.tsx +++ b/tests/perf-test/SelectionList.perf-test.tsx @@ -9,6 +9,7 @@ import type {KeyboardStateContextValue} from '@components/withKeyboardState'; import variables from '@styles/variables'; +import type * as NativeNavigation from '@react-navigation/native'; import type {ComponentType} from 'react'; import type ReactNative from 'react-native'; @@ -64,11 +65,17 @@ jest.mock('@react-navigation/stack', () => ({ useCardAnimation: () => {}, })); -jest.mock('@react-navigation/native', () => ({ - useFocusEffect: () => {}, - useIsFocused: () => true, - createNavigationContainerRef: jest.fn(), -})); +jest.mock('@react-navigation/native', () => { + // Spread the actual module so context objects like NavigationContainerRefContext and NavigationContext + // remain defined. useResponsiveLayout (native) reads them via useContext, which crashes if they are undefined. + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useFocusEffect: () => {}, + useIsFocused: () => true, + createNavigationContainerRef: jest.fn(), + }; +}); jest.mock('../../src/hooks/useKeyboardState', () => ({ __esModule: true, From 59e916fb373b3d2ea92511bd750246233a384cae Mon Sep 17 00:00:00 2001 From: "Shawn Borton (via MelvinBot)" Date: Mon, 20 Jul 2026 16:52:33 +0000 Subject: [PATCH 3/6] Use textSupporting theme color for compact search input placeholder Co-authored-by: Shawn Borton --- src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx | 3 +++ src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 3 +++ src/components/SelectionList/components/TextInput.tsx | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 57270657bf14..0c0a6b874436 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -9,6 +9,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useSingleExecution from '@hooks/useSingleExecution'; import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -32,6 +33,7 @@ import useEmojiPickerMenu from './useEmojiPickerMenu'; // eslint-disable-next-line @typescript-eslint/no-unused-vars function EmojiPickerMenu({onEmojiSelected, activeEmoji, ref}: EmojiPickerMenuProps) { const styles = useThemeStyles(); + const theme = useTheme(); const {windowWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {translate} = useLocalize(); @@ -191,6 +193,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji, ref}: EmojiPickerMenuPro Date: Tue, 21 Jul 2026 12:20:41 +0000 Subject: [PATCH 4/6] Simplify native emoji picker search input styles: drop always-true shouldUseNarrowLayout branch and redundant w100 Co-authored-by: {"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"} <{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"}@users.noreply.github.com> --- src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 0c0a6b874436..40eee6a9aaea 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -196,9 +196,9 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji, ref}: EmojiPickerMenuPro placeholderTextColor={theme.textSupporting} accessibilityLabel={translate('common.search')} role={CONST.ROLE.PRESENTATION} - touchableInputWrapperStyle={shouldUseNarrowLayout ? styles.listSearchInputNarrowWrapper : styles.listSearchInputWideWrapper} - textInputContainerStyles={[styles.pb0, shouldUseNarrowLayout ? styles.ph3 : styles.ph2]} - inputStyle={[styles.w100, styles.lineHeightUndefined, shouldUseNarrowLayout ? undefined : styles.fontSizeLabel]} + touchableInputWrapperStyle={styles.listSearchInputNarrowWrapper} + textInputContainerStyles={[styles.pb0, styles.ph3]} + inputStyle={[styles.lineHeightUndefined]} onChangeText={(text: string) => { setSearchText(text); filterEmojis(text); From 4490061d499d6884e10322a3b3eddac02b8ed21b Mon Sep 17 00:00:00 2001 From: "{\"message\":\"Not Found\",\"documentation_url\":\"https://docs.github.com/rest/issues/comments#get-an-issue-comment\",\"status\":\"404\"} (via MelvinBot)" Date: Wed, 22 Jul 2026 14:07:23 +0000 Subject: [PATCH 5/6] Size compact search input by device width (isSmallScreenWidth) not RHP width Co-authored-by: {"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"} <{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"}@users.noreply.github.com> --- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 10 ++++++---- src/components/SelectionList/components/TextInput.tsx | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index 0ae4b0bd7f6c..c6c1cf7b24b9 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -42,7 +42,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji, ref}: EmojiPickerMenuPro const theme = useTheme(); const StyleUtils = useStyleUtils(); const {windowWidth} = useWindowDimensions(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const {translate} = useLocalize(); const {singleExecution} = useSingleExecution(); const { @@ -406,9 +406,11 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji, ref}: EmojiPickerMenuPro placeholderTextColor={theme.textSupporting} accessibilityLabel={translate('common.search')} role={CONST.ROLE.PRESENTATION} - touchableInputWrapperStyle={shouldUseNarrowLayout ? styles.listSearchInputNarrowWrapper : styles.listSearchInputWideWrapper} - textInputContainerStyles={[styles.pb0, shouldUseNarrowLayout ? styles.ph3 : styles.ph2]} - inputStyle={[styles.w100, styles.lineHeightUndefined, shouldUseNarrowLayout ? undefined : styles.fontSizeLabel]} + // Size is based on device width (isSmallScreenWidth), not shouldUseNarrowLayout, so the search input + // stays the compact 34px size on web/desktop and only grows to 46px on mobile. + touchableInputWrapperStyle={isSmallScreenWidth ? styles.listSearchInputNarrowWrapper : styles.listSearchInputWideWrapper} + textInputContainerStyles={[styles.pb0, isSmallScreenWidth ? styles.ph3 : styles.ph2]} + inputStyle={[styles.w100, styles.lineHeightUndefined, isSmallScreenWidth ? undefined : styles.fontSizeLabel]} onChangeText={(text: string) => { setSearchText(text); filterEmojis(text); diff --git a/src/components/SelectionList/components/TextInput.tsx b/src/components/SelectionList/components/TextInput.tsx index eddfd89068e2..fcec0afce3bd 100644 --- a/src/components/SelectionList/components/TextInput.tsx +++ b/src/components/SelectionList/components/TextInput.tsx @@ -76,7 +76,7 @@ function TextInput({ const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isSmallScreenWidth} = useResponsiveLayout(); const { label, value, @@ -179,9 +179,11 @@ function TextInput({ errorText={errorText} autoCorrect={!disableAutoCorrect} shouldInterceptSwipe={shouldInterceptSwipe ?? false} - touchableInputWrapperStyle={shouldUseNarrowLayout ? styles.listSearchInputNarrowWrapper : styles.listSearchInputWideWrapper} - textInputContainerStyles={[styles.pb0, shouldUseNarrowLayout ? styles.ph3 : styles.ph2]} - inputStyle={[styles.w100, styles.lineHeightUndefined, shouldUseNarrowLayout ? undefined : styles.fontSizeLabel]} + // Size is based on device width (isSmallScreenWidth), not shouldUseNarrowLayout, so the input stays + // the compact 34px size on web/desktop even inside the RHP/narrow pane, and only grows to 46px on mobile. + touchableInputWrapperStyle={isSmallScreenWidth ? styles.listSearchInputNarrowWrapper : styles.listSearchInputWideWrapper} + textInputContainerStyles={[styles.pb0, isSmallScreenWidth ? styles.ph3 : styles.ph2]} + inputStyle={[styles.w100, styles.lineHeightUndefined, isSmallScreenWidth ? undefined : styles.fontSizeLabel]} /> {shouldShowHeaderMessage && ( From c649e169fca11842104949d2cdb308a69837cce8 Mon Sep 17 00:00:00 2001 From: "Shawn Borton (via MelvinBot)" Date: Wed, 22 Jul 2026 17:34:53 +0000 Subject: [PATCH 6/6] Add justified eslint-disable for isSmallScreenWidth in compact search inputs Co-authored-by: Shawn Borton --- src/components/EmojiPicker/EmojiPickerMenu/index.tsx | 4 ++++ src/components/SelectionList/components/TextInput.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx index c6c1cf7b24b9..49fb15ae2b3f 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -42,6 +42,10 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji, ref}: EmojiPickerMenuPro const theme = useTheme(); const StyleUtils = useStyleUtils(); const {windowWidth} = useWindowDimensions(); + // The compact search input must be sized by the physical device width, not by `shouldUseNarrowLayout`. Using + // `shouldUseNarrowLayout` would grow the input to the tall mobile size whenever it is rendered inside an + // RHP/narrow pane on web/desktop, so `isSmallScreenWidth` is intentionally used here to keep it compact. + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const {translate} = useLocalize(); const {singleExecution} = useSingleExecution(); diff --git a/src/components/SelectionList/components/TextInput.tsx b/src/components/SelectionList/components/TextInput.tsx index fcec0afce3bd..270c36fbe957 100644 --- a/src/components/SelectionList/components/TextInput.tsx +++ b/src/components/SelectionList/components/TextInput.tsx @@ -76,6 +76,10 @@ function TextInput({ const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); + // The compact search input must be sized by the physical device width, not by `shouldUseNarrowLayout`. Using + // `shouldUseNarrowLayout` would grow the input to the tall mobile size whenever it is rendered inside an + // RHP/narrow pane on web/desktop, so `isSmallScreenWidth` is intentionally used here to keep it compact. + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); const { label,