Skip to content

Commit 96eda94

Browse files
authored
feat: support hiding tabs (#111)
1 parent 63fa844 commit 96eda94

File tree

15 files changed

+134
-52
lines changed

15 files changed

+134
-52
lines changed

android/src/main/java/com/rcttabview/RCTTabView.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
9292
this.items = items
9393
items.forEachIndexed { index, item ->
9494
val menuItem = getOrCreateItem(index, item.title)
95+
menuItem.isVisible = !item.hidden
9596
if (icons.containsKey(index)) {
9697
menuItem.icon = getDrawable(icons[index]!!)
9798
}

android/src/main/java/com/rcttabview/RCTTabViewImpl.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ data class TabInfo(
1212
val key: String,
1313
val title: String,
1414
val badge: String,
15-
val activeTintColor: Int?
15+
val activeTintColor: Int?,
16+
val hidden: Boolean,
1617
)
1718

1819
class RCTTabViewImpl {
@@ -29,7 +30,8 @@ class RCTTabViewImpl {
2930
key = item.getString("key") ?: "",
3031
title = item.getString("title") ?: "",
3132
badge = item.getString("badge") ?: "",
32-
activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null
33+
activeTintColor = if (item.hasKey("activeTintColor")) item.getInt("activeTintColor") else null,
34+
hidden = if (item.hasKey("hidden")) item.getBoolean("hidden") else false
3335
)
3436
)
3537
}

docs/docs/docs/guides/standalone-usage.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,12 @@ Function to get the active tint color for a tab.
200200
#### `getIcon`
201201

202202
Function to get the icon for a tab.
203+
203204
- Default: Uses `route.focusedIcon` and `route.unfocusedIcon`
205+
206+
207+
#### `getHidden`
208+
209+
Function to determine if a tab should be hidden.
210+
211+
- Default: Uses `route.hidden`

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,16 @@ SF Symbols are only supported on Apple platforms.
193193

194194
Badge to show on the tab icon.
195195

196+
#### `tabBarItemHidden`
197+
198+
Whether the tab bar item is hidden.
199+
200+
:::warning
201+
202+
Due to native limitations on iOS, this option doesn't hide the tab item **when hidden route is focused**.
203+
204+
:::
205+
196206
#### `lazy`
197207

198208
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.

example/src/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ const FourTabsIgnoreSafeArea = () => {
3737
return <FourTabs ignoresTopSafeArea />;
3838
};
3939

40+
const HiddenTab = () => {
41+
return <FourTabs hideOneTab />;
42+
};
43+
4044
const FourTabsRippleColor = () => {
4145
return <FourTabs rippleColor={'#00ff00'} />;
4246
};
@@ -114,6 +118,10 @@ const examples = [
114118
component: FourTabsActiveIndicatorColor,
115119
name: 'Four Tabs - Active Indicator color',
116120
},
121+
{
122+
component: HiddenTab,
123+
name: 'Four Tabs - With Hidden Tab',
124+
},
117125
{
118126
component: NativeBottomTabsVectorIcons,
119127
name: 'Native Bottom Tabs with Vector Icons',

example/src/Examples/FourTabs.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface Props {
1212
scrollEdgeAppearance?: 'default' | 'opaque' | 'transparent';
1313
barTintColor?: ColorValue;
1414
translucent?: boolean;
15+
hideOneTab?: boolean;
1516
rippleColor?: ColorValue;
1617
activeIndicatorColor?: ColorValue;
1718
}
@@ -22,6 +23,7 @@ export default function FourTabs({
2223
scrollEdgeAppearance = 'default',
2324
barTintColor,
2425
translucent = true,
26+
hideOneTab = false,
2527
rippleColor,
2628
activeIndicatorColor,
2729
}: Props) {
@@ -39,6 +41,7 @@ export default function FourTabs({
3941
title: 'Albums',
4042
focusedIcon: require('../../assets/icons/grid_dark.png'),
4143
badge: '5',
44+
hidden: hideOneTab,
4245
},
4346
{
4447
key: 'contacts',

example/src/Examples/NativeBottomTabsVectorIcons.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ function NativeBottomTabsVectorIcons() {
6969
options={{
7070
tabBarIcon: () => messageIcon,
7171
tabBarActiveTintColor: 'white',
72+
tabBarItemHidden: true,
7273
}}
7374
/>
7475
</Tab.Navigator>

ios/Fabric/RCTTabViewComponentView.mm

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ bool areTabItemsEqual(const RNCTabViewItemsStruct& lhs, const RNCTabViewItemsStr
155155
lhs.title == rhs.title &&
156156
lhs.sfSymbol == rhs.sfSymbol &&
157157
lhs.badge == rhs.badge &&
158-
lhs.activeTintColor == rhs.activeTintColor;
158+
lhs.activeTintColor == rhs.activeTintColor &&
159+
lhs.hidden == rhs.hidden;
159160
}
160161

161162
bool haveTabItemsChanged(const std::vector<RNCTabViewItemsStruct>& oldItems,
@@ -178,7 +179,7 @@ bool haveTabItemsChanged(const std::vector<RNCTabViewItemsStruct>& oldItems,
178179
NSMutableArray<TabInfo *> *result = [NSMutableArray array];
179180

180181
for (const auto& item : items) {
181-
auto tabInfo = [[TabInfo alloc] initWithKey:RCTNSStringFromString(item.key) title:RCTNSStringFromString(item.title) badge:RCTNSStringFromString(item.badge) sfSymbol:RCTNSStringFromString(item.sfSymbol) activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor)];
182+
auto tabInfo = [[TabInfo alloc] initWithKey:RCTNSStringFromString(item.key) title:RCTNSStringFromString(item.title) badge:RCTNSStringFromString(item.badge) sfSymbol:RCTNSStringFromString(item.sfSymbol) activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor) hidden:item.hidden];
182183

183184
[result addObject:tabInfo];
184185
}

ios/TabViewImpl.swift

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -56,50 +56,20 @@ struct TabViewImpl: View {
5656
var body: some View {
5757
TabView(selection: $props.selectedPage) {
5858
ForEach(props.children.indices, id: \.self) { index in
59-
let child = props.children[safe: index] ?? UIView()
60-
let tabData = props.items[safe: index]
61-
let icon = props.icons[index]
62-
63-
RepresentableView(view: child)
64-
.ignoresTopSafeArea(
65-
props.ignoresTopSafeArea ?? false,
66-
frame: child.frame
67-
)
68-
.tabItem {
69-
TabItem(
70-
title: tabData?.title,
71-
icon: icon,
72-
sfSymbol: tabData?.sfSymbol,
73-
labeled: props.labeled
74-
)
75-
}
76-
.tag(tabData?.key)
77-
.tabBadge(tabData?.badge)
78-
#if os(iOS)
79-
.onAppear {
80-
// SwiftUI doesn't change `selection` when clicked on items from the "More" list.
81-
// More tab is visible only if we have more than 5 items.
82-
// This is a workaround that fixes the "More" feature.
83-
if index < 4 {
84-
return
85-
}
86-
if let key = tabData?.key, props.selectedPage != key {
87-
onSelect(key)
88-
}
89-
}
90-
#endif
59+
renderTabItem(at: index)
9160
}
92-
9361
}
9462
.onTabItemEvent({ index, isLongPress in
95-
if let key = props.items[safe: index]?.key {
96-
if isLongPress {
97-
onLongPress(key)
98-
emitHapticFeedback(longPress: true)
99-
} else {
100-
onSelect(key)
101-
emitHapticFeedback()
102-
}
63+
guard let key = props.items.filter({
64+
!$0.hidden || $0.key == props.selectedPage
65+
})[safe: index]?.key else { return }
66+
67+
if isLongPress {
68+
onLongPress(key)
69+
emitHapticFeedback(longPress: true)
70+
} else {
71+
onSelect(key)
72+
emitHapticFeedback()
10373
}
10474
})
10575
.tintColor(props.selectedActiveTintColor)
@@ -115,6 +85,42 @@ struct TabViewImpl: View {
11585
}
11686
}
11787

88+
@ViewBuilder
89+
private func renderTabItem(at index: Int) -> some View {
90+
let tabData = props.items[safe: index]
91+
let isHidden = tabData?.hidden ?? false
92+
let isFocused = props.selectedPage == tabData?.key
93+
94+
if !isHidden || isFocused {
95+
let child = props.children[safe: index] ?? UIView()
96+
let icon = props.icons[index]
97+
98+
RepresentableView(view: child)
99+
.ignoresTopSafeArea(
100+
props.ignoresTopSafeArea ?? false,
101+
frame: child.frame
102+
)
103+
.tabItem {
104+
TabItem(
105+
title: tabData?.title,
106+
icon: icon,
107+
sfSymbol: tabData?.sfSymbol,
108+
labeled: props.labeled
109+
)
110+
}
111+
.tag(tabData?.key)
112+
.tabBadge(tabData?.badge)
113+
#if os(iOS)
114+
.onAppear {
115+
guard index >= 4,
116+
let key = tabData?.key,
117+
props.selectedPage != key else { return }
118+
onSelect(key)
119+
}
120+
#endif
121+
}
122+
}
123+
118124
func emitHapticFeedback(longPress: Bool = false) {
119125
#if os(iOS)
120126
if !props.hapticFeedbackEnabled {

ios/TabViewProvider.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@ import React
88
@objc public let badge: String
99
@objc public let sfSymbol: String
1010
@objc public let activeTintColor: UIColor?
11+
@objc public let hidden: Bool
1112

1213
@objc
1314
public init(
1415
key: String,
1516
title: String,
1617
badge: String,
1718
sfSymbol: String,
18-
activeTintColor: UIColor?
19+
activeTintColor: UIColor?,
20+
hidden: Bool
1921
) {
2022
self.key = key
2123
self.title = title
2224
self.badge = badge
2325
self.sfSymbol = sfSymbol
2426
self.activeTintColor = activeTintColor
27+
self.hidden = hidden
2528
super.init()
2629
}
2730
}
@@ -210,7 +213,8 @@ import React
210213
title: itemDict["title"] as? String ?? "",
211214
badge: itemDict["badge"] as? String ?? "",
212215
sfSymbol: itemDict["sfSymbol"] as? String ?? "",
213-
activeTintColor: RCTConvert.uiColor(itemDict["activeTintColor"] as? NSNumber)
216+
activeTintColor: RCTConvert.uiColor(itemDict["activeTintColor"] as? NSNumber),
217+
hidden: itemDict["hidden"] as? Bool ?? false
214218
)
215219
)
216220
}

0 commit comments

Comments
 (0)