Skip to content

feat:migrate to standard navigation shared adapter - #581

Open
oleksandrzavarzin-callstack wants to merge 16 commits into
callstack:mainfrom
oleksandrzavarzin-callstack:feat/migrate-to-standard-navigation-shared-adapter
Open

oleksandrzavarzin-callstack wants to merge 16 commits into
callstack:mainfrom
oleksandrzavarzin-callstack:feat/migrate-to-standard-navigation-shared-adapter

Conversation

@oleksandrzavarzin-callstack

@oleksandrzavarzin-callstack oleksandrzavarzin-callstack commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

What

Expo SDK 56 dropped Expo Router's dependency on @react-navigation/* in favour of a vendored fork. @bottom-tabs/react-navigation imports the real thing, so on SDK 56+ two copies of React Navigation load and their contexts stop lining up.

This moves the tab view into a new framework-agnostic package, @bottom-tabs/standard-navigation, built on the standard-navigation contract. One implementation, two adapters:

Expo SDK Package Integration
56+ @bottom-tabs/standard-navigation unstable_createStandardRouterNavigator
52 to 55 @bottom-tabs/react-navigation withLayoutContext

@bottom-tabs/react-navigation's public API is unchanged - same exports, same types, same theme-derived tint defaults. It becomes a thin adapter over the shared view.

Notable

  • Dropped the color dependency for React Native's processColor. color is ESM-only, so every consumer who writes tests had to patch transformIgnorePatterns before Jest would run at all.
  • The peer on @react-navigation/native stays at >=7. An earlier revision raised it to >=7.3.0, which broke npm install outright for SDK 52-55 apps locked below that - and nothing on that path needs 7.3.
  • NativeBottomTabsContent carries its event map and navigator props on phantom type-only properties. Without them an integrator loses every navigator prop at the call site: tabBarActiveTintColor and friends become type errors.
  • unstable_createStandardRouterNavigator is marked unstable by Expo and may change between minors. We deliberately don't call it ourselves, so a break stays in app code.
  • On the Expo Router path a custom tabBar receives { state, descriptors, actions, emitter } rather than a navigation object, so @react-navigation/bottom-tabs' BottomTabBar can't be used there. The React Navigation path is unaffected.

How to test

yarn && yarn build
yarn test        # 96 tests across 5 workspaces
yarn lint && yarn typecheck

apps/expo-router-test runs one assertion suite against both adapters under a real Expo Router tree, with a console spy that fails on any warning or error.

Verified on an app scaffolded from create-expo-app --template default@sdk-57 (Expo 57, React Native 0.86.3, React 19.2.3):

  • iOS 26 and Android 17 - builds and bundles, Liquid Glass and Material 3 tab bars render with tint and badge, tapping routes between screens. No JS warnings or errors.
  • @react-navigation/* is absent from node_modules entirely, so there's no second copy to line up - everything resolves through Expo Router's vendored fork.

The SDK 52-55 path was checked separately: the documented withLayoutContext recipe typechecks clean against a real expo-router@55 install, at both 7.1.33 and 7.3.0.

Screenshots

iOS 26 Android 17
image image

@oleksandrzavarzin-callstack
oleksandrzavarzin-callstack marked this pull request as ready for review September 11, 2026 14:20
Comment thread .changeset/standard-navigation-shared-adapter.md Outdated
Comment thread .changeset/standard-navigation-shared-adapter.md Outdated
Match the naming of @bottom-tabs/react-navigation, and trim the
changeset down to the release-note facts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread docs/docs/docs/getting-started/quick-start.mdx Outdated
Comment thread apps/expo-router-test/src/__tests__/adapters.test.tsx
Comment thread docs/docs/docs/guides/usage-with-expo-router.mdx
Comment thread docs/docs/docs/guides/usage-with-expo-router.mdx Outdated
Comment thread docs/docs/docs/guides/usage-with-expo-router.mdx Outdated
'@bottom-tabs/react-navigation': patch
---

Drop the `color` dependency in favour of React Native's `processColor`. `color` is ESM-only, so consumers had to extend `transformIgnorePatterns` before Jest would run at all

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we need to drop this dependency? I'm concerned we might get regressions

{
"name": "@bottom-tabs/standard-navigation",
"version": "1.4.0",
"description": "Framework-agnostic native bottom tabs navigator built on the standard-navigation contract",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"description": "Framework-agnostic native bottom tabs navigator built on the standard-navigation contract",
"description": "Framework-agnostic native bottom tabs navigator",

Comment on lines +1 to +25
import type { StandardNavigator } from 'standard-navigation';

import { nativeBottomTabsNavigator } from '../index';
import type {
NativeBottomTabNavigationOptions,
NativeBottomTabsEventMap,
NativeBottomTabsNavigatorProps,
} from '../types';

jest.mock('react-native-bottom-tabs', () => ({
__esModule: true,
default: () => null,
}));

const conformsToTheContract: StandardNavigator<
NativeBottomTabNavigationOptions,
NativeBottomTabsEventMap,
NativeBottomTabsNavigatorProps
> = nativeBottomTabsNavigator;

it('declares a navigator matching the published standard-navigation contract', () => {
expect(conformsToTheContract.type).toBe('standard');
expect(conformsToTheContract.version).toBe(1);
expect(typeof conformsToTheContract.NavigatorContent).toBe('function');
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we can remove this whole test

@thiagobrez

Copy link
Copy Markdown
Collaborator

I have two requested changes before approving:

  1. The switch from color to processColor changes the inactive tint for themes with translucent colors. mixColors averages RGB channels without alpha weighting. For example, with transparent text and a white card, the previous Color(colors.text).mix(Color(colors.card), 0.5).hex() returns #FFFFFF, while the new implementation returns #808080. Please roll back the removal of the color dependency and restore the previous mixing behavior to avoid a visible regression. A translucent-color regression test would help protect it.

  2. The new test suites are not run by the PR's CI workflow; it runs lint, typecheck, and builds, but no yarn test. Please add a CI test step or job so these tests gate future changes.

This branch has not been deployed

No deployments
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.

2 participants