Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions apps/onestack.dev/data/docs/components-NativeTabs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
title: "<NativeTabs />"
---

The NativeTabs component provides a native platform tab bar using [`react-native-bottom-tabs`](https://github.com/okwasniewski/react-native-bottom-tabs). Unlike the regular [Tabs](/docs/components-Tabs) component which uses a JavaScript-based tab bar, NativeTabs renders using the platform's native tab bar (`UITabBarController` on iOS, Material Bottom Tabs on Android), giving you native feel and SF Symbol support on iOS.

Import it from `one/native-tabs` to keep the bundle size minimal when not using native tabs.

This component should only be rendered inside a `_layout.tsx` file, where it will serve as the location that children will render for routes below the layout.

NativeTabs wraps the navigator from [`@bottom-tabs/react-navigation`](https://github.com/react-navigation/react-navigation) and accepts the same props.

## Basic Usage

```tsx fileName="_layout.native.tsx"
import { NativeTabs } from 'one/native-tabs'

export default function Layout() {
return (
<NativeTabs>
<NativeTabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: () => ({ sfSymbol: 'house' }),
}}
/>
<NativeTabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: () => ({ sfSymbol: 'person' }),
}}
/>
</NativeTabs>
)
}
```

## Tab Icons

On iOS, you can use [SF Symbols](https://developer.apple.com/sf-symbols/) by returning an object with `sfSymbol`:

```tsx
tabBarIcon: () => ({ sfSymbol: 'house' })
```

On Android, SF Symbols are not available. You can use imported SVG or image assets instead:

```tsx fileName="_layout.native.tsx"
import { Platform } from 'react-native'
import { NativeTabs } from 'one/native-tabs'
import homeIcon from '../assets/icons/home.svg'

export default function Layout() {
return (
<NativeTabs>
<NativeTabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: () =>
Platform.OS === 'ios' ? { sfSymbol: 'house' } : homeIcon,
}}
/>
</NativeTabs>
)
}
```

<Notice>
Use `import` for assets rather than `require()`.
</Notice>

## Platform-Specific Layouts

Since NativeTabs is native-only, you'll typically pair it with a web layout using [platform-specific file extensions](/docs/routing):

```
app/
├── _layout.tsx # Web layout (Tabs or custom)
├── _layout.native.tsx # Native layout (NativeTabs)
├── index.tsx
└── profile.tsx
```

## Common Options

| Option | Type | Description |
|--------|------|-------------|
| `title` | `string` | Tab label text |
| `tabBarIcon` | `() => ImageSource \| { sfSymbol: string }` | Tab icon (SF Symbol on iOS, image asset on Android) |
| `tabBarBadge` | `string` | Badge text on the tab |
| `tabBarActiveTintColor` | `string` | Active tab tint color |

## Dependencies

NativeTabs requires these optional peer dependencies to be installed:

```bash
npm install @bottom-tabs/react-navigation react-native-bottom-tabs
```

These are marked as optional peer dependencies in `one`, so they won't be installed automatically. This keeps bundle sizes smaller for apps that don't use NativeTabs.

For more configuration options, see the [`react-native-bottom-tabs` documentation](https://github.com/okwasniewski/react-native-bottom-tabs).
8 changes: 3 additions & 5 deletions apps/onestack.dev/data/docs/exports-withLayoutContext.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ title: withLayoutContext
description: Create your own layouts.
---

Today there are a few built-in navigators: [Slot](/docs/components-Slot), [Stack](/docs/components-Stack), etc. But you may want to create your own.
Today there are a few built-in navigators: [Slot](/docs/components-Slot), [Stack](/docs/components-Stack), [Tabs](/docs/components-Tabs), [NativeTabs](/docs/components-NativeTabs), and [Drawer](/docs/components-Drawer). But you may want to create your own.

A good example of this is the new [`react-native-bottom-tabs`](https://github.com/okwasniewski/react-native-bottom-tabs) library, that exports a React Navigation compatible navigator.

To make any React Navigation navigator work inside a One layout, use `withLayoutContext`:
To make any React Navigation navigator work inside a One layout, use `withLayoutContext`. For example, this is how [NativeTabs](/docs/components-NativeTabs) is built:

```tsx
import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation'
Expand All @@ -20,4 +18,4 @@ export const NativeTabs = withLayoutContext(
)
```

Now you can use `NativeTabs` in any `_layout.tsx` file.
Now you can use `NativeTabs` in any `_layout.tsx` file. This same pattern works with any React Navigation compatible navigator.
1 change: 1 addition & 0 deletions apps/onestack.dev/data/docs/routing-layouts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Layouts must render one of the following to show the matched pages that exist in
- [Slot](/docs/components-Slot): Directly show sub-route with no frame.
- [Stack](/docs/components-Stack): Creates a [React Navigation Stack](https://reactnavigation.org/docs/stack-navigator/) of sub-routes.
- [Tabs](/docs/components-Tabs): Creates a [React Navigation BottomTabs](https://reactnavigation.org/docs/bottom-tab-navigator) with sub-routes.
- [NativeTabs](/docs/components-NativeTabs): Creates a native platform tab bar using [react-native-bottom-tabs](https://github.com/okwasniewski/react-native-bottom-tabs) with sub-routes.
- [Drawer](/docs/components-Drawer): Creates a [React Navigation Drawer](https://reactnavigation.org/docs/drawer-navigator) with sub-routes.

This looks something like this at the simplest:
Expand Down
1 change: 1 addition & 0 deletions apps/onestack.dev/features/docs/docsRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const docsRoutes = [
{ title: 'Slot', route: '/docs/components-Slot' },
{ title: 'Stack', route: '/docs/components-Stack' },
{ title: 'Tabs', route: '/docs/components-Tabs' },
{ title: 'NativeTabs', route: '/docs/components-NativeTabs' },
{ title: 'Drawer', route: '/docs/components-Drawer' },
{ title: 'Protected', route: '/docs/components-Protected' },
{ title: 'withLayoutContext', route: '/docs/exports-withLayoutContext' },
Expand Down
7 changes: 6 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/testflight/assets/icons/bell.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/testflight/assets/icons/newspaper.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/testflight/assets/icons/user.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 12 additions & 3 deletions examples/testflight/code/home/HomeLayout.native.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Platform } from 'react-native'
import { NativeTabs } from '~/code/ui/BottomTabs.native'
import newspaperIcon from '../../assets/icons/newspaper.svg'
import bellIcon from '../../assets/icons/bell.svg'
import userIcon from '../../assets/icons/user.svg'

const isIOS = Platform.OS === 'ios'

export function HomeLayout() {
return (
Expand All @@ -7,23 +13,26 @@ export function HomeLayout() {
name="index"
options={{
title: 'Feed',
tabBarIcon: () => ({ sfSymbol: 'newspaper' }),
tabBarIcon: () =>
isIOS ? { sfSymbol: 'newspaper' } : newspaperIcon,
}}
/>

<NativeTabs.Screen
name="notifications"
options={{
title: 'Notifications',
tabBarIcon: () => ({ sfSymbol: 'bell' }),
tabBarIcon: () =>
isIOS ? { sfSymbol: 'bell' } : bellIcon,
}}
/>

<NativeTabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: () => ({ sfSymbol: 'person' }),
tabBarIcon: () =>
isIOS ? { sfSymbol: 'person' } : userIcon,
}}
/>
</NativeTabs>
Expand Down
5 changes: 1 addition & 4 deletions examples/testflight/code/ui/BottomTabs.native.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation'
import { withLayoutContext } from 'one'

export const NativeTabs = withLayoutContext(createNativeBottomTabNavigator().Navigator)
export { NativeTabs } from 'one/native-tabs'
19 changes: 19 additions & 0 deletions packages/one/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@
"import": "./dist/esm/drawer.mjs",
"require": "./dist/cjs/drawer.js"
},
"./native-tabs": {
"react-native": {
"import": "./dist/esm/native-tabs.native.js",
"require": "./dist/cjs/native-tabs.native.js"
},
"types": "./types/native-tabs.d.ts",
"import": "./dist/esm/native-tabs.mjs",
"require": "./dist/cjs/native-tabs.js"
},
"./devtools/*": "./devtools/*"
},
"main": "dist/cjs",
Expand Down Expand Up @@ -201,9 +210,17 @@
"react-native-reanimated": "~4.1.3",
"react-native-safe-area-context": "~5.6.1",
"react-native-screens": "~4.16.0",
"@bottom-tabs/react-navigation": ">=0.9.0",
"react-native-bottom-tabs": ">=1.0.0",
"sharp": ">=0.33.0"
},
"peerDependenciesMeta": {
"@bottom-tabs/react-navigation": {
"optional": true
},
"react-native-bottom-tabs": {
"optional": true
},
"@react-navigation/drawer": {
"optional": true
},
Expand All @@ -221,6 +238,8 @@
}
},
"devDependencies": {
"@bottom-tabs/react-navigation": "^0.9.1",
"react-native-bottom-tabs": "^1.0.2",
"@react-navigation/core": "^7.13.0",
"@react-navigation/drawer": "~7.7.2",
"@react-navigation/native": "~7.1.19",
Expand Down
24 changes: 24 additions & 0 deletions packages/one/src/layouts/NativeTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
createNativeBottomTabNavigator,
type NativeBottomTabNavigationEventMap,
type NativeBottomTabNavigationOptions,
} from '@bottom-tabs/react-navigation'
import type { ParamListBase, TabNavigationState } from '@react-navigation/native'
import type React from 'react'

import { withLayoutContext } from './withLayoutContext'

// typed as ComponentType<any> because @bottom-tabs/react-navigation doesn't export
// NativeBottomTabNavigatorProps, causing TS2742 on build. the actual typing comes
// from the withLayoutContext generics below, not from the Navigator's own props.
const NativeBottomTabNavigator: React.ComponentType<any> =
createNativeBottomTabNavigator().Navigator

export const NativeTabs = withLayoutContext<
NativeBottomTabNavigationOptions,
typeof NativeBottomTabNavigator,
TabNavigationState<ParamListBase>,
NativeBottomTabNavigationEventMap
>(NativeBottomTabNavigator)

export default NativeTabs
1 change: 1 addition & 0 deletions packages/one/src/native-tabs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NativeTabs, NativeTabs as default } from './layouts/NativeTabs'
6 changes: 6 additions & 0 deletions packages/one/types/layouts/NativeTabs.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type React from 'react';
export declare const NativeTabs: React.ForwardRefExoticComponent<Omit<import("../types").PickPartial<any, "children">, "ref"> & React.RefAttributes<unknown>> & {
Screen: typeof import("../views/Screen").Screen;
};
export default NativeTabs;
//# sourceMappingURL=NativeTabs.d.ts.map
2 changes: 2 additions & 0 deletions packages/one/types/native-tabs.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { NativeTabs, NativeTabs as default } from './layouts/NativeTabs';
//# sourceMappingURL=native-tabs.d.ts.map