Skip to content

Conversation

@brianacnguyen
Copy link
Contributor

@brianacnguyen brianacnguyen commented Oct 28, 2025

Description

This PR simplifies the TabsList component by removing complex ScrollView-based tab navigation in favor of a lightweight gesture-based approach.

What is the reason for the change?

The previous TabsList implementation had significant complexity that was unnecessary for the wallet page use case:

  • Heavy ScrollView with horizontal paging and complex scroll handling
  • Multiple refs, timeouts, and state management for scroll coordination
  • Complex height measurement logic with debouncing and timing constants
  • Adjacent tab preloading logic
  • Over 1,600 lines of code across implementation and tests

This complexity caused:

  • Difficult maintenance and debugging
  • Performance overhead from scroll event handlers
  • Conflicts with page-level scrolling in the wallet redesign
  • Flaky tests due to timing-dependent behavior

What is the improvement/solution?

This PR dramatically simplifies TabsList by:

  1. Replacing ScrollView with gesture-based navigation using react-native-gesture-handler

    • Pan gestures detect swipe left/right
    • Direct tab switching with hidden CSS class
    • No scroll event coordination needed
  2. Simplifying tab rendering

    • Removed horizontal ScrollView and paging logic
    • Uses simple show/hide with hidden class
    • All tabs render in place, inactive tabs are hidden
  3. Streamlined caching

    • Only loads the active tab on demand
    • Removed adjacent tab preloading
    • Removed complex timing coordination
  4. Reduced code complexity

    • 389 lines removed from TabsList.tsx
    • 1,357 lines removed from tests
    • Removed refs for scroll coordination, timeout management, and measurement timers
    • Removed dependencies: useTailwind, Dimensions, scroll event types
  5. Improved gesture support

    • Natural swipe thresholds (50px or 500 velocity)
    • Proper worklet execution with react-native-reanimated
    • Skips disabled tabs during swipe navigation

Benefits:

  • ✅ Simpler, more maintainable code
  • ✅ Better compatibility with page-level scrolling
  • ✅ No scroll event overhead
  • ✅ More reliable tests (no timing dependencies)
  • ✅ Cleaner architecture for wallet page integration

Changelog

CHANGELOG entry: Simplified TabsList component with gesture-based navigation

Related issues

Fixes: https://consensyssoftware.atlassian.net/jira/software/c/projects/DSYS/boards/1888?search=tab&selectedIssue=DSYS-247

Manual testing steps

Feature: Simplified TabsList navigation

  Scenario: user swipes between tabs
    Given TabsList with multiple tabs
    
    When user swipes left
    Then next tab displays
    And transition is smooth
    
    When user swipes right
    Then previous tab displays

  Scenario: user taps tabs directly
    Given TabsList with multiple tabs
    
    When user taps a tab button
    Then that tab becomes active immediately
    And only the active tab content loads

  Scenario: disabled tabs are skipped during swipe
    Given TabsList with some disabled tabs
    
    When user swipes through tabs
    Then disabled tabs are skipped
    And only enabled tabs are accessible

  Scenario: lazy loading of tab content
    Given TabsList with 4 tabs
    And user is on first tab
    
    When user switches to third tab
    Then third tab content loads
    And unused tabs remain unloaded
    And no preloading occurs

Screenshots/Recordings

Before

After

Simulator.Screen.Recording.-.iPhone.15.Pro.Max.-.2025-11-02.at.22.56.05.mp4

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Refactors TabsList to use pan-gesture navigation and InteractionManager-driven lazy loading, rendering only loaded/active tabs and updating tests/snapshots accordingly.

  • Component TabsList:
    • Replace ScrollView paging and scroll handlers with GestureDetector pan gestures; remove scroll refs/timeouts and layout width logic.
    • Add lazy loading via InteractionManager.runAfterInteractions; cache loaded tabs; render only active/loaded content; skip disabled tabs; preserve selection by key on children changes.
    • Keep ref API (goToTabIndex, getCurrentIndex); pass through tabsBarProps and testIDs; simplify layout to a single Box container.
  • Tests & Snapshots:
    • Rewrite tests to mock InteractionManager, verify deferred loading, cancellation, immediate loads for cached tabs, and gesture wrapper presence.
    • Update/refocus tests on tab press behavior, disabled tabs handling, ref navigation, dynamic tabs by key, and edge cases (non-React children, initialActiveIndex).
    • Refresh snapshots to reflect new structure without ScrollView.

Written by Cursor Bugbot for commit 2d17260. This will update automatically on new commits. Configure here.

@brianacnguyen brianacnguyen requested review from a team as code owners October 28, 2025 21:11
@github-actions
Copy link
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@brianacnguyen brianacnguyen marked this pull request as draft October 28, 2025 21:11
cursor[bot]

This comment was marked as outdated.


return contentHeight + emptyStateHeight + padding;
}, [isHomepageRedesignV1Enabled, formattedDeFiPositions?.length]);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These calculations are needed for the FlashList to work with autoheight. After talking with @Prithpal-Sooriya , there will be a follow up to conditionally not use flashlist for when autoheight is needed

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, I am happy with this being a fast follow. I'll do an initial spike to see what work would be required to conditionally not use a FlashList in this specific state.

@brianacnguyen brianacnguyen changed the title Fix/tab individual height feat: Disable scroll from wallet tabs, enable wallet scroll Oct 29, 2025
@brianacnguyen brianacnguyen marked this pull request as ready for review October 29, 2025 23:27
@brianacnguyen brianacnguyen self-assigned this Oct 29, 2025
@brianacnguyen brianacnguyen added the team-design-system All issues relating to design system in Mobile label Oct 29, 2025
const itemWidth = (availableWidth - gapBetweenItems) / 3;
const textHeight = 44;
const rowMarginBottom = 12;
const estimatedRowHeight = itemWidth + textHeight + rowMarginBottom;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These calculations are needed for the FlashList to work with autoheight. After talking with @Prithpal-Sooriya , there will be a follow up to conditionally not use flashlist for when autoheight is needed

if (!isHomepageRedesignV1Enabled) return undefined;
if (claimablePositions.length === 0) return undefined;
return claimablePositions.length * estimatedPositionItemHeight + 48;
}, [isHomepageRedesignV1Enabled, claimablePositions.length]);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These calculations are needed for the FlashList to work with autoheight. After talking with @kevinbluer , there will be a follow up to conditionally not use flashlist for when autoheight is needed


const estimatedTokenItemHeight = 64;

const calculatedListHeight = useMemo(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These calculations are needed for the FlashList to work with autoheight. After talking with @Prithpal-Sooriya , there will be a follow up to conditionally not use flashlist for when autoheight is needed

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request implements a homepage redesign feature (v1) that introduces an auto-height scrolling experience when the feature flag is enabled. The changes modify the main wallet view and various tab components to support dynamic height calculations and conditional scrolling behavior.

Key changes:

  • Adds support for an autoHeight mode in the TabsList component that dynamically adjusts container heights based on content
  • Implements a feature-flagged redesign (isHomepageRedesignV1Enabled) that disables nested scrolling and uses calculated heights for tab content
  • Adds logic to reset the current tab index when the tabs structure changes (e.g., network switching)

Reviewed Changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
app/components/Views/Wallet/index.tsx Wraps wallet content in ScrollView with conditional scrolling based on homepage redesign flag; adds tab reset logic
app/components/Views/TokensFullView/TokensFullView.tsx Replaces includesTopInset prop with style prop and adds testID to back button
app/components/Views/TokensFullView/TokensFullView.test.tsx Simplifies back button test using new testID; removes duplicate and unnecessary tests
app/components/UI/Tokens/index.tsx Implements maxItems limit and calculated height for token list when homepage redesign is enabled
app/components/UI/Tokens/TokenList/index.tsx Moves "View all tokens" button into ListFooterComponent; adjusts container classes for homepage redesign
app/components/UI/Tokens/TokenList/index.test.tsx Updates mocks to use selector functions directly instead of string matching
app/components/UI/Predict/views/PredictTabView/PredictTabView.tsx Conditionally disables scrolling and adjusts flex styling for homepage redesign
app/components/UI/Predict/components/PredictPositions/PredictPositions.tsx Calculates and applies fixed heights for position lists when homepage redesign is enabled
app/components/UI/Perps/components/PerpsLoadingSkeleton/PerpsLoadingSkeleton.tsx Adjusts container styling to use min-height instead of flex-1 for homepage redesign
app/components/UI/Perps/components/PerpsLoadingSkeleton/PerpsLoadingSkeleton.test.tsx Adds mock setup for the homepage redesign feature flag
app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.tsx Disables scrolling and adjusts flex styling based on homepage redesign flag
app/components/UI/NftGrid/NftGrid.tsx Implements maxItems limit (18) and calculated height for NFT grid; removes unused props
app/components/UI/NftGrid/NftGrid.test.tsx Updates tests to reflect new maxItems behavior driven by feature flag
app/components/UI/DeFiPositions/DeFiPositionsList.tsx Adds calculated height wrapper for DeFi positions list with homepage redesign
app/component-library/components-temp/Tabs/TabsList/TabsList.types.ts Adds autoHeight prop to TabsList interface
app/component-library/components-temp/Tabs/TabsList/TabsList.tsx Implements auto-height mode with dynamic height measurement and tab content preloading

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@georgewrmarshall georgewrmarshall left a comment

Choose a reason for hiding this comment

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

Great work getting the dynamic tab heights working! Approving to unblock delivery, but flagging critical technical debt for fast follow up.

The TabsList component is now managing 6+ timeout systems and multiple ref Maps, which raises potential memory management concerns:

  • The cleanup effect runs expensive operations on every tab change
  • Height measurement callbacks may hold onto stale closures after unmount
  • Timers for scroll, load, and measurement aren’t coordinated and should be consolidated
  • The height effect triggers 4+ times per tab switch due to overly broad dependencies
  • A new Map is created on every height change, in a hot render path

The component has also grown in complexity. The height measurement logic alone spans 100+ lines, with tightly coupled effects that will be difficult to maintain or debug safely.

As part of the follow-up work with the @MetaMask/metamask-assets team, I'd recommend exploring ways to simplify the need for this level of complexity. It might also be worth bringing in the Margelo team for a review. They may have ideas on achieving this layout with a lighter implementation.

Copy link
Contributor

@Prithpal-Sooriya Prithpal-Sooriya left a comment

Choose a reason for hiding this comment

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

After a sync with @brianacnguyen, to unblock delivery, we are accepting the complex item size calculations.

We will a have a fast follow to clean this up - discussed in comments:

If possible can we get some QA testing on this before it gets merged, just to alieviate some concerns or any need to revert this?

@Prithpal-Sooriya Prithpal-Sooriya added the needs-qa Any New Features that needs a full manual QA prior to being added to a release. label Oct 30, 2025
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

@Unik0rnMaggie
Copy link
Contributor

Testing on the QA build from here:

  1. iOS: the View all Tokens and View all NFTs buttons are not displayed at the bottom of the assets list(as they are in the After video from the PR)
  2. Android: build failed for Android, so unable to test
wallet.scroll.PR.iOS.mp4

@Unik0rnMaggie
Copy link
Contributor

Testing on the QA build from here , the View all NFTs button is missing on iOS.

Android build failed, so it cannot be tested

wallet.scroll.PR.1.mp4

gambinish
gambinish previously approved these changes Oct 31, 2025
Copy link
Contributor

@gambinish gambinish left a comment

Choose a reason for hiding this comment

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

PerpsTab seems to work fine with these changes. I do notice a behavior where the screen snaps to the view, when the scrolled to tab takes up less vertical space than the previous tab. Unsure if there's anything to do about this?

It's most noticeable when the empty states or "Retry Connection" components are rendered, and you are swiping to that tab from another long list where you have previously scrolled down.

Screen.Recording.2025-10-31.at.12.16.46.PM.mov

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

@brianacnguyen brianacnguyen changed the title feat: Disable scroll from wallet tabs, enable wallet scroll feat: Updated TabsList to no longer use ScrollView, but only rendering active tab Nov 3, 2025
@brianacnguyen brianacnguyen force-pushed the fix/tab-individual-height branch from 885f3b3 to a17d05f Compare November 3, 2025 03:31
cursor[bot]

This comment was marked as outdated.

- Use InteractionManager to defer content rendering until animations finish
- Prevents UI lag during tab transitions
- Already-loaded tabs still render immediately
- Properly cleanup interaction handles on component unmount
- Mock InteractionManager.runAfterInteractions for test determinism
- Update tests to handle deferred content loading behavior
- Replace timer-based tests with InteractionManager-based tests
- Add tests for interaction cancellation on rapid tab switching
- Add test for already-loaded tabs rendering immediately
- Update assertions for disabled tabs to check activeIndex instead of DOM
- Update snapshots to match new deferred loading behavior
@brianacnguyen brianacnguyen force-pushed the fix/tab-individual-height branch from 5be8a02 to 266caf6 Compare November 3, 2025 06:53
@Unik0rnMaggie
Copy link
Contributor

Unik0rnMaggie commented Nov 3, 2025

Testing on the QA build from here:

  1. The DeFi Tab is looking good!
  2. On Perps tab there are these intermittent errors (not sure if they are related to the PR) and sometimes the oder cannot be placed:

Invalid asset...is not a tradable asset
Order failed. your funds have been returned to you
Failed to close position

  1. Sometimes the token list is not loading until clicking on it or switching networks; loads a bit slow also when opening MM
Screenshot 2025-11-03 at 09 39 48 Screenshot 2025-11-03 at 09 40 07 Screenshot 2025-11-03 at 10 19 30
error.token.list.not.loading.completely.mp4
opening.MM.mp4
Perps.failed.to.close.position.mp4

@Matt561 Matt561 self-requested a review November 3, 2025 18:17
Matt561
Matt561 previously approved these changes Nov 3, 2025
Copy link
Contributor

@Matt561 Matt561 left a comment

Choose a reason for hiding this comment

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

Tests well on iOS simulator and Android physical device. Feel free to ping for re-approval once code coverage is fixed! 👍

@brianacnguyen brianacnguyen added the skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. label Nov 3, 2025
@brianacnguyen
Copy link
Contributor Author

Added additional tests but sonar cloud still fails. will skip for now

@brianacnguyen brianacnguyen removed needs-qa Any New Features that needs a full manual QA prior to being added to a release. size-XL labels Nov 3, 2025
@sonarqubecloud
Copy link

sonarqubecloud bot commented Nov 3, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
62.7% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@brianacnguyen brianacnguyen added this pull request to the merge queue Nov 3, 2025
Merged via the queue into main with commit a68cbf9 Nov 3, 2025
87 of 89 checks passed
@brianacnguyen brianacnguyen deleted the fix/tab-individual-height branch November 3, 2025 22:01
@github-actions github-actions bot locked and limited conversation to collaborators Nov 3, 2025
@metamaskbot metamaskbot added the release-7.59.0 Issue or pull request that will be included in release 7.59.0 label Nov 3, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.59.0 Issue or pull request that will be included in release 7.59.0 size-XL skip-sonar-cloud Only used for bypassing sonar cloud when failures are not relevant to the changes. team-design-system All issues relating to design system in Mobile

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants