@onekeyfe/react-native-tab-view 1.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/android/build.gradle +119 -0
  2. package/android/gradle.properties +4 -0
  3. package/android/src/main/AndroidManifest.xml +3 -0
  4. package/android/src/main/AndroidManifestNew.xml +2 -0
  5. package/android/src/main/java/com/rcttabview/ImageSource.kt +86 -0
  6. package/android/src/main/java/com/rcttabview/RCTTabView.kt +529 -0
  7. package/android/src/main/java/com/rcttabview/RCTTabViewManager.kt +204 -0
  8. package/android/src/main/java/com/rcttabview/RCTTabViewPackage.kt +16 -0
  9. package/android/src/main/java/com/rcttabview/TabInfo.kt +12 -0
  10. package/android/src/main/java/com/rcttabview/Utils.kt +31 -0
  11. package/android/src/main/java/com/rcttabview/events/OnNativeLayoutEvent.kt +20 -0
  12. package/android/src/main/java/com/rcttabview/events/OnTabBarMeasuredEvent.kt +19 -0
  13. package/android/src/main/java/com/rcttabview/events/PageSelectedEvent.kt +21 -0
  14. package/android/src/main/java/com/rcttabview/events/TabLongPressedEvent.kt +19 -0
  15. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h +32 -0
  16. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp +18 -0
  17. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h +35 -0
  18. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.cpp +15 -0
  19. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h +25 -0
  20. package/ios/Extensions.swift +46 -0
  21. package/ios/RCTBottomAccessoryComponentView.h +12 -0
  22. package/ios/RCTBottomAccessoryComponentView.mm +67 -0
  23. package/ios/RCTBottomAccessoryContainerView.swift +51 -0
  24. package/ios/RCTTabViewComponentView.h +12 -0
  25. package/ios/RCTTabViewComponentView.mm +325 -0
  26. package/ios/RCTTabViewContainerView.swift +768 -0
  27. package/ios/RCTTabViewLog.h +7 -0
  28. package/ios/RCTTabViewLog.m +32 -0
  29. package/ios/SVG/CoreSVG.h +13 -0
  30. package/ios/SVG/CoreSVG.mm +177 -0
  31. package/ios/SVG/SvgDecoder.h +10 -0
  32. package/ios/SVG/SvgDecoder.mm +32 -0
  33. package/ios/TabBarFontSize.swift +55 -0
  34. package/lib/module/BottomAccessoryView.js +45 -0
  35. package/lib/module/BottomAccessoryView.js.map +1 -0
  36. package/lib/module/BottomAccessoryViewNativeComponent.ts +27 -0
  37. package/lib/module/DelayedFreeze.js +26 -0
  38. package/lib/module/DelayedFreeze.js.map +1 -0
  39. package/lib/module/NativeSVGDecoder.js +5 -0
  40. package/lib/module/NativeSVGDecoder.js.map +1 -0
  41. package/lib/module/SceneMap.js +28 -0
  42. package/lib/module/SceneMap.js.map +1 -0
  43. package/lib/module/TabView.js +263 -0
  44. package/lib/module/TabView.js.map +1 -0
  45. package/lib/module/TabViewNativeComponent.ts +68 -0
  46. package/lib/module/codegen-types.d.js +2 -0
  47. package/lib/module/codegen-types.d.js.map +1 -0
  48. package/lib/module/index.js +20 -0
  49. package/lib/module/index.js.map +1 -0
  50. package/lib/module/package.json +1 -0
  51. package/lib/module/types.js +4 -0
  52. package/lib/module/types.js.map +1 -0
  53. package/lib/module/utils/BottomTabBarHeightContext.js +5 -0
  54. package/lib/module/utils/BottomTabBarHeightContext.js.map +1 -0
  55. package/lib/module/utils/useBottomTabBarHeight.js +12 -0
  56. package/lib/module/utils/useBottomTabBarHeight.js.map +1 -0
  57. package/lib/typescript/package.json +1 -0
  58. package/lib/typescript/src/BottomAccessoryView.d.ts +8 -0
  59. package/lib/typescript/src/BottomAccessoryView.d.ts.map +1 -0
  60. package/lib/typescript/src/BottomAccessoryViewNativeComponent.d.ts +16 -0
  61. package/lib/typescript/src/BottomAccessoryViewNativeComponent.d.ts.map +1 -0
  62. package/lib/typescript/src/DelayedFreeze.d.ts +8 -0
  63. package/lib/typescript/src/DelayedFreeze.d.ts.map +1 -0
  64. package/lib/typescript/src/NativeSVGDecoder.d.ts +6 -0
  65. package/lib/typescript/src/NativeSVGDecoder.d.ts.map +1 -0
  66. package/lib/typescript/src/SceneMap.d.ts +10 -0
  67. package/lib/typescript/src/SceneMap.d.ts.map +1 -0
  68. package/lib/typescript/src/TabView.d.ts +178 -0
  69. package/lib/typescript/src/TabView.d.ts.map +1 -0
  70. package/lib/typescript/src/TabViewNativeComponent.d.ts +55 -0
  71. package/lib/typescript/src/TabViewNativeComponent.d.ts.map +1 -0
  72. package/lib/typescript/src/index.d.ts +16 -0
  73. package/lib/typescript/src/index.d.ts.map +1 -0
  74. package/lib/typescript/src/types.d.ts +29 -0
  75. package/lib/typescript/src/types.d.ts.map +1 -0
  76. package/lib/typescript/src/utils/BottomTabBarHeightContext.d.ts +3 -0
  77. package/lib/typescript/src/utils/BottomTabBarHeightContext.d.ts.map +1 -0
  78. package/lib/typescript/src/utils/useBottomTabBarHeight.d.ts +2 -0
  79. package/lib/typescript/src/utils/useBottomTabBarHeight.d.ts.map +1 -0
  80. package/package.json +114 -0
  81. package/react-native-tab-view.podspec +36 -0
  82. package/react-native.config.js +13 -0
  83. package/src/BottomAccessoryView.tsx +58 -0
  84. package/src/BottomAccessoryViewNativeComponent.ts +27 -0
  85. package/src/DelayedFreeze.tsx +27 -0
  86. package/src/NativeSVGDecoder.ts +5 -0
  87. package/src/SceneMap.tsx +34 -0
  88. package/src/TabView.tsx +466 -0
  89. package/src/TabViewNativeComponent.ts +68 -0
  90. package/src/codegen-types.d.ts +28 -0
  91. package/src/index.tsx +18 -0
  92. package/src/types.ts +31 -0
  93. package/src/utils/BottomTabBarHeightContext.ts +5 -0
  94. package/src/utils/useBottomTabBarHeight.ts +15 -0
@@ -0,0 +1,466 @@
1
+ import React, { useLayoutEffect, useRef } from 'react';
2
+ import type { NativeSyntheticEvent } from 'react-native';
3
+ import {
4
+ type ColorValue,
5
+ type DimensionValue,
6
+ Image,
7
+ Platform,
8
+ type StyleProp,
9
+ StyleSheet,
10
+ View,
11
+ type ViewStyle,
12
+ processColor,
13
+ } from 'react-native';
14
+ import { BottomTabBarHeightContext } from './utils/BottomTabBarHeightContext';
15
+
16
+ // eslint-disable-next-line @react-native/no-deep-imports
17
+ import type { ImageSource } from 'react-native/Libraries/Image/ImageSource';
18
+ import NativeTabView, {
19
+ type TabViewItems,
20
+ } from './TabViewNativeComponent';
21
+ import useLatestCallback from 'use-latest-callback';
22
+ import type { AppleIcon, BaseRoute, NavigationState, TabRole } from './types';
23
+ import DelayedFreeze from './DelayedFreeze';
24
+ import {
25
+ BottomAccessoryView,
26
+ type BottomAccessoryViewProps,
27
+ } from './BottomAccessoryView';
28
+
29
+ const isAppleSymbol = (icon: any): icon is { sfSymbol: string } =>
30
+ icon?.sfSymbol;
31
+
32
+ interface Props<Route extends BaseRoute> {
33
+ /*
34
+ * Whether to show labels in tabs. When false, only icons will be displayed.
35
+ */
36
+ labeled?: boolean;
37
+ /**
38
+ * A tab bar style that adapts to each platform.
39
+ *
40
+ * Tab views using the sidebar adaptable style have an appearance
41
+ * that varies depending on the platform:
42
+ * - iPadOS displays a top tab bar that can adapt into a sidebar.
43
+ * - iOS displays a bottom tab bar.
44
+ * - macOS and tvOS always show a sidebar.
45
+ * - visionOS shows an ornament and also shows a sidebar for secondary tabs within a `TabSection`.
46
+ */
47
+ sidebarAdaptable?: boolean;
48
+ /**
49
+ * Whether to disable page animations between tabs. (iOS only) Defaults to `false`.
50
+ */
51
+ disablePageAnimations?: boolean;
52
+ /**
53
+ * Whether to enable haptic feedback. Defaults to `false`.
54
+ */
55
+ hapticFeedbackEnabled?: boolean;
56
+ /**
57
+ * Describes the appearance attributes for the tabBar to use when an observable scroll view is scrolled to the bottom. (iOS only)
58
+ */
59
+ scrollEdgeAppearance?: 'default' | 'opaque' | 'transparent';
60
+
61
+ /**
62
+ * Behavior for minimizing the tab bar. (iOS 26+)
63
+ */
64
+ minimizeBehavior?: 'automatic' | 'onScrollDown' | 'onScrollUp' | 'never';
65
+ /**
66
+ * Active tab color.
67
+ */
68
+ tabBarActiveTintColor?: ColorValue;
69
+ /**
70
+ * Inactive tab color.
71
+ */
72
+ tabBarInactiveTintColor?: ColorValue;
73
+ /**
74
+ * State for the tab view.
75
+ */
76
+ navigationState: NavigationState<Route>;
77
+ /**
78
+ * Function which takes an object with the route and returns a React element.
79
+ */
80
+ renderScene: (props: {
81
+ route: Route;
82
+ jumpTo: (key: string) => void;
83
+ }) => React.ReactNode | null;
84
+ /**
85
+ * Callback which is called on tab change, receives the index of the new tab as argument.
86
+ */
87
+ onIndexChange: (index: number) => void;
88
+ /**
89
+ * Callback which is called on long press on tab, receives the index of the tab as argument.
90
+ */
91
+ onTabLongPress?: (index: number) => void;
92
+ /**
93
+ * Get lazy for the current screen. Uses true by default.
94
+ */
95
+ getLazy?: (props: { route: Route }) => boolean | undefined;
96
+ /**
97
+ * Get label text for the tab, uses `route.title` by default.
98
+ */
99
+ getLabelText?: (props: { route: Route }) => string | undefined;
100
+ /**
101
+ * Get badge for the tab, uses `route.badge` by default.
102
+ */
103
+ getBadge?: (props: { route: Route }) => string | undefined;
104
+ /**
105
+ * Get badge background color for the tab. (Android only)
106
+ */
107
+ getBadgeBackgroundColor?: (props: { route: Route }) => ColorValue | undefined;
108
+ /**
109
+ * Get badge text color for the tab. (Android only)
110
+ */
111
+ getBadgeTextColor?: (props: { route: Route }) => ColorValue | undefined;
112
+ /**
113
+ * Get active tint color for the tab.
114
+ */
115
+ getActiveTintColor?: (props: { route: Route }) => ColorValue | undefined;
116
+ /**
117
+ * Determines whether the tab prevents default action on press.
118
+ */
119
+ getPreventsDefault?: (props: { route: Route }) => boolean | undefined;
120
+ /**
121
+ * Get icon for the tab.
122
+ */
123
+ getIcon?: (props: {
124
+ route: Route;
125
+ focused: boolean;
126
+ }) => ImageSource | AppleIcon | undefined | null;
127
+
128
+ /**
129
+ * Get hidden for the tab.
130
+ */
131
+ getHidden?: (props: { route: Route }) => boolean | undefined;
132
+
133
+ /**
134
+ * Get testID for the tab.
135
+ */
136
+ getTestID?: (props: { route: Route }) => string | undefined;
137
+
138
+ /**
139
+ * Get role for the tab. (iOS only)
140
+ */
141
+ getRole?: (props: { route: Route }) => TabRole | undefined;
142
+
143
+ /**
144
+ * Custom tab bar to render.
145
+ */
146
+ tabBar?: () => React.ReactNode;
147
+
148
+ /**
149
+ * Get freezeOnBlur for the current screen.
150
+ */
151
+ getFreezeOnBlur?: (props: { route: Route }) => boolean | undefined;
152
+
153
+ /**
154
+ * Get style for the scene.
155
+ */
156
+ getSceneStyle?: (props: { route: Route }) => StyleProp<ViewStyle>;
157
+
158
+ tabBarStyle?: {
159
+ backgroundColor?: ColorValue;
160
+ };
161
+
162
+ /**
163
+ * A Boolean value that indicates whether the tab bar is translucent. (iOS only)
164
+ */
165
+ translucent?: boolean;
166
+ rippleColor?: ColorValue;
167
+ /**
168
+ * Color of tab indicator. (Android only)
169
+ */
170
+ activeIndicatorColor?: ColorValue;
171
+ tabLabelStyle?: {
172
+ fontFamily?: string;
173
+ fontWeight?: string;
174
+ fontSize?: number;
175
+ };
176
+ /**
177
+ * A function that returns a React element to display as bottom accessory view.
178
+ * iOS 26+ only.
179
+ */
180
+ renderBottomAccessoryView?: BottomAccessoryViewProps['renderBottomAccessoryView'];
181
+ /**
182
+ * Whether the tab bar is hidden.
183
+ */
184
+ tabBarHidden?: boolean;
185
+ /**
186
+ * Whether to ignore bottom system insets (navigation bar). Android only.
187
+ */
188
+ ignoreBottomInsets?: boolean;
189
+ }
190
+
191
+ const ANDROID_MAX_TABS = 100;
192
+
193
+ const TabView = <Route extends BaseRoute>({
194
+ navigationState,
195
+ renderScene,
196
+ onIndexChange,
197
+ onTabLongPress,
198
+ rippleColor,
199
+ tabBarActiveTintColor: activeTintColor,
200
+ tabBarInactiveTintColor: inactiveTintColor,
201
+ getBadge = ({ route }: { route: Route }) => route.badge,
202
+ getBadgeBackgroundColor = ({ route }: { route: Route }) =>
203
+ route.badgeBackgroundColor,
204
+ getBadgeTextColor = ({ route }: { route: Route }) => route.badgeTextColor,
205
+ getLazy = ({ route }: { route: Route }) => route.lazy,
206
+ getLabelText = ({ route }: { route: Route }) => route.title,
207
+ getIcon = ({ route, focused }: { route: Route; focused: boolean }) =>
208
+ route.unfocusedIcon
209
+ ? focused
210
+ ? route.focusedIcon
211
+ : route.unfocusedIcon
212
+ : route.focusedIcon,
213
+ getHidden = ({ route }: { route: Route }) => route.hidden,
214
+ getActiveTintColor = ({ route }: { route: Route }) => route.activeTintColor,
215
+ getTestID = ({ route }: { route: Route }) => route.testID,
216
+ getRole = ({ route }: { route: Route }) => route.role,
217
+ getSceneStyle = ({ route }: { route: Route }) => route.style,
218
+ getPreventsDefault = ({ route }: { route: Route }) => route.preventsDefault,
219
+ hapticFeedbackEnabled = false,
220
+ labeled = Platform.OS !== 'android' ? true : undefined,
221
+ getFreezeOnBlur = ({ route }: { route: Route }) => route.freezeOnBlur,
222
+ tabBar: renderCustomTabBar,
223
+ tabBarStyle,
224
+ tabLabelStyle,
225
+ renderBottomAccessoryView,
226
+ activeIndicatorColor,
227
+ ...props
228
+ }: Props<Route>) => {
229
+ // @ts-ignore
230
+ const focusedKey = navigationState.routes[navigationState.index].key;
231
+ const customTabBarWrapperRef = useRef<View>(null) as React.RefObject<any>;
232
+ const [tabBarHeight, setTabBarHeight] = React.useState<number | undefined>(0);
233
+ const [measuredDimensions, setMeasuredDimensions] = React.useState<
234
+ { width: DimensionValue; height: DimensionValue } | undefined
235
+ >({ width: '100%', height: '100%' });
236
+
237
+ const trimmedRoutes = React.useMemo(() => {
238
+ if (
239
+ Platform.OS === 'android' &&
240
+ navigationState.routes.length > ANDROID_MAX_TABS
241
+ ) {
242
+ console.warn(
243
+ `TabView only supports up to ${ANDROID_MAX_TABS} tabs on Android`
244
+ );
245
+ return navigationState.routes.slice(0, ANDROID_MAX_TABS);
246
+ }
247
+ return navigationState.routes;
248
+ }, [navigationState.routes]);
249
+
250
+ const [loaded, setLoaded] = React.useState<string[]>([focusedKey]);
251
+
252
+ if (!loaded.includes(focusedKey)) {
253
+ setLoaded((loaded) => [...loaded, focusedKey]);
254
+ }
255
+
256
+ const icons = React.useMemo(
257
+ () =>
258
+ trimmedRoutes.map((route) =>
259
+ getIcon({
260
+ route,
261
+ focused: route.key === focusedKey,
262
+ })
263
+ ),
264
+ [focusedKey, getIcon, trimmedRoutes]
265
+ );
266
+
267
+ const items: TabViewItems[number][] = React.useMemo(
268
+ () =>
269
+ trimmedRoutes.map((route, index) => {
270
+ const icon = icons[index];
271
+ const isSfSymbol = isAppleSymbol(icon);
272
+
273
+ if (Platform.OS === 'android' && isSfSymbol) {
274
+ console.warn(
275
+ 'SF Symbols are not supported on Android. Use require() or pass uri to load an image instead.'
276
+ );
277
+ }
278
+
279
+ const color = processColor(getActiveTintColor({ route }));
280
+ const badgeBgColor = processColor(getBadgeBackgroundColor?.({ route }));
281
+ const badgeTxtColor = processColor(getBadgeTextColor?.({ route }));
282
+
283
+ return {
284
+ key: route.key,
285
+ title: getLabelText({ route }) ?? route.key,
286
+ sfSymbol: isSfSymbol ? icon.sfSymbol : undefined,
287
+ badge: getBadge?.({ route }),
288
+ badgeBackgroundColor:
289
+ typeof badgeBgColor === 'number' ? badgeBgColor : undefined,
290
+ badgeTextColor:
291
+ typeof badgeTxtColor === 'number' ? badgeTxtColor : undefined,
292
+ activeTintColor: typeof color === 'number' ? color : undefined,
293
+ hidden: getHidden?.({ route }),
294
+ testID: getTestID?.({ route }),
295
+ role: getRole?.({ route }),
296
+ preventsDefault: getPreventsDefault?.({ route }),
297
+ };
298
+ }),
299
+ [
300
+ trimmedRoutes,
301
+ icons,
302
+ getLabelText,
303
+ getBadge,
304
+ getBadgeBackgroundColor,
305
+ getBadgeTextColor,
306
+ getActiveTintColor,
307
+ getHidden,
308
+ getTestID,
309
+ getRole,
310
+ getPreventsDefault,
311
+ ]
312
+ );
313
+
314
+ const resolvedIconAssets = React.useMemo(
315
+ () =>
316
+ icons.map((icon) => {
317
+ if (icon && !isAppleSymbol(icon)) {
318
+ // @ts-ignore - resolveAssetSource accepts ImageSourcePropType
319
+ const resolved = Image.resolveAssetSource(icon);
320
+ return {
321
+ uri: resolved?.uri ?? '',
322
+ width: resolved?.width ?? 0,
323
+ height: resolved?.height ?? 0,
324
+ scale: resolved?.scale ?? 1,
325
+ };
326
+ }
327
+ return { uri: '', width: 0, height: 0, scale: 1 };
328
+ }),
329
+ [icons]
330
+ );
331
+
332
+ const jumpTo = useLatestCallback((key: string) => {
333
+ const index = trimmedRoutes.findIndex((route) => route.key === key);
334
+ onIndexChange(index);
335
+ });
336
+
337
+ const handleTabLongPress = React.useCallback(
338
+ (event: NativeSyntheticEvent<{ key: string }>) => {
339
+ const { key } = event.nativeEvent;
340
+ const index = trimmedRoutes.findIndex((route) => route.key === key);
341
+ onTabLongPress?.(index);
342
+ },
343
+ [trimmedRoutes, onTabLongPress]
344
+ );
345
+
346
+ const handlePageSelected = React.useCallback(
347
+ (event: NativeSyntheticEvent<{ key: string }>) => {
348
+ const { key } = event.nativeEvent;
349
+ jumpTo(key);
350
+ },
351
+ [jumpTo]
352
+ );
353
+
354
+ const handleTabBarMeasured = React.useCallback(
355
+ (event: NativeSyntheticEvent<{ height: number }>) => {
356
+ setTabBarHeight(event.nativeEvent.height);
357
+ },
358
+ [setTabBarHeight]
359
+ );
360
+
361
+ const handleNativeLayout = React.useCallback(
362
+ (event: NativeSyntheticEvent<{ width: number; height: number }>) => {
363
+ const { width, height } = event.nativeEvent;
364
+ setMeasuredDimensions({ width, height });
365
+ },
366
+ [setMeasuredDimensions]
367
+ );
368
+
369
+ useLayoutEffect(() => {
370
+ if (renderCustomTabBar && customTabBarWrapperRef.current) {
371
+ customTabBarWrapperRef.current.measure((_x: number, _y: number, _width: number, height: number) => {
372
+ setTabBarHeight(height);
373
+ });
374
+ }
375
+ }, [renderCustomTabBar]);
376
+
377
+ return (
378
+ <BottomTabBarHeightContext.Provider value={tabBarHeight}>
379
+ <NativeTabView
380
+ {...props}
381
+ {...tabLabelStyle}
382
+ style={styles.fullWidth}
383
+ items={items}
384
+ icons={renderCustomTabBar ? undefined : resolvedIconAssets}
385
+ selectedPage={focusedKey}
386
+ tabBarHidden={props.tabBarHidden ?? !!renderCustomTabBar}
387
+ onTabLongPress={handleTabLongPress}
388
+ onPageSelected={handlePageSelected}
389
+ onTabBarMeasured={handleTabBarMeasured}
390
+ onNativeLayout={handleNativeLayout}
391
+ hapticFeedbackEnabled={hapticFeedbackEnabled}
392
+ activeTintColor={activeTintColor}
393
+ inactiveTintColor={inactiveTintColor}
394
+ barTintColor={tabBarStyle?.backgroundColor}
395
+ rippleColor={rippleColor}
396
+ activeIndicatorColor={activeIndicatorColor}
397
+ labeled={labeled}
398
+ >
399
+ {trimmedRoutes.map((route) => {
400
+ if (getLazy({ route }) !== false && !loaded.includes(route.key)) {
401
+ return (
402
+ <View
403
+ key={route.key}
404
+ collapsable={false}
405
+ style={styles.fullWidth}
406
+ />
407
+ );
408
+ }
409
+
410
+ const focused = route.key === focusedKey;
411
+ const freeze = !focused ? getFreezeOnBlur({ route }) : false;
412
+
413
+ const customStyle = getSceneStyle({ route });
414
+
415
+ return (
416
+ <View
417
+ key={route.key}
418
+ style={[
419
+ styles.screen,
420
+ renderCustomTabBar ? styles.fullWidth : measuredDimensions,
421
+ customStyle,
422
+ ]}
423
+ collapsable={false}
424
+ pointerEvents={focused ? 'auto' : 'none'}
425
+ accessibilityElementsHidden={!focused}
426
+ importantForAccessibility={
427
+ focused ? 'auto' : 'no-hide-descendants'
428
+ }
429
+ >
430
+ <DelayedFreeze freeze={!!freeze}>
431
+ {renderScene({
432
+ route,
433
+ jumpTo,
434
+ })}
435
+ </DelayedFreeze>
436
+ </View>
437
+ );
438
+ })}
439
+ {Platform.OS === 'ios' &&
440
+ parseFloat(Platform.Version) >= 26 &&
441
+ renderBottomAccessoryView &&
442
+ !renderCustomTabBar ? (
443
+ <BottomAccessoryView
444
+ renderBottomAccessoryView={renderBottomAccessoryView}
445
+ />
446
+ ) : null}
447
+ </NativeTabView>
448
+ {renderCustomTabBar ? (
449
+ <View ref={customTabBarWrapperRef}>{renderCustomTabBar()}</View>
450
+ ) : null}
451
+ </BottomTabBarHeightContext.Provider>
452
+ );
453
+ };
454
+
455
+ const styles = StyleSheet.create({
456
+ fullWidth: {
457
+ width: '100%',
458
+ height: '100%',
459
+ flex: 1,
460
+ },
461
+ screen: {
462
+ position: 'absolute',
463
+ },
464
+ });
465
+
466
+ export default TabView;
@@ -0,0 +1,68 @@
1
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
2
+ import type { ColorValue, ProcessedColorValue, ViewProps } from 'react-native';
3
+ import type {
4
+ DirectEventHandler,
5
+ Double,
6
+ Int32,
7
+ WithDefault,
8
+ } from 'react-native/Libraries/Types/CodegenTypes';
9
+ //@ts-ignore
10
+ import type { ImageSource } from 'react-native/Libraries/Image/ImageSource';
11
+
12
+ export type OnPageSelectedEventData = Readonly<{
13
+ key: string;
14
+ }>;
15
+
16
+ export type OnTabBarMeasured = Readonly<{
17
+ height: Int32;
18
+ }>;
19
+
20
+ export type OnNativeLayout = Readonly<{
21
+ width: Double;
22
+ height: Double;
23
+ }>;
24
+
25
+ export type TabViewItems = ReadonlyArray<{
26
+ key: string;
27
+ title: string;
28
+ sfSymbol?: string;
29
+ badge?: string;
30
+ badgeBackgroundColor?: ProcessedColorValue | null;
31
+ badgeTextColor?: ProcessedColorValue | null;
32
+ activeTintColor?: ProcessedColorValue | null;
33
+ hidden?: boolean;
34
+ testID?: string;
35
+ role?: string;
36
+ preventsDefault?: boolean;
37
+ }>;
38
+
39
+ export interface TabViewProps extends ViewProps {
40
+ items: TabViewItems;
41
+ selectedPage: string;
42
+ onPageSelected?: DirectEventHandler<OnPageSelectedEventData>;
43
+ onTabLongPress?: DirectEventHandler<OnPageSelectedEventData>;
44
+ onTabBarMeasured?: DirectEventHandler<OnTabBarMeasured>;
45
+ onNativeLayout?: DirectEventHandler<OnNativeLayout>;
46
+ icons?: ReadonlyArray<ImageSource>;
47
+ tabBarHidden?: boolean;
48
+ labeled?: boolean;
49
+ sidebarAdaptable?: boolean;
50
+ scrollEdgeAppearance?: string;
51
+ barTintColor?: ColorValue;
52
+ translucent?: WithDefault<boolean, true>;
53
+ rippleColor?: ColorValue;
54
+ activeTintColor?: ColorValue;
55
+ inactiveTintColor?: ColorValue;
56
+ disablePageAnimations?: boolean;
57
+ activeIndicatorColor?: ColorValue;
58
+ hapticFeedbackEnabled?: boolean;
59
+ minimizeBehavior?: string;
60
+ fontFamily?: string;
61
+ fontWeight?: string;
62
+ fontSize?: Int32;
63
+ ignoreBottomInsets?: boolean;
64
+ }
65
+
66
+ export default codegenNativeComponent<TabViewProps>('RNCTabView', {
67
+ interfaceOnly: true,
68
+ });
@@ -0,0 +1,28 @@
1
+ declare module 'react-native/Libraries/Types/CodegenTypes' {
2
+ import type { NativeSyntheticEvent } from 'react-native';
3
+
4
+ export type Double = number;
5
+ export type Float = number;
6
+ export type Int32 = number;
7
+ export type WithDefault<T, V> = T;
8
+ export type DirectEventHandler<T> = (
9
+ event: NativeSyntheticEvent<T>
10
+ ) => void;
11
+ export type BubblingEventHandler<T> = (
12
+ event: NativeSyntheticEvent<T>
13
+ ) => void;
14
+ }
15
+
16
+ declare module 'react-native/Libraries/Utilities/codegenNativeComponent' {
17
+ import type { HostComponent } from 'react-native';
18
+
19
+ export default function codegenNativeComponent<P>(
20
+ componentName: string,
21
+ options?: {
22
+ interfaceOnly?: boolean;
23
+ paperComponentName?: string;
24
+ paperComponentNameDeprecated?: string;
25
+ excludedPlatforms?: ReadonlyArray<'iOS' | 'android'>;
26
+ }
27
+ ): HostComponent<P>;
28
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,18 @@
1
+ import TabView from './TabView';
2
+
3
+ /**
4
+ * Views
5
+ */
6
+ export default TabView;
7
+
8
+ /**
9
+ * Utilities
10
+ */
11
+ export { SceneMap } from './SceneMap';
12
+ export { useBottomTabBarHeight } from './utils/useBottomTabBarHeight';
13
+ export { BottomTabBarHeightContext } from './utils/BottomTabBarHeightContext';
14
+
15
+ /**
16
+ * Types
17
+ */
18
+ export type { AppleIcon, TabRole } from './types';
package/src/types.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { ImageSourcePropType, StyleProp, ViewStyle } from 'react-native';
2
+ import type { SFSymbol } from 'sf-symbols-typescript';
3
+
4
+ export type IconSource = string | ImageSourcePropType;
5
+
6
+ export type AppleIcon = { sfSymbol: SFSymbol };
7
+
8
+ export type TabRole = 'search';
9
+
10
+ export type BaseRoute = {
11
+ key: string;
12
+ title?: string;
13
+ badge?: string;
14
+ badgeBackgroundColor?: string;
15
+ badgeTextColor?: string;
16
+ lazy?: boolean;
17
+ focusedIcon?: ImageSourcePropType | AppleIcon;
18
+ unfocusedIcon?: ImageSourcePropType | AppleIcon;
19
+ activeTintColor?: string;
20
+ hidden?: boolean;
21
+ testID?: string;
22
+ role?: TabRole;
23
+ freezeOnBlur?: boolean;
24
+ style?: StyleProp<ViewStyle>;
25
+ preventsDefault?: boolean;
26
+ };
27
+
28
+ export type NavigationState<Route extends BaseRoute> = {
29
+ index: number;
30
+ routes: Route[];
31
+ };
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+
3
+ export const BottomTabBarHeightContext = React.createContext<
4
+ number | undefined
5
+ >(undefined);
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+
3
+ import { BottomTabBarHeightContext } from './BottomTabBarHeightContext';
4
+
5
+ export function useBottomTabBarHeight() {
6
+ const height = React.useContext(BottomTabBarHeightContext);
7
+
8
+ if (height === undefined) {
9
+ throw new Error(
10
+ "Couldn't find the bottom tab bar height. Are you inside a screen in Native Bottom Tab Navigator?"
11
+ );
12
+ }
13
+
14
+ return height;
15
+ }