Skip to content

Use compact search input size in lists, popovers, and emoji picker#96499

Merged
blimpich merged 7 commits into
mainfrom
claude-compactPopoverSearchInput
Jul 22, 2026
Merged

Use compact search input size in lists, popovers, and emoji picker#96499
blimpich merged 7 commits into
mainfrom
claude-compactPopoverSearchInput

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Updates search inputs that appear above lists / inside popovers to use the smaller, compact size we already use above our tables, with the two responsive sizes: 34px on web/desktop and 46px on mobile (narrow layout).

Previously these inputs rendered a tall (variables.inputHeight = 52px) BaseTextInput with a floating label. The compact height cannot fit a floating label, so — matching how the "above the table" SearchPageInput works — the label is now rendered as a placeholder instead, while accessibility is preserved via accessibilityLabel.

Per shawnborton's direction on the issue, this is a global change applied to the shared search input, so it affects all search inputs that appear above lists (Search filter popovers, member/selection-list search, etc.) plus the emoji picker.

What changed:

  • Added compact wrapper styles listSearchInputWideWrapper (height: 34) and listSearchInputNarrowWrapper (height: 46) in src/styles/index.ts.
  • SelectionList's shared search input (src/components/SelectionList/components/TextInput.tsx) now renders the compact, placeholder-based input responsively.
  • The emoji picker search input (EmojiPickerMenu/index.tsx and index.native.tsx) uses the same compact, placeholder-based styling.

🤖 Draft opened by MelvinBot. Web-verified on 3 surfaces (see screenshots below). Still needs cross-platform QA (Android/iOS native + mWeb) and a broader regression pass since SelectionList is a widely-used shared component. React Compiler compliance check should be confirmed by CI (it couldn't run in the automation environment); oxfmt, eslint, and tsc typecheck all passed locally.

Fixed Issues

$ #96491
PROPOSAL:

Tests

  1. Sign in to the app.
  2. Start a new chat (FAB → Start chat). Verify the member-search input at the top of the selection list is the compact size (34px on web/desktop, 46px on narrow/mobile layout) and that its label now appears as a placeholder (no floating label).
  3. Open the Search page, open a filter popover that contains a search input (e.g. Category or Tag). Verify that search input is the same compact size with a placeholder.
  4. Open the emoji picker (react to a message, or the emoji button in the composer). Verify the search input at the top is the compact size.
  5. In each case, confirm the input is still vertically aligned with the list/content below it, text is not clipped, and typing/filtering works as before.
  6. Verify the input is still announced correctly by a screen reader (the label is preserved via accessibilityLabel).
  7. Verify that no errors appear in the JS console.

Offline tests

This is a purely presentational change (input height + label→placeholder) with no network dependency, so behavior is identical online and offline.

  1. Turn off your network connection.
  2. Open any of the surfaces above (start-chat member search, a Search filter popover, the emoji picker).
  3. Verify the compact search input renders and behaves exactly as it does online.

QA Steps

Same as tests.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

Automated web verification (MelvinBot):

Filter popover: compact Search input with placeholder above the list
Emoji picker: compact Search input at top
Selection list (Find member): compact search input with placeholder

Co-authored-by: Shawn Borton <shawnborton@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚧 shawnborton has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
.../components/SelectionList/components/TextInput.tsx 97.77% <100.00%> (ø)
src/styles/index.ts 56.18% <ø> (ø)
...nents/EmojiPicker/EmojiPickerMenu/index.native.tsx 0.00% <0.00%> (ø)
...c/components/EmojiPicker/EmojiPickerMenu/index.tsx 0.00% <0.00%> (ø)

… context

Co-authored-by: Shawn Borton <shawnborton@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed the failing branch-perf-tests check. 🤖

Root cause: This PR added a useResponsiveLayout() call to the shared SelectionList search input (TextInput.tsx:77). On native, useResponsiveLayout reads react-navigation contexts via useContext(NavigationContainerRefContext) / useContext(NavigationContext) (useResponsiveLayout/index.native.ts:55-56). The SelectionList perf test mocks @react-navigation/native by replacing the whole module, so those two context objects were undefined. useContext(undefined) then threw TypeError: Cannot read properties of undefined (reading '$$typeof'), crashing all 4 SelectionList perf tests. This only surfaced now because main's TextInput never called useResponsiveLayout (baseline-perf-tests passed; branch-perf-tests failed).

Fix: Spread jest.requireActual('@react-navigation/native') in the mock so the real context objects stay defined, keeping the existing overrides. This mirrors how the other perf tests already mock it (e.g. OptionsListUtils.perf-test.ts, SearchRouter.perf-test.tsx). See SelectionList.perf-test.tsx:68-78.

Notes
  • The other red check, PR Author Checklist / checklist, fails only because the human author checklist boxes are unticked — that's a manual step for a person, not something I fill out on your behalf.
  • I couldn't execute the perf suite in this automation environment (local npm/jest runs are sandboxed off), so the fix is verified by root-cause analysis + the established precedent in the sibling perf tests. CI will re-run branch-perf-tests on this push to confirm.

@shawnborton

Copy link
Copy Markdown
Contributor

@MelvinBot the placeholder text color is incorrect, it should use textSupporting from our theme colors. Can you please update?
CleanShot 2026-07-20 at 12 41 31@2x

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The only failing check is PR Author Checklist / checklist, which reports:

You completed 0 out of 38 checklist items with 38 unfinished items

Every other check is passing (validate-perf-tests is still just pending).

There's no code fix to push here — this check fails because the PR Author Checklist boxes in the description aren't ticked. That's a human checklist meant to confirm a person actually verified each item (testing on all platforms, screenshots, offline behavior, etc.), so I won't check those boxes on your behalf.

To clear it, the checklist items in the PR body need to be reviewed and checked off by a human author/reviewer once the corresponding work is done. Note the PR still needs cross-platform QA (Android/iOS native + mWeb) and the Tests/QA Steps sections filled in, which the checklist is gating on.

Co-authored-by: Shawn Borton <shawnborton@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — updated the placeholder to use textSupporting. The compact search input renders via BaseTextInput, which defaults its placeholder to theme.placeholderText; I now pass placeholderTextColor={theme.textSupporting} so it matches our theme (same as the "above the table" SearchAutocompleteInput).

Applied to all three compact search inputs this PR touches:

oxfmt, eslint, and tsc all pass. Pushed to the branch.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Still the same situation — the only failing check is PR Author Checklist / checklist:

You completed 0 out of 38 checklist items with 38 unfinished items

The latest commit (59e916f) triggered a fresh CI run, so the code-level checks (tests, typecheck, perf, builds) are currently re-running/pending — they all passed on the prior run.

There's nothing for me to push a code fix for. This check is gated on the human PR Author Checklist boxes in the description being ticked, which confirm a person verified each item (cross-platform testing, screenshots, offline behavior, etc.). I won't check those on your behalf, so the check will keep failing until an author/reviewer completes the checklist once the underlying work (cross-platform QA + filling in the Tests/QA Steps sections) is done.

@github-actions

Copy link
Copy Markdown
Contributor

🚧 shawnborton has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

@shawnborton

Copy link
Copy Markdown
Contributor

@MelvinBot the placeholder text color doesn't look correct in that latest adhoc build above, can you double check?

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I double-checked, and the change is in that adhoc build (it was built from 59e916f, which is the commit that added placeholderTextColor={theme.textSupporting}). I confirmed both inputs still carry it on the branch:

So the placeholder is now rendering theme.textSupporting, which resolves to:

  • Light mode: #76847E (this is darker than the old default placeholder #A2A9A3)
  • Dark mode: #AFBBB0 (this is lighter than the old default placeholder #8B9C8F)

Note the relative brightness flips between themes — the default placeholderText token and textSupporting sit on opposite sides of the neutral ramp per theme. So if the build you looked at was in dark mode, the placeholder will now look lighter, which may be what reads as "off."

I wasn't able to grab a fresh screenshot in the automation environment to compare against your eye, so to make sure I fix the right thing: which platform/theme were you viewing (e.g. web light vs. dark, iOS native)? A screenshot of what looks wrong would help. If textSupporting isn't the token you actually want here, just tell me which one and I'll swap it.

@shawnborton

Copy link
Copy Markdown
Contributor

@Expensify/design are we cool with the search input being small but inputs that have filter values are larger? I think it's probably okay but definitely a bit noticeable:

CleanShot.2026-07-20.at.19.46.58.mp4

@dubielzyk-expensify

Copy link
Copy Markdown
Contributor

Yeah I think it's okay cause they're different purposes so I don't mind it 👍

@shawnborton

Copy link
Copy Markdown
Contributor

Sweet, thanks for confirming! We can start getting this into review then!

@shawnborton
shawnborton marked this pull request as ready for review July 21, 2026 01:11
@shawnborton
shawnborton requested review from a team as code owners July 21, 2026 01:11
@melvin-bot
melvin-bot Bot requested a review from ahmedGaber93 July 21, 2026 01:11
@melvin-bot

melvin-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

@ahmedGaber93 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot
melvin-bot Bot requested review from joekaufmanexpensify and removed request for a team July 21, 2026 01:11
@shawnborton

Copy link
Copy Markdown
Contributor

@MelvinBot can you please fill out the author PR checklist as best as you can?

@ahmedGaber93 I will need your help testing all platforms given this is a Melvin PR.

@shawnborton

Copy link
Copy Markdown
Contributor

Correct - those inputs should change height ever so slightly based on platform.

@dannymcclain

Copy link
Copy Markdown
Contributor

Alright cool I'm on board 👍 ⛵

@shawnborton

Copy link
Copy Markdown
Contributor

Okay cool, @ahmedGaber93 what's left for review?

@joekaufmanexpensify joekaufmanexpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good for product 👍

errorText={errorText}
autoCorrect={!disableAutoCorrect}
shouldInterceptSwipe={shouldInterceptSwipe ?? false}
touchableInputWrapperStyle={shouldUseNarrowLayout ? styles.listSearchInputNarrowWrapper : styles.listSearchInputWideWrapper}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OP say:

Note that we use two different sizes: one for web/desktop, and one for mobile.

However, shouldUseNarrowLayout is always true in the RHP on web, so the input heights are actually:

  • Web RHP: 46
  • Web popover: 34
  • Mobile: 46

If this isn't the intended behavior, we could use isSmallScreenWidth instead.

@shawnborton

@ahmedGaber93 ahmedGaber93 Jul 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

height: 34 Image Image Image
height: 46 Image Image Image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MelvinBot please implement the feedback above. We want the input size to change based on device width, not RHP width.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — pushed in 4490061.

The compact search input now sizes off device width (isSmallScreenWidth) instead of shouldUseNarrowLayout, which was also true inside the RHP/narrow pane on web and forced the 46px size there. Result:

  • Web (desktop + RHP + popover): 34px
  • Mobile: 46px

Applied to both web variants that use this compact styling — SelectionList/components/TextInput.tsx and EmojiPicker/EmojiPickerMenu/index.tsx — so they stay in sync. EmojiPickerMenu/index.native.tsx already hardcodes the narrow values, which is correct since isSmallScreenWidth is always true on native. The other shouldUseNarrowLayout usages in the emoji picker (picker container/header layout) are unrelated and left untouched.

oxfmt, eslint (lint-changed), and typecheck all passed. The React Compiler check surfaces the same pre-existing [InferMutationAliasingEffects] invariant on EmojiPickerMenu/index.tsx that was present before this edit (identical error/lines on the base), so it's not a regression from this change.

@ahmedGaber93 ahmedGaber93 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Just one small comment about the expected behavior: #96499 (comment)

We also need to ask Melvin to fix the the git conflict and mark all author checklist as checked

@melvin-bot

melvin-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

We did not find an internal engineer to review this PR, trying to assign a random engineer to #96491 as well as to this PR... Please reach out for help on Slack if no one gets assigned!

@melvin-bot
melvin-bot Bot requested a review from blimpich July 22, 2026 04:30
@shawnborton

Copy link
Copy Markdown
Contributor

@MelvinBot please fix the conflict

…SearchInput

Co-authored-by: Shawn Borton <shawnborton@users.noreply.github.com>

# Conflicts:
#	tests/perf-test/SelectionList.perf-test.tsx
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Resolved the merge conflict and pushed the merge commit — the PR is mergeable again.

Conflict: tests/perf-test/SelectionList.perf-test.tsx — the jest.mock('@react-navigation/native', ...) block. Both sides made the same essential change (spread the real module, then override useFocusEffect, useIsFocused, and createNavigationContainerRef); main used jest.requireActual<Record<string, unknown>>(...) while this branch used the typed jest.requireActual<typeof NativeNavigation>(...) with a comment explaining why the spread is needed.

Resolution: Kept this branch's typed version (the NativeNavigation type import already exists in the file), since it's functionally a superset of main's change. All of the PR's own source changes (src/styles/index.ts, SelectionList/components/TextInput.tsx, and both EmojiPickerMenu files) are intact.

Please re-run through the test steps since main was merged in, and let CI finish its checks.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⚠️ MelvinBot CI Fix Loop Detected

MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR.

Next steps: A human should review the recent commits and resolve the conflicting CI errors manually.

@github-actions

Copy link
Copy Markdown
Contributor

🚧 shawnborton has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

@shawnborton

Copy link
Copy Markdown
Contributor

Feeling good to me, let's get this into final review!

@ahmedGaber93

Copy link
Copy Markdown
Contributor

@shawnborton Could you please ask Melvin to complete this tasks?

  • Fix the ESLint error by adding a justified // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth with the reasoning

  • Mark all author checklist as checked

@shawnborton

Copy link
Copy Markdown
Contributor

@MelvinBot please Fix the ESLint error by adding a justified // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth with the reasoning

… inputs

Co-authored-by: Shawn Borton <shawnborton@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed the ESLint failure. Both errors came from the shared search-input change intentionally reading isSmallScreenWidth (device width) rather than shouldUseNarrowLayout — so I added a justified // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth above each destructure explaining why:

The reasoning: sizing by shouldUseNarrowLayout would grow the input to the tall mobile size whenever it renders inside an RHP/narrow pane on web/desktop, so isSmallScreenWidth is used deliberately to keep it compact (34px web/desktop, 46px mobile).

./scripts/lint.sh now passes clean on both files, and I ran npm run fmt. Pushed as c649e16.

@blimpich
blimpich merged commit cca70da into main Jul 22, 2026
40 checks passed
@blimpich
blimpich deleted the claude-compactPopoverSearchInput branch July 22, 2026 19:47
@github-actions

Copy link
Copy Markdown
Contributor

🚧 blimpich has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/blimpich in version: 9.4.43-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor Author

No help site changes required

I reviewed the changes in this PR against the help site articles under docs/articles, and no updates are needed.

Why: This is a purely presentational change. It:

  • Shrinks the search inputs that appear above lists/popovers and in the emoji picker to the compact size (34px web/desktop, 46px mobile).
  • Renders the field's label as a placeholder instead of a floating label (accessibility preserved via accessibilityLabel).

No user-facing feature, tab name, setting, button label, or workflow changed — only input height and label presentation. Help articles document features and workflows, not input dimensions or label styling, and none of them describe or screenshot these search inputs (the emoji picker and selection-list search aren't referenced anywhere in docs/articles). So there's nothing to update and no draft docs PR was created.

@shawnborton, no linked help site PR was needed here since the change is cosmetic-only. If you feel any article should be refreshed regardless, let me know and I'll open one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants