Skip to content

Commit 9769561

Browse files
committed
feat: introduce config
1 parent 3b66be2 commit 9769561

File tree

10 files changed

+141
-22
lines changed

10 files changed

+141
-22
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
117117
}
118118
}
119119

120+
fun setConfig(config: TabViewConfig) {
121+
labelVisibilityMode = if (config.labeled == false) {
122+
LABEL_VISIBILITY_UNLABELED
123+
} else if (config.labeled == true) {
124+
LABEL_VISIBILITY_LABELED
125+
} else {
126+
LABEL_VISIBILITY_AUTO
127+
}
128+
}
129+
120130
private fun getDrawable(imageSource: ImageSource): Drawable {
121131
// TODO: Check if this can be done using some built-in React Native class
122132
val imageRequest = ImageRequestBuilder.newBuilderWithSource(imageSource.uri).build()

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.rcttabview
22

33
import android.view.View.MeasureSpec
44
import com.facebook.react.bridge.ReadableArray
5+
import com.facebook.react.bridge.ReadableMap
56
import com.facebook.react.common.MapBuilder
67
import com.facebook.react.module.annotations.ReactModule
78
import com.facebook.react.uimanager.LayoutShadowNode
@@ -22,6 +23,10 @@ data class TabInfo(
2223
val badge: String
2324
)
2425

26+
data class TabViewConfig(
27+
val labeled: Boolean?
28+
)
29+
2530
@ReactModule(name = RCTTabViewViewManager.NAME)
2631
class RCTTabViewViewManager :
2732
SimpleViewManager<ReactBottomNavigationView>() {
@@ -55,6 +60,14 @@ class RCTTabViewViewManager :
5560
}
5661
}
5762

63+
@ReactProp(name = "config")
64+
fun setConfig(view: ReactBottomNavigationView, config: ReadableMap?) {
65+
val tabViewConfig = TabViewConfig(
66+
labeled = if (config?.hasKey("labeled") == true) config.getBoolean("labeled") else null,
67+
)
68+
view.setConfig(tabViewConfig)
69+
}
70+
5871
@ReactProp(name = "icons")
5972
fun setIcons(view: ReactBottomNavigationView, icons: ReadableArray?) {
6073
view.setIcons(icons)

example/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import ThreeTabs from './Examples/ThreeTabs';
2020
import FourTabs from './Examples/FourTabs';
2121
import MaterialBottomTabs from './Examples/MaterialBottomTabs';
2222
import SFSymbols from './Examples/SFSymbols';
23+
import LabeledTabs from './Examples/Labeled';
2324

2425
const examples = [
2526
{ component: ThreeTabs, name: 'Three Tabs' },
2627
{ component: FourTabs, name: 'Four Tabs' },
2728
{ component: SFSymbols, name: 'SF Symbols' },
29+
{ component: LabeledTabs, name: 'Labeled Tabs' },
2830
{
2931
component: FourTabs,
3032
name: 'Four Tabs - No header',

example/src/Examples/FourTabs.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export default function FourTabs() {
4242

4343
return (
4444
<TabView
45+
sidebarAdaptable
4546
navigationState={{ index, routes }}
4647
onIndexChange={setIndex}
4748
renderScene={renderScene}

example/src/Examples/Labeled.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import TabView, { SceneMap } from 'react-native-bottom-tabs';
2+
import { useState } from 'react';
3+
import { Article } from '../Screens/Article';
4+
import { Albums } from '../Screens/Albums';
5+
import { Contacts } from '../Screens/Contacts';
6+
import { Chat } from '../Screens/Chat';
7+
8+
export default function LabeledTabs() {
9+
const [index, setIndex] = useState(0);
10+
const [routes] = useState([
11+
{
12+
key: 'article',
13+
title: 'Article',
14+
focusedIcon: require('../../assets/icons/article_dark.png'),
15+
badge: '!',
16+
},
17+
{
18+
key: 'albums',
19+
title: 'Albums',
20+
focusedIcon: require('../../assets/icons/grid_dark.png'),
21+
badge: '5',
22+
},
23+
{
24+
key: 'contacts',
25+
focusedIcon: require('../../assets/icons/person_dark.png'),
26+
title: 'Contacts',
27+
},
28+
{
29+
key: 'chat',
30+
focusedIcon: require('../../assets/icons/chat_dark.png'),
31+
title: 'Chat',
32+
},
33+
]);
34+
35+
const renderScene = SceneMap({
36+
article: Article,
37+
albums: Albums,
38+
contacts: Contacts,
39+
chat: Chat,
40+
});
41+
42+
return (
43+
<TabView
44+
labeled
45+
navigationState={{ index, routes }}
46+
onIndexChange={setIndex}
47+
renderScene={renderScene}
48+
/>
49+
);
50+
}

ios/RCTTabViewViewManager.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ - (UIView *)view
2222
RCT_EXPORT_VIEW_PROPERTY(selectedPage, NSString)
2323
RCT_EXPORT_VIEW_PROPERTY(tabViewStyle, NSString)
2424
RCT_EXPORT_VIEW_PROPERTY(icons, NSArray<RCTImageSource *>);
25+
RCT_EXPORT_VIEW_PROPERTY(config, NSDictionary)
2526

2627
@end

ios/TabViewImpl.swift

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import React
77
*/
88
class TabViewProps: ObservableObject {
99
@Published var children: [UIView]?
10+
@Published var config: TabViewConfig?
1011
@Published var items: TabData?
1112
@Published var selectedPage: String?
12-
@Published var tabViewStyle: String?
1313
@Published var icons: [Int: UIImage] = [:]
1414
}
1515

@@ -38,50 +38,56 @@ struct TabViewImpl: View {
3838
let child = props.children?[safe: index] ?? UIView()
3939
let tabData = props.items?.tabs[safe: index]
4040
let icon = props.icons[index]
41+
4142
RepresentableView(view: child)
4243
.frame(maxWidth: .infinity, maxHeight: .infinity)
4344
.tabItem {
44-
TabItem(icon: icon, sfSymbol: tabData?.sfSymbol, title: tabData?.title)
45+
TabItem(
46+
title: tabData?.title,
47+
icon: icon,
48+
sfSymbol: tabData?.sfSymbol,
49+
labeled: props.config?.labeled
50+
)
4551
}
4652
.tag(tabData?.key)
4753
.tabBadge(tabData?.badge)
4854
}
49-
.getTabViewStyle(name: props.tabViewStyle ?? "")
5055
.onChange(of: props.selectedPage ?? "") { newValue in
5156
onSelect(newValue)
5257
}
5358
}
59+
.getSidebarAdaptable(enabled: props.config?.sidebarAdaptable ?? false)
5460
}
5561
}
5662

5763
struct TabItem: View {
64+
var title: String?
5865
var icon: UIImage?
5966
var sfSymbol: String?
60-
var title: String?
67+
var labeled: Bool?
6168

6269
var body: some View {
6370
if let icon {
6471
Image(uiImage: icon)
6572
} else if let sfSymbol, !sfSymbol.isEmpty {
6673
Image(systemName: sfSymbol)
6774
}
68-
Text(title ?? "")
75+
if (labeled != false) {
76+
Text(title ?? "")
77+
}
6978
}
7079
}
7180

7281
extension View {
7382
@ViewBuilder
74-
func getTabViewStyle(name: String) -> some View {
75-
switch name {
76-
case "automatic":
77-
self.tabViewStyle(.automatic)
78-
case "sidebarAdaptable":
79-
if #available(iOS 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, *) {
83+
func getSidebarAdaptable(enabled: Bool) -> some View {
84+
if #available(iOS 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, *) {
85+
if (enabled) {
8086
self.tabViewStyle(.sidebarAdaptable)
8187
} else {
8288
self
8389
}
84-
default:
90+
} else {
8591
self
8692
}
8793
}

ios/TabViewProvider.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ struct TabInfo: Codable {
99
let sfSymbol: String
1010
}
1111

12+
struct TabViewConfig: Codable {
13+
let sidebarAdaptable: Bool
14+
let labeled: Bool?
15+
}
16+
1217
struct TabData: Codable {
1318
let tabs: [TabInfo]
1419
}
@@ -30,15 +35,15 @@ struct TabData: Codable {
3035
}
3136
}
3237

33-
@objc var selectedPage: NSString? {
38+
@objc var config: NSDictionary? {
3439
didSet {
35-
props.selectedPage = selectedPage as? String
40+
props.config = parseTabViewConfig(from: config)
3641
}
3742
}
3843

39-
@objc var tabViewStyle: NSString? {
44+
@objc var selectedPage: NSString? {
4045
didSet {
41-
props.tabViewStyle = tabViewStyle as? String
46+
props.selectedPage = selectedPage as? String
4247
}
4348
}
4449

@@ -129,4 +134,16 @@ struct TabData: Codable {
129134

130135
return TabData(tabs: items)
131136
}
137+
138+
private func parseTabViewConfig(from dict: NSDictionary?) -> TabViewConfig? {
139+
guard let configDict = dict as? [String: Any] else { return nil }
140+
let sidebarAdaptable = configDict["sidebarAdaptable"] as? Bool ?? false
141+
let labeled = configDict["labeled"] as? Bool ?? nil
142+
143+
return TabViewConfig(
144+
sidebarAdaptable: sidebarAdaptable,
145+
labeled: labeled
146+
)
147+
}
148+
132149
}

src/TabView.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { TabViewItems } from './TabViewNativeComponent';
1+
import type { TabViewConfig, TabViewItems } from './TabViewNativeComponent';
22
import { Image, Platform, StyleSheet, View } from 'react-native';
33

44
//@ts-ignore
@@ -12,6 +12,14 @@ const isAppleSymbol = (icon: any): icon is { sfSymbol: string } =>
1212
icon.sfSymbol;
1313

1414
interface Props<Route extends BaseRoute> {
15+
/*
16+
* Whether to show labels in tabs. When false, only icons will be displayed.
17+
*/
18+
labeled?: boolean;
19+
/*
20+
* Whether to show labels in tabs. When false, only icons will be displayed.
21+
*/
22+
sidebarAdaptable?: boolean;
1523
navigationState: NavigationState<Route>;
1624
renderScene: (props: {
1725
route: Route;
@@ -96,6 +104,14 @@ const TabView = <Route extends BaseRoute>({
96104
[icons]
97105
);
98106

107+
const nativeConfig: TabViewConfig = useMemo(
108+
() => ({
109+
labeled: props.labeled,
110+
sidebarAdaptable: props.sidebarAdaptable,
111+
}),
112+
[props.labeled, props.sidebarAdaptable]
113+
);
114+
99115
const jumpTo = useLatestCallback((key: string) => {
100116
const index = navigationState.routes.findIndex(
101117
(route) => route.key === key
@@ -110,6 +126,7 @@ const TabView = <Route extends BaseRoute>({
110126
items={items}
111127
icons={resolvedIconAssets}
112128
selectedPage={focusedKey}
129+
config={nativeConfig}
113130
onPageSelected={({ nativeEvent: { key } }) => {
114131
jumpTo(key);
115132
}}

src/TabViewNativeComponent.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
22
import type { ViewProps } from 'react-native';
3-
import type {
4-
DirectEventHandler,
5-
WithDefault,
6-
} from 'react-native/Libraries/Types/CodegenTypes';
3+
import type { DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes';
74
//@ts-ignore
85
import type { ImageSource } from 'react-native/Libraries/Image/ImageSource';
96

@@ -18,12 +15,17 @@ export type TabViewItems = ReadonlyArray<{
1815
badge?: string;
1916
}>;
2017

18+
export type TabViewConfig = {
19+
labeled?: boolean;
20+
sidebarAdaptable?: boolean;
21+
};
22+
2123
export interface TabViewProps extends ViewProps {
2224
items: TabViewItems;
2325
selectedPage: string;
2426
onPageSelected?: DirectEventHandler<OnPageSelectedEventData>;
25-
tabViewStyle?: WithDefault<'automatic' | 'sidebarAdaptable', 'automatic'>;
2627
icons?: ReadonlyArray<ImageSource>;
28+
config?: TabViewConfig;
2729
}
2830

2931
export default codegenNativeComponent<TabViewProps>('RCTTabView');

0 commit comments

Comments
 (0)