Skip to content

Commit 2167733

Browse files
committed
feat: add fonts
1 parent f0ac627 commit 2167733

File tree

11 files changed

+240
-18
lines changed

11 files changed

+240
-18
lines changed

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

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.rcttabview
22

33
import android.content.Context
44
import android.content.res.ColorStateList
5+
import android.graphics.Typeface
56
import android.graphics.drawable.BitmapDrawable
67
import android.graphics.drawable.ColorDrawable
78
import android.graphics.drawable.Drawable
@@ -11,6 +12,8 @@ import android.view.Choreographer
1112
import android.view.HapticFeedbackConstants
1213
import android.view.MenuItem
1314
import android.view.View
15+
import android.view.ViewGroup
16+
import android.widget.TextView
1417
import androidx.appcompat.content.res.AppCompatResources
1518
import com.facebook.common.references.CloseableReference
1619
import com.facebook.datasource.DataSources
@@ -20,8 +23,10 @@ import com.facebook.imagepipeline.request.ImageRequestBuilder
2023
import com.facebook.react.bridge.Arguments
2124
import com.facebook.react.bridge.ReadableArray
2225
import com.facebook.react.bridge.WritableMap
26+
import com.facebook.react.common.assets.ReactFontManager
2327
import com.facebook.react.modules.core.ReactChoreographer
2428
import com.facebook.react.views.imagehelper.ImageSource
29+
import com.facebook.react.views.text.ReactTypefaceUtils
2530
import com.google.android.material.bottomnavigation.BottomNavigationView
2631

2732

@@ -37,6 +42,9 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
3742
private val checkedStateSet = intArrayOf(android.R.attr.state_checked)
3843
private val uncheckedStateSet = intArrayOf(-android.R.attr.state_checked)
3944
private var hapticFeedbackEnabled = true
45+
private var fontSize: Int? = null
46+
private var fontFamily: String? = null
47+
private var fontWeight: Int? = null
4048

4149
private val layoutCallback = Choreographer.FrameCallback {
4250
isLayoutEnqueued = false
@@ -96,6 +104,7 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
96104
if (icons.containsKey(index)) {
97105
menuItem.icon = getDrawable(icons[index]!!)
98106
}
107+
99108
if (item.badge.isNotEmpty()) {
100109
val badge = this.getOrCreateBadge(index)
101110
badge.isVisible = true
@@ -112,6 +121,7 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
112121
onTabSelected(menuItem)
113122
updateTintColors(menuItem)
114123
}
124+
updateTextAppearance()
115125
}
116126
}
117127
}
@@ -211,7 +221,55 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
211221
hapticFeedbackEnabled = enabled
212222
}
213223

214-
fun emitHapticFeedback(feedbackConstants: Int) {
224+
fun setFontSize(size: Int) {
225+
fontSize = size
226+
updateTextAppearance()
227+
}
228+
229+
fun setFontFamily(family: String?) {
230+
fontFamily = family
231+
updateTextAppearance()
232+
}
233+
234+
fun setFontWeight(weight: String?) {
235+
val fontWeight = ReactTypefaceUtils.parseFontWeight(weight)
236+
this.fontWeight = fontWeight
237+
updateTextAppearance()
238+
}
239+
240+
private fun getTypefaceStyle(weight: Int?) = when (weight) {
241+
700 -> Typeface.BOLD
242+
else -> Typeface.NORMAL
243+
}
244+
245+
private fun updateTextAppearance() {
246+
if (fontSize != null || fontFamily != null || fontWeight != null) {
247+
val menuView = getChildAt(0) as? ViewGroup ?: return
248+
val size = fontSize?.toFloat()?.takeIf { it > 0 } ?: 12f
249+
val typeface = ReactFontManager.getInstance().getTypeface(
250+
fontFamily ?: "",
251+
getTypefaceStyle(fontWeight),
252+
context.assets
253+
)
254+
255+
for (i in 0 until menuView.childCount) {
256+
val item = menuView.getChildAt(i)
257+
val largeLabel =
258+
item.findViewById<TextView>(com.google.android.material.R.id.navigation_bar_item_large_label_view)
259+
val smallLabel =
260+
item.findViewById<TextView>(com.google.android.material.R.id.navigation_bar_item_small_label_view)
261+
262+
listOf(largeLabel, smallLabel).forEach { label ->
263+
label?.apply {
264+
setTextSize(TypedValue.COMPLEX_UNIT_SP, size)
265+
setTypeface(typeface)
266+
}
267+
}
268+
}
269+
}
270+
}
271+
272+
private fun emitHapticFeedback(feedbackConstants: Int) {
215273
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && hapticFeedbackEnabled) {
216274
this.performHapticFeedback(feedbackConstants)
217275
}

android/src/newarch/RCTTabViewManager.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,18 @@ class RCTTabViewManager(context: ReactApplicationContext) :
127127
)
128128
}
129129

130+
override fun setFontFamily(view: ReactBottomNavigationView?, value: String?) {
131+
view?.setFontFamily(value)
132+
}
133+
134+
override fun setFontWeight(view: ReactBottomNavigationView?, value: String?) {
135+
view?.setFontWeight(value)
136+
}
137+
138+
override fun setFontSize(view: ReactBottomNavigationView?, value: Int) {
139+
view?.setFontSize(value)
140+
}
141+
130142
// iOS Methods
131143

132144
override fun setTranslucent(view: ReactBottomNavigationView?, value: Boolean) {
@@ -143,4 +155,6 @@ class RCTTabViewManager(context: ReactApplicationContext) :
143155

144156
override fun setScrollEdgeAppearance(view: ReactBottomNavigationView?, value: String?) {
145157
}
158+
159+
146160
}

android/src/oldarch/RCTTabViewManager.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,21 @@ class RCTTabViewManager(context: ReactApplicationContext) : SimpleViewManager<Re
114114
tabViewImpl.setHapticFeedbackEnabled(view, value)
115115
}
116116

117+
@ReactProp(name = "fontFamily")
118+
fun setFontFamily(view: ReactBottomNavigationView?, value: String?) {
119+
view?.setFontFamily(value)
120+
}
121+
122+
@ReactProp(name = "fontWeight")
123+
fun setFontWeight(view: ReactBottomNavigationView?, value: String?) {
124+
view?.setFontWeight(value)
125+
}
126+
127+
@ReactProp(name = "fontSize")
128+
fun setFontSize(view: ReactBottomNavigationView?, value: Int) {
129+
view?.setFontSize(value)
130+
}
131+
117132
class TabViewShadowNode() : LayoutShadowNode(),
118133
YogaMeasureFunction {
119134
private var mWidth = 0

example/src/Examples/NativeBottomTabs.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ function NativeBottomTabs() {
1818
tabBarActiveTintColor="#F7DBA7"
1919
barTintColor="#1E2D2F"
2020
rippleColor="#F7DBA7"
21+
fontFamily="Avenir"
22+
fontSize={15}
2123
activeIndicatorColor="#041F1E"
2224
screenListeners={{
2325
tabLongPress: (data) => {

ios/Fabric/RCTTabViewComponentView.mm

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,18 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
146146
if (oldViewProps.hapticFeedbackEnabled != newViewProps.hapticFeedbackEnabled) {
147147
_tabViewProvider.hapticFeedbackEnabled = newViewProps.hapticFeedbackEnabled;
148148
}
149+
150+
if (oldViewProps.fontSize != newViewProps.fontSize) {
151+
_tabViewProvider.fontSize = [NSNumber numberWithInt:newViewProps.fontSize];
152+
}
153+
154+
if (oldViewProps.fontWeight != newViewProps.fontWeight) {
155+
_tabViewProvider.fontWeigth = RCTNSStringFromStringNilIfEmpty(newViewProps.fontWeight);
156+
}
157+
158+
if (oldViewProps.fontFamily != newViewProps.fontFamily) {
159+
_tabViewProvider.fontFamily = RCTNSStringFromStringNilIfEmpty(newViewProps.fontFamily);
160+
}
149161

150162
[super updateProps:props oldProps:oldProps];
151163
}
@@ -179,7 +191,12 @@ bool haveTabItemsChanged(const std::vector<RNCTabViewItemsStruct>& oldItems,
179191
NSMutableArray<TabInfo *> *result = [NSMutableArray array];
180192

181193
for (const auto& item : items) {
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];
194+
auto tabInfo = [[TabInfo alloc] initWithKey:RCTNSStringFromString(item.key)
195+
title:RCTNSStringFromString(item.title)
196+
badge:RCTNSStringFromStringNilIfEmpty(item.badge)
197+
sfSymbol:RCTNSStringFromStringNilIfEmpty(item.sfSymbol)
198+
activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor)
199+
hidden:item.hidden];
183200

184201
[result addObject:tabInfo];
185202
}

ios/RCTTabViewViewManager.mm

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ - (instancetype)init
4343
RCT_EXPORT_VIEW_PROPERTY(activeTintColor, UIColor)
4444
RCT_EXPORT_VIEW_PROPERTY(inactiveTintColor, UIColor)
4545
RCT_EXPORT_VIEW_PROPERTY(hapticFeedbackEnabled, BOOL)
46+
RCT_EXPORT_VIEW_PROPERTY(fontFamily, NSString)
47+
RCT_EXPORT_VIEW_PROPERTY(fontWeight, NSString)
48+
RCT_EXPORT_VIEW_PROPERTY(fontSize, NSNumber)
4649

4750
// MARK: TabViewProviderDelegate
4851

ios/TabViewImpl.swift

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ class TabViewProps: ObservableObject {
2222
@Published var ignoresTopSafeArea: Bool = true
2323
@Published var disablePageAnimations: Bool = false
2424
@Published var hapticFeedbackEnabled: Bool = true
25+
@Published var fontSize: Int?
26+
@Published var fontFamily: String?
27+
@Published var fontWeight: String?
2528

2629
var selectedActiveTintColor: UIColor? {
2730
if let selectedPage = selectedPage,
@@ -166,36 +169,100 @@ struct TabItem: View {
166169
}
167170

168171
private func updateTabBarAppearance(props: TabViewProps, tabBar: UITabBar?) {
169-
guard let tabBar else { return }
170-
let appearanceType = props.scrollEdgeAppearance
172+
guard let tabBar else { return }
173+
174+
if props.scrollEdgeAppearance == "transparent" {
175+
configureTransparentAppearance(tabBar: tabBar, props: props)
176+
return
177+
}
178+
179+
configureStandardAppearance(tabBar: tabBar, props: props)
180+
}
181+
182+
private func createFontAttributes(
183+
size: CGFloat,
184+
family: String?,
185+
weight: String?,
186+
inactiveTintColor: UIColor?
187+
) -> [NSAttributedString.Key: Any] {
188+
var attributes: [NSAttributedString.Key: Any] = [:]
189+
190+
if let inactiveTintColor {
191+
attributes[.foregroundColor] = inactiveTintColor
192+
}
193+
194+
if family != nil || weight != nil {
195+
attributes[.font] = RCTFont.update(
196+
nil,
197+
withFamily: family,
198+
size: NSNumber(value: size),
199+
weight: weight,
200+
style: nil,
201+
variant: nil,
202+
scaleMultiplier: 1.0
203+
)
204+
} else {
205+
attributes[.font] = UIFont.boldSystemFont(ofSize: size)
206+
}
207+
208+
return attributes
209+
}
171210

172-
if (appearanceType == "transparent") {
173-
tabBar.barTintColor = props.barTintColor
174-
tabBar.isTranslucent = props.translucent
175-
tabBar.unselectedItemTintColor = props.inactiveTintColor
176-
return
211+
private func configureTransparentAppearance(tabBar: UITabBar, props: TabViewProps) {
212+
tabBar.barTintColor = props.barTintColor
213+
tabBar.isTranslucent = props.translucent
214+
tabBar.unselectedItemTintColor = props.inactiveTintColor
215+
216+
guard let items = tabBar.items else { return }
217+
218+
let fontSize = props.fontSize ?? 14
219+
let attributes = createFontAttributes(
220+
size: CGFloat(fontSize),
221+
family: props.fontFamily,
222+
weight: props.fontWeight,
223+
inactiveTintColor: props.inactiveTintColor
224+
)
225+
226+
items.forEach { item in
227+
item.setTitleTextAttributes(attributes, for: .normal)
177228
}
229+
}
178230

231+
private func configureStandardAppearance(tabBar: UITabBar, props: TabViewProps) {
179232
let appearance = UITabBarAppearance()
180233

181-
switch appearanceType {
234+
// Configure background
235+
switch props.scrollEdgeAppearance {
182236
case "opaque":
183237
appearance.configureWithOpaqueBackground()
184238
default:
185239
appearance.configureWithDefaultBackground()
186240
}
187241
appearance.backgroundColor = props.barTintColor
188-
242+
243+
// Configure item appearance
244+
let itemAppearance = UITabBarItemAppearance()
245+
let fontSize = props.fontSize != nil ? CGFloat(props.fontSize!) : UIFont.smallSystemFontSize
246+
247+
let attributes = createFontAttributes(
248+
size: fontSize,
249+
family: props.fontFamily,
250+
weight: props.fontWeight,
251+
inactiveTintColor: props.inactiveTintColor
252+
)
253+
189254
if let inactiveTintColor = props.inactiveTintColor {
190-
let itemAppearance = UITabBarItemAppearance()
191255
itemAppearance.normal.iconColor = inactiveTintColor
192-
itemAppearance.normal.titleTextAttributes = [.foregroundColor: inactiveTintColor]
193-
194-
appearance.stackedLayoutAppearance = itemAppearance
195-
appearance.inlineLayoutAppearance = itemAppearance
196-
appearance.compactInlineLayoutAppearance = itemAppearance
197256
}
198-
257+
258+
itemAppearance.normal.titleTextAttributes = attributes
259+
260+
// Apply item appearance to all layouts
261+
appearance.stackedLayoutAppearance = itemAppearance
262+
appearance.inlineLayoutAppearance = itemAppearance
263+
appearance.compactInlineLayoutAppearance = itemAppearance
264+
265+
// Apply final appearance
199266
tabBar.standardAppearance = appearance
200267
if #available(iOS 15.0, *) {
201268
tabBar.scrollEdgeAppearance = appearance.copy()
@@ -272,6 +339,15 @@ extension View {
272339
.onChange(of: props.selectedActiveTintColor) { newValue in
273340
updateTabBarAppearance(props: props, tabBar: tabBar)
274341
}
342+
.onChange(of: props.fontSize) { newValue in
343+
updateTabBarAppearance(props: props, tabBar: tabBar)
344+
}
345+
.onChange(of: props.fontFamily) { newValue in
346+
updateTabBarAppearance(props: props, tabBar: tabBar)
347+
}
348+
.onChange(of: props.fontWeight) { newValue in
349+
updateTabBarAppearance(props: props, tabBar: tabBar)
350+
}
275351
}
276352

277353
@ViewBuilder

ios/TabViewProvider.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,24 @@ import React
130130
props.inactiveTintColor = inactiveTintColor
131131
}
132132
}
133+
134+
@objc public var fontFamily: NSString? {
135+
didSet {
136+
props.fontFamily = fontFamily as? String
137+
}
138+
}
139+
140+
@objc public var fontWeigth: NSString? {
141+
didSet {
142+
props.fontWeight = fontWeigth as? String
143+
}
144+
}
145+
146+
@objc public var fontSize: NSNumber? {
147+
didSet {
148+
props.fontSize = fontSize as? Int
149+
}
150+
}
133151

134152
// New arch specific properties
135153

src/TabView.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,18 @@ interface Props<Route extends BaseRoute> {
123123
* Color of tab indicator. (Android only)
124124
*/
125125
activeIndicatorColor?: ColorValue;
126+
/**
127+
* Font family for the tab labels.
128+
*/
129+
fontFamily?: string;
130+
/**
131+
* Font weight for the tab labels.
132+
*/
133+
fontWeight?: string;
134+
/**
135+
* Font size for the tab labels.
136+
*/
137+
fontSize?: number;
126138
}
127139

128140
const ANDROID_MAX_TABS = 6;

0 commit comments

Comments
 (0)