Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/khaki-maps-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-native-bottom-tabs': minor
---

fix: adjust the behavior of empty string for badge prop
1 change: 1 addition & 0 deletions apps/example/src/Examples/FourTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default function FourTabs({
key: 'contacts',
focusedIcon: require('../../assets/icons/person_dark.png'),
title: 'Contacts',
badge: ' ',
},
{
key: 'chat',
Expand Down
29 changes: 24 additions & 5 deletions docs/docs/docs/guides/standalone-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ export default function TabViewExample() {
{
key: 'home',
title: 'Home',
focusedIcon: { sfSymbol: 'house' }
focusedIcon: { sfSymbol: 'house' },
},
{
key: 'settings',
title: 'Settings',
focusedIcon: { sfSymbol: 'gear' }
focusedIcon: { sfSymbol: 'gear' },
},
]);

Expand Down Expand Up @@ -91,6 +91,7 @@ const renderScene = SceneMap({
#### `navigationState`

State for the tab view. The state should contain:

- `routes`: Array of route objects containing `key` and `title` props
- `index`: Current selected tab index

Expand All @@ -107,13 +108,15 @@ Callback that is called when the tab index changes.
#### `labeled`

Whether to show labels in tabs. When `false`, only icons will be displayed.

- Type: `boolean`
- Default <Badge text="iOS" type="info" />: `true`
- Default <Badge text="Android" type="info" />: `false`

#### `sidebarAdaptable` <Badge text="iOS" type="info" />

A tab bar style that adapts to each platform:

- iPadOS: Top tab bar that can adapt into a sidebar
- iOS: Bottom tab bar
- macOS/tvOS: Sidebar
Expand All @@ -122,38 +125,43 @@ A tab bar style that adapts to each platform:
#### `disablePageAnimations` <Badge text="iOS" type="info" />

Whether to disable animations between tabs.

- Type: `boolean`

#### `hapticFeedbackEnabled`

Whether to enable haptic feedback on tab press.

- Type: `boolean`
- Default: `false`


#### `tabLabelStyle`

Object containing styles for the tab label.
Supported properties:

- `fontFamily`
- `fontSize`
- `fontWeight`

#### `scrollEdgeAppearance` <Badge text="iOS" type="info" />

Appearance attributes for the tab bar when a scroll view is at the bottom.

- Type: `'default' | 'opaque' | 'transparent'`

#### `minimizeBehavior` <Badge text="iOS 26+" type="info" />

Controls how the tab bar behaves when content is scrolled.

- Type: `'automatic' | 'onScrollDown' | 'onScrollUp' | 'never'`
- Default: `undefined` (uses system default)

Options:

- `automatic`: Platform determines the behavior
- `onScrollDown`: Tab bar minimizes when scrolling down
- `onScrollUp`: Tab bar minimizes when scrolling up
- `onScrollUp`: Tab bar minimizes when scrolling up
- `never`: Tab bar never minimizes

:::note
Expand All @@ -163,6 +171,7 @@ This feature requires iOS 26.0 or later and is only available on iOS. On older v
#### `tabBarActiveTintColor`

Color for the active tab.

- Type: `ColorValue`

#### `tabBarInactiveTintColor`
Expand All @@ -182,11 +191,13 @@ Supported properties:
#### `translucent` <Badge text="iOS" type="info" />

Whether the tab bar is translucent.

- Type: `boolean`

#### `activeIndicatorColor` <Badge text="Android" type="info" />

Color of tab indicator.

- Type: `ColorValue`

### Route Configuration
Expand All @@ -207,21 +218,29 @@ Each route in the `routes` array can have the following properties:
#### `getLazy`

Function to determine if a screen should be lazy loaded.

- Default: Uses `route.lazy`

#### `getLabelText`

Function to get the label text for a tab.

- Default: Uses `route.title`

#### `getBadge`

Function to get the badge text for a tab.

- Default: Uses `route.badge`

:::warning
To display a badge without text (just a dot), you need to pass a string with a space character (`" "`).
:::

#### `getActiveTintColor`

Function to get the active tint color for a tab.

- Default: Uses `route.activeTintColor`

#### `getIcon`
Expand All @@ -230,7 +249,6 @@ Function to get the icon for a tab.

- Default: Uses `route.focusedIcon` and `route.unfocusedIcon`


#### `getHidden`

Function to determine if a tab should be hidden.
Expand All @@ -240,4 +258,5 @@ Function to determine if a tab should be hidden.
#### `getTestID`

Function to get the test ID for a tab item.

- Default: Uses `route.testID`
6 changes: 5 additions & 1 deletion docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ Controls how the tab bar behaves when content is scrolled.
Options:
- `automatic`: Platform determines the behavior
- `onScrollDown`: Tab bar minimizes when scrolling down
- `onScrollUp`: Tab bar minimizes when scrolling up
- `onScrollUp`: Tab bar minimizes when scrolling up
- `never`: Tab bar never minimizes

:::note
Expand Down Expand Up @@ -298,6 +298,10 @@ tabBarIcon: () => require('person.svgx')

Badge to show on the tab icon.

:::warning
To display a badge without text (just a dot), you need to pass a string with a space character (`" "`).
:::

#### `tabBarItemHidden`

Whether the tab bar item is hidden.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) {
}
}

if (item.badge.isNotEmpty()) {
if (item.badge?.isNotEmpty() == true) {
val badge = bottomNavigation.getOrCreateBadge(index)
badge.isVisible = true
badge.text = item.badge
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import com.rcttabview.events.TabLongPressEvent
data class TabInfo(
val key: String,
val title: String,
val badge: String,
val badge: String?,
val activeTintColor: Int?,
val hidden: Boolean,
val testID: String?
Expand All @@ -32,7 +32,7 @@ class RCTTabViewImpl {
TabInfo(
key = item.getString("key") ?: "",
title = item.getString("title") ?: "",
badge = item.getString("badge") ?: "",
badge = if (item.hasKey("badge")) item.getString("badge") else null,
activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null,
hidden = if (item.hasKey("hidden")) item.getBoolean("hidden") else false,
testID = item.getString("testID")
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-bottom-tabs/ios/TabViewImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
attributes[.font] = RCTFont.update(
nil,
withFamily: family,
size: NSNumber(value: size),

Check warning on line 159 in packages/react-native-bottom-tabs/ios/TabViewImpl.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
weight: weight,
style: nil,
variant: nil,
Expand Down Expand Up @@ -272,7 +272,7 @@
@ViewBuilder
func tabBadge(_ data: String?) -> some View {
if #available(iOS 15.0, macOS 15.0, visionOS 2.0, tvOS 15.0, *) {
if let data, !data.isEmpty {
if let data {
#if !os(tvOS)
self.badge(data)
#else
Expand Down
6 changes: 3 additions & 3 deletions packages/react-native-bottom-tabs/ios/TabViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import SwiftUI
public final class TabInfo: NSObject {
public let key: String
public let title: String
public let badge: String
public let badge: String?
public let sfSymbol: String
public let activeTintColor: PlatformColor?
public let hidden: Bool
Expand All @@ -17,7 +17,7 @@ public final class TabInfo: NSObject {
public init(
key: String,
title: String,
badge: String,
badge: String?,
sfSymbol: String,
activeTintColor: PlatformColor?,
hidden: Bool,
Expand Down Expand Up @@ -269,7 +269,7 @@ public final class TabInfo: NSObject {
TabInfo(
key: itemDict["key"] as? String ?? "",
title: itemDict["title"] as? String ?? "",
badge: itemDict["badge"] as? String ?? "",
badge: itemDict["badge"] as? String,
sfSymbol: itemDict["sfSymbol"] as? String ?? "",
activeTintColor: RCTConvert.uiColor(itemDict["activeTintColor"] as? NSNumber),
hidden: itemDict["hidden"] as? Bool ?? false,
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-bottom-tabs/src/TabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@
renderScene,
onIndexChange,
onTabLongPress,
getBadge,
rippleColor,
tabBarActiveTintColor: activeTintColor,
tabBarInactiveTintColor: inactiveTintColor,
getBadge = ({ route }: { route: Route }) => route.badge,
getLazy = ({ route }: { route: Route }) => route.lazy,
getLabelText = ({ route }: { route: Route }) => route.title,
getIcon = ({ route, focused }: { route: Route; focused: boolean }) =>
Expand Down Expand Up @@ -227,7 +227,7 @@

if (!loaded.includes(focusedKey)) {
// Set the current tab to be loaded if it was not loaded before
setLoaded((loaded) => [...loaded, focusedKey]);

Check warning on line 230 in packages/react-native-bottom-tabs/src/TabView.tsx

View workflow job for this annotation

GitHub Actions / lint

'loaded' is already declared in the upper scope on line 226 column 10
}

const icons = React.useMemo(
Expand Down
Loading