Skip to content

Commit 3793cbb

Browse files
feat: Add badge color support for Android (#473)
* feat: Add badge color support for Android - Add badgeBackgroundColor and badgeTextColor props to TabView - Implement badge styling in Android native code using Material BadgeDrawable API - Add support in @bottom-tabs/react-navigation wrapper - Colors are processed using processColor for proper color conversion - Properly reset colors to theme defaults when props are removed (fixes stale color bug) - Android only feature (iOS uses system default badge styling) * feat: Updated the documentation for the badge text and background colors
1 parent 13717eb commit 3793cbb

File tree

7 files changed

+78
-0
lines changed

7 files changed

+78
-0
lines changed

docs/docs/docs/guides/usage-with-react-navigation.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,20 @@ Badge to show on the tab icon.
309309
To display a badge without text (just a dot), you need to pass a string with a space character (`" "`).
310310
:::
311311

312+
#### `tabBarBadgeBackgroundColor`
313+
314+
- Type: `string`
315+
316+
Set the background color for the badge on android.
317+
Uses the system color by default.
318+
319+
#### `tabBarBadgeTextColor`
320+
321+
- Type: `string`
322+
323+
Set the text color for the badge on android.
324+
Uses the system color by default.
325+
312326
#### `tabBarItemHidden`
313327

314328
Whether the tab bar item is hidden.

packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,31 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) {
256256
if (item.badge != " ") {
257257
badge.text = item.badge
258258
}
259+
// Apply badge colors if provided, or reset to theme defaults if null
260+
if (item.badgeBackgroundColor != null) {
261+
badge.backgroundColor = item.badgeBackgroundColor
262+
} else {
263+
// Reset to theme default color by resolving the colorError attribute
264+
val typedValue = TypedValue()
265+
context.theme.resolveAttribute(
266+
com.google.android.material.R.attr.colorError,
267+
typedValue,
268+
true
269+
)
270+
badge.backgroundColor = typedValue.data
271+
}
272+
if (item.badgeTextColor != null) {
273+
badge.badgeTextColor = item.badgeTextColor
274+
} else {
275+
// Reset to theme default text color by resolving the colorOnError attribute
276+
val typedValue = TypedValue()
277+
context.theme.resolveAttribute(
278+
com.google.android.material.R.attr.colorOnError,
279+
typedValue,
280+
true
281+
)
282+
badge.badgeTextColor = typedValue.data
283+
}
259284
} else {
260285
bottomNavigation.removeBadge(index)
261286
}

packages/react-native-bottom-tabs/src/TabView.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ interface Props<Route extends BaseRoute> {
107107
* Get badge for the tab, uses `route.badge` by default.
108108
*/
109109
getBadge?: (props: { route: Route }) => string | undefined;
110+
/**
111+
* Get badge background color for the tab, uses `route.badgeBackgroundColor` by default. (Android only)
112+
*/
113+
getBadgeBackgroundColor?: (props: { route: Route }) => ColorValue | undefined;
114+
/**
115+
* Get badge text color for the tab, uses `route.badgeTextColor` by default. (Android only)
116+
*/
117+
getBadgeTextColor?: (props: { route: Route }) => ColorValue | undefined;
110118
/**
111119
* Get active tint color for the tab, uses `route.activeTintColor` by default.
112120
*/
@@ -206,6 +214,9 @@ const TabView = <Route extends BaseRoute>({
206214
tabBarActiveTintColor: activeTintColor,
207215
tabBarInactiveTintColor: inactiveTintColor,
208216
getBadge = ({ route }: { route: Route }) => route.badge,
217+
getBadgeBackgroundColor = ({ route }: { route: Route }) =>
218+
route.badgeBackgroundColor,
219+
getBadgeTextColor = ({ route }: { route: Route }) => route.badgeTextColor,
209220
getLazy = ({ route }: { route: Route }) => route.lazy,
210221
getLabelText = ({ route }: { route: Route }) => route.title,
211222
getIcon = ({ route, focused }: { route: Route; focused: boolean }) =>
@@ -289,6 +300,10 @@ const TabView = <Route extends BaseRoute>({
289300
title: getLabelText({ route }) ?? route.key,
290301
sfSymbol: isSfSymbol ? icon.sfSymbol : undefined,
291302
badge: getBadge?.({ route }),
303+
badgeBackgroundColor: processColor(
304+
getBadgeBackgroundColor?.({ route })
305+
),
306+
badgeTextColor: processColor(getBadgeTextColor?.({ route })),
292307
activeTintColor: processColor(getActiveTintColor({ route })),
293308
hidden: getHidden?.({ route }),
294309
testID: getTestID?.({ route }),
@@ -301,6 +316,8 @@ const TabView = <Route extends BaseRoute>({
301316
icons,
302317
getLabelText,
303318
getBadge,
319+
getBadgeBackgroundColor,
320+
getBadgeTextColor,
304321
getActiveTintColor,
305322
getHidden,
306323
getTestID,

packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export type TabViewItems = ReadonlyArray<{
2727
title: string;
2828
sfSymbol?: string;
2929
badge?: string;
30+
badgeBackgroundColor?: ProcessedColorValue | null;
31+
badgeTextColor?: ProcessedColorValue | null;
3032
activeTintColor?: ProcessedColorValue | null;
3133
hidden?: boolean;
3234
testID?: string;

packages/react-native-bottom-tabs/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export type BaseRoute = {
1111
key: string;
1212
title?: string;
1313
badge?: string;
14+
badgeBackgroundColor?: string;
15+
badgeTextColor?: string;
1416
lazy?: boolean;
1517
focusedIcon?: ImageSourcePropType | AppleIcon;
1618
unfocusedIcon?: ImageSourcePropType | AppleIcon;

packages/react-navigation/src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ export type NativeBottomTabNavigationOptions = {
7777
*/
7878
tabBarBadge?: string;
7979

80+
/**
81+
* Badge background color. (Android only)
82+
*/
83+
tabBarBadgeBackgroundColor?: string;
84+
85+
/**
86+
* Badge text color. (Android only)
87+
*/
88+
tabBarBadgeTextColor?: string;
89+
8090
/**
8191
* Whether this screens should render the first time it's accessed. Defaults to true. Set it to false if you want to render the screen on initial render.
8292
*/
@@ -142,6 +152,8 @@ export type NativeBottomTabNavigationConfig = Partial<
142152
| 'getIcon'
143153
| 'getLabelText'
144154
| 'getBadge'
155+
| 'getBadgeBackgroundColor'
156+
| 'getBadgeTextColor'
145157
| 'onTabLongPress'
146158
| 'getActiveTintColor'
147159
| 'getTestID'

packages/react-navigation/src/views/NativeBottomTabView.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ export default function NativeBottomTabView({
4242
: (route as Route<string>).name;
4343
}}
4444
getBadge={({ route }) => descriptors[route.key]?.options.tabBarBadge}
45+
getBadgeBackgroundColor={({ route }) =>
46+
descriptors[route.key]?.options.tabBarBadgeBackgroundColor
47+
}
48+
getBadgeTextColor={({ route }) =>
49+
descriptors[route.key]?.options.tabBarBadgeTextColor
50+
}
4551
getHidden={({ route }) => {
4652
const options = descriptors[route.key]?.options;
4753
return options?.tabBarItemHidden === true;

0 commit comments

Comments
 (0)