diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 59e4bde45078..40eee6a9aaea 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(); @@ -190,9 +192,13 @@ 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..49fb15ae2b3f 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.tsx @@ -10,6 +10,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; 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'; @@ -38,9 +39,14 @@ const throttleTime = isMobile() ? 200 : 50; function EmojiPickerMenu({onEmojiSelected, activeEmoji, ref}: EmojiPickerMenuProps) { const styles = useThemeStyles(); + const theme = useTheme(); const StyleUtils = useStyleUtils(); const {windowWidth} = useWindowDimensions(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + // 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(); const { @@ -400,9 +406,15 @@ 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..270c36fbe957 100644 --- a/src/components/SelectionList/components/TextInput.tsx +++ b/src/components/SelectionList/components/TextInput.tsx @@ -6,6 +6,8 @@ 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 useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Accessibility from '@libs/Accessibility'; @@ -72,7 +74,13 @@ function TextInput({ focusTextInput, }: TextInputProps) { 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, value, @@ -155,12 +163,14 @@ 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} + placeholderTextColor={theme.textSupporting} maxLength={maxLength} onChangeText={handleTextInputChange} inputMode={inputMode} @@ -173,6 +183,11 @@ function TextInput({ errorText={errorText} autoCorrect={!disableAutoCorrect} shouldInterceptSwipe={shouldInterceptSwipe ?? false} + // 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 && ( diff --git a/src/styles/index.ts b/src/styles/index.ts index a0ef67de052e..6c9e5cf7b2b7 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, diff --git a/tests/perf-test/SelectionList.perf-test.tsx b/tests/perf-test/SelectionList.perf-test.tsx index 8dd999a19838..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,12 +65,17 @@ jest.mock('@react-navigation/stack', () => ({ useCardAnimation: () => {}, })); -jest.mock('@react-navigation/native', () => ({ - ...jest.requireActual>('@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,