Skip to content

Migrate Button to ButtonComposed (batch 3) - #96627

Merged
mountiny merged 4 commits into
Expensify:mainfrom
software-mansion-labs:button-migration-pr3-3rd
Jul 27, 2026
Merged

mountiny merged 4 commits into
Expensify:mainfrom
software-mansion-labs:button-migration-pr3-3rd

Conversation

@mikolajpochec

Copy link
Copy Markdown
Member

This is a duplicate of closed PR: #96270

Explanation of Change

This is batch 3 (PR3 of 9) of an ongoing effort to migrate every direct <Button> usage (import Button from '@components/Button') over to the new composed ButtonComposed, so the old Button component can eventually be deprecated. This batch covers 33 files / ~41 button instances (34 files were listed in the migration issue for this batch. One file, src/pages/workspace/members/WorkspaceOwnerChangeErrorPage.tsx, doesn't exist under that name — the real file is src/pages/workspace/members/members/DynamicWorkspaceOwnerChangeErrorPage.tsx)

Fixed Issues

$ #95171
PROPOSAL: #83762 (comment)

Tests

For every migrated button, verify that behaviour is unchanged from before the migration:

  1. Label renders correctly and is not clipped or truncated.
  2. Variant/color is correct — success = green, danger = red, default = grey.
  3. Size is correct — SMALL vs LARGE height/padding matches the previous look.
  4. Disabled state — the button greys out and is non-interactive when its precondition isn't met (dirty check, required field, offline).
  5. Loading state — the spinner replaces the label and the button is non-interactive while a request is in flight (isLoading).
  6. Press action — tapping fires the same documented action as before (navigates / submits / opens modal / external link).
  7. Enter-key shortcut (where applicable) — pressing Enter on web fires the same action (<Button.KeyboardShortcut>).

Moreover, I divided migrated buttons into several categories, based on their custom styling or behavior which is applied in the application:

  • Plain, always-enabled button, no conditional state (baseline sanity check)
  • Disabled while offline (isDisabled={isOffline})
  • Loading spinner while a request is in flight (isLoading)
  • Responsive layout that changes with window size / orientation
  • Visually "disabled" but still clickable — dimmed for read-only/permission reasons, but onPress still opens a read-only modal instead of doing nothing (distinct from the true offline-disabled case above)
  • Confirm-modal-gated action — pressing opens a confirmation modal rather than acting directly
  • Multi-branch/conditional button — variant, label, or visibility changes depending on account/policy state

For each of these categories, detailed test steps are described in sections below:

Plain button
Preconditions
  1. Be part of a Control/Collect workspace.
  2. Turn on Taxes in Workspaces > Workspace > More features.
Test steps
  1. Navigate to Workspaces > Workspace > Taxes.
  2. Click Add rate.
  3. Click on Value.
Expected behavior

Button on the bottom should render correctly and display Save text. It should match the
rendering from a production build.


Button with a loading spinner

While loading spinner state is not easily achievable, there are several ways to reach this behavior on web:

  1. Navigate to Settings > Profile > Status.
    Option A: Click right mouse button on page and go to Console. There, paste Onyx.merge('settingsStatusSetForm', {isLoading: true}) and press Enter.
    Option B: Open file StatusPage.tsx in the application's codebase and replace isLoading={isFormLoading} with isLoading={true}.
Expected behavior

The button's text is replaced by a spinner and the button can't be clicked.


Button whose layout adapts to screen size, and looks greyed out for some members
Preconditions
  1. Be an Admin of a workspace where the Expensify Card feature has not been turned on yet
    (a promo banner with this button only shows up in that case).
Test steps
  1. As the Admin: navigate to Workspaces > Workspace > Company cards.
  2. With the promo banner's Learn more button visible, resize the browser window between
    narrow and wide (or rotate the device/simulator).
Expected behavior

As Admin: the button's layout changes between a full-width row and a smaller inline button as the
window gets narrower or wider, with no cut-off text at either size.


Button that looks greyed out for one specific workspace role
Preconditions
  1. Turn on HR under Workspaces > Workspace > More features.
  2. Under Workspaces > Workspace > Members, change the test account's role to
    Auditor. (A regular "Member" role won't even be able to open this page at all — you'll
    need to be on Auditor specifically to see the greyed-out button described below.)
Test steps
  1. Navigate to Workspaces > Workspace > HR.
  2. Pick any provider that isn't connected yet and click Connect.
  3. Change the same account's role back to Admin, reload the page, and click Connect
    again.
Expected behavior

As Auditor: the Connect button looks greyed out but is still clickable — clicking it pops up
a message saying you can view this but can't make changes, instead of connecting anything. As
Admin: the same button looks normal (not greyed out), and clicking it actually starts connecting.


Button that opens a confirmation popup before doing anything
Preconditions

None — any account works.

Test steps
  1. Navigate to Settings > Security.
  2. Click Lock account (may be listed under "Report suspicious activity").
Expected behavior

Clicking Lock account should never lock the account right away — a confirmation popup should
appear first, asking you to confirm. Only clicking the confirm button inside that popup should
actually lock the account (and briefly show a loading state while it does). The button and popup
should look and behave the same as before this change.


Button that changes label/appearance depending on account state
Preconditions
  1. The account you're logged in as needs to be the actual owner of a paid workspace (Team or
    Control plan) — just being an Admin of one isn't enough; if you're not the owner, the whole
    Subscription page will appear blank.
Test steps
  1. Navigate to Settings > Subscription.
  2. Look at the button under the "Your plan" section.
  3. Click Explore all plans (next to the "Your plan" heading) to open a comparison popup.
  4. In that popup, look at the column for your current plan.
  5. In that popup, look at the column for the other plan, then click its button.
Expected behavior
  • Step 2: the button says Edit subscription and takes you to the subscription details page.
  • Step 4: the column for your current plan shows plain text saying it's your current plan — no
    clickable button there.
  • Step 5: the other plan's column shows a real button, either Downgrade or Upgrade.
    Clicking it either applies the change right away, or takes you to a separate upgrade/downgrade
    page, depending on your account setup — either way, no crash or blank screen.

All of the above should look and behave the same as they did before this change.

Offline tests

Similarly to the regular tests, offline testing steps take focus on one of the representatives for this category:

Test steps
  1. Navigate to Settings > Security > Copilot access.
  2. Click Add copilot, enter an email, pick an access role, and continue to the confirmation
    step.
  3. Turn off your device's network connection (airplane mode, or disable Wi-Fi/data) while on this
    screen.
  4. Turn the network back on.
  5. Turn off the network again, then reload the page while still offline, and reconnect
    afterward.
Expected behavior

Whenever you're offline — whether you went offline while already on the page, or loaded the page
while already offline — the Add copilot button should look greyed out and not respond to
clicks. As soon as you're back online, it should immediately look normal and clickable again,
with no delay or stuck state.

QA Steps

Same as tests/offline 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
add_delegate_offline_behavior_android.mp4
buttons_admin_action_hr_android.mp4
company_cards_width_behavior_android.mp4
dangerous_buttons_lock_account_android.mp4
disabled_buttons_alt_action_hr_android.mp4
multiple_states_buttons_down_upgrade_android.mp4
status_activityindicator_behavior_android.mp4
tax_value_button_plain_android.mp4
Android: mWeb Chrome
add_delegate_offline_behavior_android_web.mp4
company_cards_width_behavior_android_web.mp4
dangerous_buttons_lock_account_android_web.mp4
disabled_buttons_alt_action_hr_android_web.mp4
multiple_states_buttons_down_upgrade_android_web.mp4
status_activityindicator_behavior_android_web.mp4
tax_value_button_plain_android_web.mp4
iOS: Native
add_delegate_offline_behavior_ios.mp4
buttons_admin_action_hr_ios.mp4
company_cards_width_behavior_ios.mp4
dangerous_buttons_lock_account_ios.mp4
disabled_buttons_alt_action_hr_ios.mp4
multiple_states_buttons_down_upgrade_ios.mp4
status_activityindicator_behavior_ios.mp4
tax_value_button_plain_ios.mp4
iOS: mWeb Safari
add_delegate_offline_behavior_ios_web.mp4
company_cards_width_behavior_ios_web.mp4
dangerous_buttons_lock_account_ios_web.mp4
disabled_buttons_alt_action_hr_ios_web.mp4
multiple_states_buttons_down_upgrade_ios_web.mp4
status_activityindicator_behavior_ios_web.mp4
tax_value_button_plain_ios_web.mp4
MacOS: Chrome / Safari
add_delegate_offline_behavior_web.mp4
buttons_admin_action_hr_web.mp4
company_cards_width_behavior_web.mp4
dangerous_buttons_lock_account_web.mp4
disabled_buttons_alt_action_hr_web.mp4
multiple_states_buttons_down_upgrade_web.mp4
status_activityindicator_behavior_web.mp4
tax_value_button_plain_web.mp4

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@mikolajpochec

Copy link
Copy Markdown
Member Author

I have read the CLA Document and I hereby sign the CLA

exfy-clabot Bot added a commit to Expensify/CLA that referenced this pull request Jul 21, 2026
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

@Guccio163 Guccio163 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 👍

@mikolajpochec
mikolajpochec marked this pull request as ready for review July 21, 2026 11:43
@mikolajpochec
mikolajpochec requested review from a team as code owners July 21, 2026 11:43
@melvin-bot
melvin-bot Bot requested review from ZhenjaHorbach and trjExpensify and removed request for a team July 21, 2026 11:43
@melvin-bot

melvin-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

@ZhenjaHorbach 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 a team and mountiny and removed request for a team July 21, 2026 11:43
@melvin-bot

melvin-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

@mountiny 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]

@trjExpensify

Copy link
Copy Markdown
Contributor

PR doesn’t need product input as a refactor PR. Unassigning and unsubscribing myself.

@trjExpensify
trjExpensify removed their request for review July 22, 2026 00:40
@mikolajpochec mikolajpochec changed the title Migrate Button to ComposeButton (batch 3) Migrate Button to ButtonComposed (batch 3) Jul 22, 2026
@mountiny

Copy link
Copy Markdown
Contributor

@ZhenjaHorbach bump on this one, looks like this slipped through the cracks

@ZhenjaHorbach

ZhenjaHorbach commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible 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 checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (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 verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • 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 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.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this 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.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
2026-07-27.14.33.30.mov
2026-07-27.14.34.50.mov
2026-07-27.14.35.52.mov
2026-07-27.14.36.20.mov
2026-07-27.14.36.51.mov
2026-07-27.14.37.16.mov
Android: mWeb Chrome
2026-07-27.14.06.21.mov
2026-07-27.14.07.09.mov
2026-07-27.14.08.39.mov
2026-07-27.14.09.18.mov
2026-07-27.14.09.40.mov
2026-07-27.14.15.22.mov
iOS: HybridApp
2026-07-27.14.33.30.mov
2026-07-27.14.34.50.mov
2026-07-27.14.35.52.mov
2026-07-27.14.36.20.mov
2026-07-27.14.36.51.mov
2026-07-27.14.37.16.mov
iOS: mWeb Safari
2026-07-27.14.06.21.mov
2026-07-27.14.07.09.mov
2026-07-27.14.08.39.mov
2026-07-27.14.09.18.mov
2026-07-27.14.09.40.mov
2026-07-27.14.15.22.mov
MacOS: Chrome / Safari
2026-07-27.13.59.05.mov
2026-07-27.13.59.34.mov
2026-07-27.14.00.52.mov
2026-07-27.14.01.36.mov
2026-07-27.14.02.15.mov
2026-07-27.14.03.08.mov

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

Changes look good!

@mountiny
mountiny merged commit ac7dbb2 into Expensify:main Jul 27, 2026
42 of 44 checks passed
@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.

@github-actions

Copy link
Copy Markdown
Contributor

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

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.45-0 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor

Help site review: no changes required

I reviewed the changes in this PR and no updates to Expensify's help site files (docs/articles) are required, so I did not create a draft docs PR.

Why: This PR is a purely internal component migration — every changed file just swaps import Button from '@components/Button' for import Button from '@components/ButtonComposed' and rewrites the button's props to the composed API (text=<Button.Text>, success/dangervariant, largesize, pressOnEnter<Button.KeyboardShortcut>). Across all 33 files:

  • No user-facing behavior changes — labels, variants, sizes, disabled/loading states, and press actions are preserved.
  • No copy changes — every button label still resolves from the same existing translation key (translate(...)), with no new or renamed strings.
  • No new features, settings, tabs, or workflows are introduced.

Help site articles document user-facing product features and workflows, none of which are affected by this refactor. There is nothing to document.

If you believe a specific user-facing behavior did change and should be reflected in the docs, let me know which one and I'll take another look.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/marcaaron in version: 9.4.45-14 🚀

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

Bundle Size Analysis (Sentry):

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants