Skip to content

Adding Active Inactive Tint Color #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
36 changes: 35 additions & 1 deletion android/src/main/java/com/rcttabview/RCTTabView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
var items: MutableList<TabInfo>? = null
var onTabSelectedListener: ((WritableMap) -> Unit)? = null
private var isAnimating = false
private var activeTintColor: Int? = null
private var inactiveTintColor: Int? = null
private val checkedStateSet = intArrayOf(android.R.attr.state_checked)
private val uncheckedStateSet = intArrayOf(-android.R.attr.state_checked)

private val layoutCallback = Choreographer.FrameCallback {
isLayoutEnqueued = false
Expand All @@ -43,6 +47,7 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
init {
setOnItemSelectedListener { item ->
onTabSelected(item)
updateTintColors(item)
true
}
}
Expand Down Expand Up @@ -160,12 +165,41 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
itemBackground = colorDrawable
}

fun setActiveTintColor(color: Int?) {
activeTintColor = color
updateTintColors()
}

fun setInactiveTintColor(color: Int?) {
inactiveTintColor = color
updateTintColors()
}

private fun updateTintColors(item: MenuItem? = null) {
// First let's check current item color.
val currentItemTintColor = items?.find { it.title == item?.title }?.activeTintColor

// getDeaultColor will always return a valid color but to satisfy the compiler we need to check for null
val colorPrimary = currentItemTintColor ?: activeTintColor ?: getDefaultColorFor(android.R.attr.colorPrimary) ?: return
val colorSecondary =
inactiveTintColor ?: getDefaultColorFor(android.R.attr.textColorSecondary) ?: return
val states = arrayOf(uncheckedStateSet, checkedStateSet)
val colors = intArrayOf(colorSecondary, colorPrimary)

ColorStateList(states, colors).apply {
[email protected] = this
[email protected] = this
}
}

private fun getDefaultColorFor(baseColorThemeAttr: Int): Int? {
val value = TypedValue()
if (!context.theme.resolveAttribute(baseColorThemeAttr, value, true)) {
return null
}
val baseColor = AppCompatResources.getColorStateList(context, value.resourceId)
val baseColor = AppCompatResources.getColorStateList(
context, value.resourceId
)
return baseColor.defaultColor
}
}
24 changes: 17 additions & 7 deletions android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import com.facebook.yoga.YogaNode
data class TabInfo(
val key: String,
val title: String,
val badge: String
val badge: String,
val activeTintColor: Int?
)

@ReactModule(name = RCTTabViewViewManager.NAME)
Expand All @@ -42,7 +43,8 @@ class RCTTabViewViewManager :
TabInfo(
key = item.getString("key") ?: "",
title = item.getString("title") ?: "",
badge = item.getString("badge") ?: ""
badge = item.getString("badge") ?: "",
activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null
)
)
}
Expand All @@ -57,7 +59,6 @@ class RCTTabViewViewManager :
}
}


@ReactProp(name = "labeled")
fun setLabeled(view: ReactBottomNavigationView, flag: Boolean?) {
view.setLabeled(flag)
Expand All @@ -72,7 +73,7 @@ class RCTTabViewViewManager :
fun setBarTintColor(view: ReactBottomNavigationView, color: Int?) {
view.setBarTintColor(color)
}

@ReactProp(name = "rippleColor")
fun setRippleColor(view: ReactBottomNavigationView, rippleColor: Int?) {
if (rippleColor != null) {
Expand All @@ -81,8 +82,14 @@ class RCTTabViewViewManager :
}
}

@ReactProp(name = "translucent")
fun setTranslucentview(view: ReactBottomNavigationView, translucent: Boolean?) {
@ReactProp(name = "activeTintColor")
fun setActiveTintColor(view: ReactBottomNavigationView, color: Int?) {
view.setActiveTintColor(color)
}

@ReactProp(name = "inactiveTintColor")
fun setInactiveTintColor(view: ReactBottomNavigationView, color: Int?) {
view.setInactiveTintColor(color)
}

public override fun createViewInstance(context: ThemedReactContext): ReactBottomNavigationView {
Expand All @@ -96,7 +103,6 @@ class RCTTabViewViewManager :
return view
}


class TabViewShadowNode() : LayoutShadowNode(),
YogaMeasureFunction {
private var mWidth = 0
Expand Down Expand Up @@ -161,4 +167,8 @@ class RCTTabViewViewManager :
@ReactProp(name = "disablePageAnimations")
fun setDisablePageAnimations(view: ReactBottomNavigationView, flag: Boolean) {
}

@ReactProp(name = "translucent")
fun setTranslucentview(view: ReactBottomNavigationView, translucent: Boolean?) {
}
}
16 changes: 16 additions & 0 deletions docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ Whether to disable page animations between tabs.

Describes the appearance attributes for the tabBar to use when an observable scroll view is scrolled to the bottom.

### `tabBarActiveTintColor`

Color for the active tab.

### `tabBarInactiveTintColor`

Color for the inactive tabs.

#### `barTintColor`

Background color of the tab bar.
Expand Down Expand Up @@ -106,6 +114,14 @@ Title text for the screen.

Label text of the tab displayed in the navigation bar. When undefined, scene title is used.

#### `tabBarActiveTintColor`

Color for the active tab.

:::note
The `tabBarInactiveTintColor` is not supported on route level due to native limitations. Use `inactiveTintColor` in the `Tab.Navigator` instead.
:::

#### `tabBarIcon`

Function that given `{ focused: boolean }` returns `ImageSource` or `AppleIcon` to display in the navigation bar.
Expand Down
15 changes: 13 additions & 2 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@
TouchableOpacity,
Button,
Alert,
useColorScheme,
} from 'react-native';
import { NavigationContainer, useNavigation } from '@react-navigation/native';
import {
DarkTheme,
DefaultTheme,
NavigationContainer,
useNavigation,
} from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider } from 'react-native-safe-area-context';
Expand All @@ -22,6 +28,7 @@
import SFSymbols from './Examples/SFSymbols';
import LabeledTabs from './Examples/Labeled';
import NativeBottomTabs from './Examples/NativeBottomTabs';
import TintColorsExample from './Examples/TintColors';

const FourTabsIgnoreSafeArea = () => {
return <FourTabs ignoresTopSafeArea />;
Expand Down Expand Up @@ -82,6 +89,7 @@
screenOptions: { headerShown: false },
},
{ component: MaterialBottomTabs, name: 'Material (JS) Bottom Tabs' },
{ component: TintColorsExample, name: 'Tint Colors' },
];

function App() {
Expand Down Expand Up @@ -112,15 +120,18 @@
export default function Navigation() {
const [mode, setMode] = React.useState<'native' | 'js'>('native');
const NavigationStack = mode === 'js' ? Stack : NativeStack;
const colorScheme = useColorScheme();
const theme = colorScheme === 'dark' ? DarkTheme : DefaultTheme;

return (
<SafeAreaProvider>
<NavigationContainer>
<NavigationContainer theme={theme}>
<NavigationStack.Navigator initialRouteName="BottomTabs Example">
<NavigationStack.Screen
name="BottomTabs Example"
component={App}
options={{
headerRight: () => (

Check warning on line 134 in example/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “Navigation” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
<Button
onPress={() =>
Alert.alert(
Expand Down
4 changes: 3 additions & 1 deletion example/src/Examples/NativeBottomTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const Tab = createNativeBottomTabNavigator();

function NativeBottomTabs() {
return (
<Tab.Navigator>
<Tab.Navigator tabBarInactiveTintColor="red" tabBarActiveTintColor="orange">
<Tab.Screen
name="Article"
component={Article}
Expand All @@ -33,13 +33,15 @@ function NativeBottomTabs() {
component={Contacts}
options={{
tabBarIcon: () => require('../../assets/icons/person_dark.png'),
tabBarActiveTintColor: 'yellow',
}}
/>
<Tab.Screen
name="Chat"
component={Chat}
options={{
tabBarIcon: () => require('../../assets/icons/chat_dark.png'),
tabBarActiveTintColor: 'purple',
}}
/>
</Tab.Navigator>
Expand Down
56 changes: 56 additions & 0 deletions example/src/Examples/TintColors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import TabView, { SceneMap } from 'react-native-bottom-tabs';
import { useState } from 'react';
import { Article } from '../Screens/Article';
import { Albums } from '../Screens/Albums';
import { Contacts } from '../Screens/Contacts';
import { Chat } from '../Screens/Chat';

export default function TintColorsExample() {
const [index, setIndex] = useState(0);
const [routes] = useState([
{
key: 'article',
title: 'Article',
focusedIcon: require('../../assets/icons/article_dark.png'),
unfocusedIcon: require('../../assets/icons/chat_dark.png'),
badge: '!',
},
{
key: 'albums',
title: 'Albums',
focusedIcon: require('../../assets/icons/grid_dark.png'),
badge: '5',
activeTintColor: 'green',
},
{
key: 'contacts',
focusedIcon: require('../../assets/icons/person_dark.png'),
title: 'Contacts',
activeTintColor: 'yellow',
},
{
key: 'chat',
focusedIcon: require('../../assets/icons/chat_dark.png'),
title: 'Chat',
},
]);

const renderScene = SceneMap({
article: Article,
albums: Albums,
contacts: Contacts,
chat: Chat,
});

return (
<TabView
sidebarAdaptable
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
tabBarActiveTintColor="red"
tabBarInactiveTintColor="orange"
scrollEdgeAppearance="default"
/>
);
}
9 changes: 9 additions & 0 deletions ios/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ extension Collection {
}
}


extension Collection where Element == TabInfo {
func findByKey(_ key: String?) -> Element? {
guard let key else { return nil }
guard !isEmpty else { return nil }
return first(where: { $0.key == key })
}
}

extension UIView {
func pinEdges(to other: UIView) {
NSLayoutConstraint.activate([
Expand Down
2 changes: 2 additions & 0 deletions ios/RCTTabViewViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ - (UIView *)view
RCT_EXPORT_VIEW_PROPERTY(scrollEdgeAppearance, NSString)
RCT_EXPORT_VIEW_PROPERTY(barTintColor, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
RCT_EXPORT_VIEW_PROPERTY(activeTintColor, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(inactiveTintColor, NSNumber)

@end
Loading
Loading