@momo-kits/foundation 0.81.1-beta.5 → 0.81.1-optimize.10

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 (77) hide show
  1. package/Application/BottomSheet.tsx +225 -0
  2. package/Application/BottomTab/BottomTabBar.tsx +291 -0
  3. package/Application/BottomTab/index.tsx +138 -0
  4. package/Application/Components.tsx +274 -211
  5. package/Application/ModalScreen.tsx +10 -226
  6. package/Application/Navigation.ts +17 -6
  7. package/Application/NavigationContainer.tsx +66 -65
  8. package/Application/Navigator.ts +106 -53
  9. package/Application/StackScreen.tsx +123 -28
  10. package/Application/index.ts +15 -9
  11. package/Application/types.ts +55 -31
  12. package/Application/utils.tsx +36 -6
  13. package/Assets/icon.json +83 -5
  14. package/Assets/icon_bank.json +506 -0
  15. package/Assets/language.json +3 -1
  16. package/Badge/Badge.tsx +56 -0
  17. package/Badge/BadgeDot.tsx +11 -0
  18. package/Badge/BadgeRibbon.tsx +125 -0
  19. package/Badge/Shape.tsx +29 -0
  20. package/Badge/index.tsx +7 -0
  21. package/Badge/styles.ts +58 -0
  22. package/Badge/types.ts +63 -0
  23. package/Button/index.tsx +60 -24
  24. package/Button/styles.ts +19 -0
  25. package/CheckBox/index.tsx +37 -27
  26. package/CheckBox/types.ts +5 -3
  27. package/Consts/colors+spacing+radius.ts +17 -3
  28. package/Icon/index.tsx +14 -9
  29. package/Icon/types.ts +4 -3
  30. package/IconButton/index.tsx +24 -15
  31. package/Image/index.tsx +61 -55
  32. package/Image/styles.ts +1 -9
  33. package/Image/types.ts +5 -1
  34. package/Input/Input.tsx +102 -25
  35. package/Input/InputDropDown.tsx +32 -23
  36. package/Input/InputMoney.tsx +67 -18
  37. package/Input/InputOTP.tsx +194 -148
  38. package/Input/InputSearch.tsx +72 -36
  39. package/Input/InputTextArea.tsx +33 -18
  40. package/Input/common.tsx +25 -16
  41. package/Input/index.tsx +62 -16
  42. package/Input/styles.ts +5 -8
  43. package/Layout/Card.tsx +20 -11
  44. package/Layout/FloatingButton.tsx +168 -0
  45. package/Layout/GridSystem.tsx +1 -2
  46. package/Layout/Item.tsx +0 -1
  47. package/Layout/ItemList.tsx +39 -40
  48. package/Layout/ItemSectionList.tsx +39 -0
  49. package/Layout/Screen.tsx +151 -65
  50. package/Layout/Section.tsx +17 -19
  51. package/Layout/index.ts +2 -1
  52. package/Layout/styles.ts +20 -15
  53. package/Layout/types.ts +2 -5
  54. package/Layout/utils.ts +2 -2
  55. package/Pagination/PaginationNumber.tsx +1 -1
  56. package/Pagination/PaginationScroll.tsx +0 -1
  57. package/Popup/PopupNotify.tsx +105 -37
  58. package/Popup/PopupPromotion.tsx +44 -9
  59. package/Popup/types.ts +12 -9
  60. package/Radio/index.tsx +35 -21
  61. package/Radio/types.ts +5 -3
  62. package/Skeleton/index.tsx +1 -1
  63. package/Switch/index.tsx +32 -19
  64. package/Switch/types.ts +5 -3
  65. package/Tag/index.tsx +2 -4
  66. package/Text/index.tsx +26 -6
  67. package/Text/styles.ts +38 -0
  68. package/Text/types.ts +25 -13
  69. package/Title/index.tsx +172 -63
  70. package/Title/styles.ts +48 -15
  71. package/Title/types.ts +16 -47
  72. package/code-scanner.js +131 -39
  73. package/index.ts +9 -0
  74. package/migrate_typo.js +56 -0
  75. package/package.json +8 -8
  76. package/verify.js +24 -6
  77. package/Application/BottomTab.tsx +0 -65
@@ -0,0 +1,225 @@
1
+ import React, {useContext, useEffect, useRef, useState} from 'react';
2
+ import {
3
+ Animated,
4
+ Dimensions,
5
+ KeyboardAvoidingView,
6
+ Modal,
7
+ PanResponder,
8
+ Platform,
9
+ StyleSheet,
10
+ TouchableOpacity,
11
+ View,
12
+ } from 'react-native';
13
+ import {ApplicationContext} from './index';
14
+ import {BottomSheetParams} from './types';
15
+ import {useSafeAreaInsets} from 'react-native-safe-area-context';
16
+ import {Colors, Radius, Spacing, Styles} from '../Consts';
17
+ import {Text} from '../Text';
18
+ import {Icon} from '../Icon';
19
+
20
+ const BottomSheet: React.FC<BottomSheetParams> = props => {
21
+ const {theme, navigator} = useContext(ApplicationContext);
22
+ const heightDevice = Dimensions.get('screen').height;
23
+ const insets = useSafeAreaInsets();
24
+ const {
25
+ screen: Screen,
26
+ options,
27
+ surface,
28
+ barrierDismissible = false,
29
+ snapPoint = 'content',
30
+ draggable = true,
31
+ dragOnContent = false,
32
+ }: BottomSheetParams = props.route.params;
33
+ const [modalVisible, setModalVisible] = useState(false);
34
+ const animatedHeight = useRef(new Animated.Value(0)).current;
35
+ const pan = useRef(new Animated.ValueXY()).current;
36
+ const panResponder = useRef(
37
+ PanResponder.create({
38
+ onStartShouldSetPanResponder: () => draggable,
39
+ onMoveShouldSetPanResponder: (_, gestureState) => {
40
+ return draggable && dragOnContent && gestureState.dy > 0;
41
+ },
42
+ onPanResponderMove: (e, gestureState) => {
43
+ gestureState.dy > 0 &&
44
+ Animated.event([null, {dy: pan.y}], {useNativeDriver: false})(
45
+ e,
46
+ gestureState,
47
+ );
48
+ },
49
+ onPanResponderRelease: (_, gestureState) => {
50
+ if (gestureState.dy > 100) {
51
+ onDismiss();
52
+ } else {
53
+ Animated.spring(pan, {
54
+ toValue: {x: 0, y: 0},
55
+ useNativeDriver: false,
56
+ }).start();
57
+ }
58
+ },
59
+ }),
60
+ ).current;
61
+ const estimate = useRef(0.9);
62
+
63
+ let backgroundColor = theme.colors.background.default;
64
+ if (surface) {
65
+ backgroundColor = theme.colors.background.surface;
66
+ }
67
+
68
+ /**
69
+ * handle open & emit dismiss event
70
+ */
71
+ useEffect(() => {
72
+ setTimeout(() => {
73
+ const mapSnapPoint: any = {
74
+ '30%': 0.3,
75
+ '50%': 0.5,
76
+ '70%': 0.7,
77
+ '90%': 0.9,
78
+ };
79
+ const point = mapSnapPoint[snapPoint] ?? estimate.current;
80
+ setModalVisible(true);
81
+ Animated.timing(animatedHeight, {
82
+ useNativeDriver: false,
83
+ toValue: heightDevice * point,
84
+ duration: 300,
85
+ }).start();
86
+ }, 100);
87
+
88
+ return () => {
89
+ props.route.params?.onDismiss?.();
90
+ };
91
+ }, []);
92
+
93
+ /**
94
+ * handler dismiss
95
+ */
96
+ const onDismiss = (force = false) => {
97
+ if (barrierDismissible && !force) {
98
+ return;
99
+ }
100
+ Animated.timing(animatedHeight, {
101
+ useNativeDriver: false,
102
+ toValue: 0,
103
+ duration: 200,
104
+ }).start(() => {
105
+ pan.setValue({x: 0, y: 0});
106
+ navigator?.pop();
107
+ });
108
+ };
109
+
110
+ return (
111
+ <>
112
+ <Modal
113
+ transparent
114
+ visible={modalVisible}
115
+ onRequestClose={() => onDismiss()}>
116
+ <KeyboardAvoidingView
117
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
118
+ keyboardVerticalOffset={-20}
119
+ style={Styles.flex}>
120
+ <TouchableOpacity
121
+ style={Styles.flex}
122
+ activeOpacity={1}
123
+ onPress={() => onDismiss()}
124
+ />
125
+ <Animated.View
126
+ {...(dragOnContent && panResponder.panHandlers)}
127
+ style={[
128
+ styles.container,
129
+ {transform: pan.getTranslateTransform()},
130
+ {
131
+ height: animatedHeight,
132
+ backgroundColor,
133
+ },
134
+ ]}>
135
+ {draggable && (
136
+ <View
137
+ {...(!dragOnContent && panResponder.panHandlers)}
138
+ style={styles.draggableContainer}>
139
+ <View style={{height: 16}}>
140
+ <View style={styles.indicator} />
141
+ </View>
142
+ <View
143
+ style={[
144
+ styles.header,
145
+ surface && {
146
+ borderBottomColor: theme.colors.border.default,
147
+ borderBottomWidth: 1,
148
+ },
149
+ ]}>
150
+ <View style={styles.iconButton} />
151
+ <Text
152
+ style={[Styles.flex, {textAlign: 'center'}]}
153
+ numberOfLines={1}
154
+ typography="header_default_bold">
155
+ {options.title ?? ''}
156
+ </Text>
157
+ <TouchableOpacity
158
+ style={styles.iconButton}
159
+ onPress={() => onDismiss(true)}>
160
+ <Icon source="navigation_close" />
161
+ </TouchableOpacity>
162
+ </View>
163
+ </View>
164
+ )}
165
+ <Screen {...props} {...props.route.params} />
166
+ <View style={{height: Math.min(insets.bottom, 21)}} />
167
+ </Animated.View>
168
+ </KeyboardAvoidingView>
169
+ </Modal>
170
+ {snapPoint == 'content' && (
171
+ <View
172
+ style={styles.estimate}
173
+ onLayout={e => {
174
+ estimate.current =
175
+ (e.nativeEvent.layout.height + 68) / heightDevice;
176
+ }}>
177
+ <Screen {...props} {...props.route.params} />
178
+ <View style={{height: Math.min(insets.bottom, 21)}} />
179
+ </View>
180
+ )}
181
+ </>
182
+ );
183
+ };
184
+ export default BottomSheet;
185
+
186
+ const styles = StyleSheet.create({
187
+ container: {
188
+ width: '100%',
189
+ height: 0,
190
+ overflow: 'hidden',
191
+ borderTopLeftRadius: Radius.M,
192
+ borderTopRightRadius: Radius.M,
193
+ },
194
+ draggableContainer: {
195
+ width: '100%',
196
+ alignItems: 'center',
197
+ },
198
+ indicator: {
199
+ width: 40,
200
+ height: 4,
201
+ borderRadius: Radius.S,
202
+ backgroundColor: Colors.black_06,
203
+ position: 'absolute',
204
+ alignSelf: 'center',
205
+ top: Spacing.S,
206
+ },
207
+ header: {
208
+ height: 56,
209
+ flexDirection: 'row',
210
+ alignItems: 'center',
211
+ },
212
+ iconButton: {
213
+ width: 24,
214
+ height: 24,
215
+ alignItems: 'center',
216
+ justifyContent: 'center',
217
+ marginHorizontal: Spacing.M,
218
+ },
219
+ estimate: {
220
+ position: 'absolute',
221
+ top: -9999,
222
+ left: -9999,
223
+ opacity: 0,
224
+ },
225
+ });
@@ -0,0 +1,291 @@
1
+ import {BottomTabBarProps} from '@react-navigation/bottom-tabs';
2
+ import BottomTabBarItem from '@react-navigation/bottom-tabs/src/views/BottomTabItem';
3
+ import {
4
+ CommonActions,
5
+ NavigationContext,
6
+ NavigationRouteContext,
7
+ useLinkBuilder,
8
+ } from '@react-navigation/native';
9
+ import React, {useCallback, useContext} from 'react';
10
+ import {
11
+ Animated,
12
+ Platform,
13
+ StyleSheet,
14
+ TouchableOpacity,
15
+ useWindowDimensions,
16
+ View,
17
+ } from 'react-native';
18
+ import {EdgeInsets, useSafeAreaInsets} from 'react-native-safe-area-context';
19
+ import {Radius} from '../../Consts';
20
+ import {Icon} from '../../Icon';
21
+ import {Text} from '../../Text';
22
+ import {FloatingButtonProps} from '../types';
23
+ import {ApplicationContext} from '../index';
24
+
25
+ type Props = BottomTabBarProps & {
26
+ activeTintColor?: string;
27
+ inactiveTintColor?: string;
28
+ floatingButton?: FloatingButtonProps;
29
+ accessibilityLabel?: string;
30
+ };
31
+
32
+ const getPaddingBottom = (insets: EdgeInsets) => {
33
+ return Math.max(insets.bottom - Platform.select({ios: 4, default: 0}), 0);
34
+ };
35
+
36
+ export default function BottomTabBar({
37
+ state,
38
+ navigation,
39
+ descriptors,
40
+ activeBackgroundColor,
41
+ activeTintColor,
42
+ allowFontScaling,
43
+ inactiveBackgroundColor,
44
+ inactiveTintColor,
45
+ labelStyle,
46
+ iconStyle,
47
+ showLabel,
48
+ style,
49
+ tabStyle,
50
+ floatingButton,
51
+ }: Props) {
52
+ const {theme} = useContext(ApplicationContext);
53
+ const {routes} = state;
54
+ const widthDevice = useWindowDimensions()?.width;
55
+ const useFloating = floatingButton && state.routes.length % 2 === 0;
56
+ const itemWidth = widthDevice / (state.routes.length + (useFloating ? 1 : 0));
57
+ const buildLink = useLinkBuilder();
58
+ const indicatorAnimated = React.useRef(new Animated.Value(0)).current;
59
+ const insets = useSafeAreaInsets();
60
+ const paddingBottom = getPaddingBottom(insets);
61
+ const focusedTab = state?.routes[state.index]?.name || routes[0]?.params;
62
+
63
+ /**
64
+ * render tab item
65
+ */
66
+ const renderTabItem = useCallback(
67
+ (data: any[]) => {
68
+ return data?.map((route, index) => {
69
+ const focused = route.params.name === focusedTab;
70
+ const {options} = descriptors[route?.key] as any;
71
+ const onPress = () => {
72
+ const event = navigation.emit({
73
+ type: 'tabPress',
74
+ target: route.key,
75
+ canPreventDefault: true,
76
+ });
77
+
78
+ if (!focused && !event.defaultPrevented) {
79
+ navigation.dispatch({
80
+ ...CommonActions.navigate(route.name),
81
+ target: state.key,
82
+ });
83
+ }
84
+ };
85
+
86
+ const onLongPress = () => {
87
+ navigation.emit({
88
+ type: 'tabLongPress',
89
+ target: route.key,
90
+ });
91
+ };
92
+
93
+ const label =
94
+ options.tabBarLabel !== undefined
95
+ ? options.tabBarLabel
96
+ : options.title !== undefined
97
+ ? options.title
98
+ : route.name;
99
+
100
+ const accessibilityLabel =
101
+ options.tabBarAccessibilityLabel !== undefined
102
+ ? options.tabBarAccessibilityLabel
103
+ : typeof label === 'string'
104
+ ? `${label}, tab, ${index + 1} of ${routes.length}`
105
+ : undefined;
106
+
107
+ return (
108
+ <NavigationContext.Provider
109
+ key={route.key}
110
+ value={(descriptors as any)[route.key].navigation}>
111
+ <NavigationRouteContext.Provider value={route}>
112
+ <BottomTabBarItem
113
+ route={route}
114
+ focused={focused}
115
+ horizontal={false}
116
+ onPress={onPress}
117
+ onLongPress={onLongPress}
118
+ accessibilityLabel={accessibilityLabel}
119
+ to={buildLink(route.name, route.params)}
120
+ testID={options.tabBarTestID}
121
+ allowFontScaling={allowFontScaling}
122
+ activeTintColor={activeTintColor}
123
+ inactiveTintColor={inactiveTintColor}
124
+ activeBackgroundColor={activeBackgroundColor}
125
+ inactiveBackgroundColor={inactiveBackgroundColor}
126
+ button={options.tabBarButton}
127
+ icon={options.tabBarIcon}
128
+ badge={options.tabBarBadge}
129
+ badgeStyle={options.tabBarBadgeStyle}
130
+ label={label}
131
+ showLabel={showLabel}
132
+ labelStyle={labelStyle}
133
+ iconStyle={iconStyle}
134
+ style={tabStyle}
135
+ />
136
+ </NavigationRouteContext.Provider>
137
+ </NavigationContext.Provider>
138
+ );
139
+ });
140
+ },
141
+ [JSON.stringify(routes), state, descriptors, focusedTab],
142
+ );
143
+
144
+ /**
145
+ * render floating button
146
+ */
147
+ const renderFloatingButton = useCallback(() => {
148
+ const renderFloatingButton = () => {
149
+ if (typeof floatingButton?.icon == 'string') {
150
+ return <Icon source={floatingButton?.icon} size={32} color={'white'} />;
151
+ }
152
+ return floatingButton?.icon;
153
+ };
154
+
155
+ return (
156
+ <View style={{flex: 1}}>
157
+ <TouchableOpacity
158
+ onPress={floatingButton?.onPress}
159
+ style={styles.floatingContainer}>
160
+ <View
161
+ style={{
162
+ ...styles.floatingContent,
163
+ backgroundColor: activeTintColor ?? theme.colors.primary,
164
+ }}>
165
+ {renderFloatingButton()}
166
+ </View>
167
+ <View
168
+ style={{
169
+ top: -6,
170
+ backgroundColor: activeTintColor ?? theme.colors.primary,
171
+ borderRadius: Radius.XL,
172
+ paddingHorizontal: 6,
173
+ }}>
174
+ <Text
175
+ numberOfLines={1}
176
+ typography={'description_xs_regular'}
177
+ color={'white'}>
178
+ {floatingButton?.label}
179
+ </Text>
180
+ </View>
181
+ </TouchableOpacity>
182
+ </View>
183
+ );
184
+ }, [useFloating]);
185
+
186
+ /**
187
+ * animated indicator
188
+ */
189
+ React.useEffect(() => {
190
+ let index = state.index;
191
+ if (useFloating && index > (state.routes.length - 1) / 2) {
192
+ index += 1;
193
+ }
194
+ Animated.timing(indicatorAnimated, {
195
+ toValue: itemWidth * index,
196
+ useNativeDriver: true,
197
+ duration: 200,
198
+ }).start();
199
+ }, [state.index, itemWidth, indicatorAnimated, useFloating]);
200
+
201
+ /**
202
+ * render tab bar
203
+ */
204
+ const renderTabBar = () => {
205
+ if (useFloating) {
206
+ const middleIndex = routes.length / 2;
207
+ const leftArr = routes.slice(0, middleIndex);
208
+ const rightArr = routes.slice(middleIndex);
209
+ return (
210
+ <>
211
+ {renderTabItem(leftArr)}
212
+ {renderFloatingButton()}
213
+ {renderTabItem(rightArr)}
214
+ </>
215
+ );
216
+ }
217
+ return renderTabItem(routes);
218
+ };
219
+
220
+ return (
221
+ <Animated.View
222
+ style={[
223
+ styles.tabBar,
224
+ {
225
+ backgroundColor: theme.colors.background.surface,
226
+ borderTopColor: theme.colors.border.default,
227
+ },
228
+ {
229
+ paddingBottom: paddingBottom,
230
+ paddingHorizontal: Math.max(insets.left, insets.right),
231
+ },
232
+ style,
233
+ ]}
234
+ pointerEvents={'auto'}>
235
+ <View style={styles.content}>
236
+ {useFloating && floatingButton?.container}
237
+ <Animated.View
238
+ style={{
239
+ width: itemWidth,
240
+ position: 'absolute',
241
+ top: 0,
242
+ transform: [{translateX: indicatorAnimated}],
243
+ }}>
244
+ <View
245
+ style={[
246
+ styles.indicator,
247
+ {backgroundColor: activeTintColor ?? theme.colors.primary},
248
+ ]}
249
+ />
250
+ </Animated.View>
251
+ {renderTabBar()}
252
+ </View>
253
+ </Animated.View>
254
+ );
255
+ }
256
+
257
+ const styles = StyleSheet.create({
258
+ content: {
259
+ flex: 1,
260
+ flexDirection: 'row',
261
+ },
262
+ tabBar: {
263
+ left: 0,
264
+ right: 0,
265
+ bottom: 0,
266
+ borderTopWidth: StyleSheet.hairlineWidth,
267
+ elevation: 8,
268
+ },
269
+ floatingContainer: {
270
+ position: 'absolute',
271
+ alignItems: 'center',
272
+ left: 0,
273
+ right: 0,
274
+ bottom: Platform.OS === 'ios' ? -6 : 0,
275
+ },
276
+ floatingContent: {
277
+ alignItems: 'center',
278
+ justifyContent: 'center',
279
+ height: 48,
280
+ width: 48,
281
+ borderRadius: 24,
282
+ top: -10,
283
+ },
284
+ indicator: {
285
+ height: 2,
286
+ width: 44,
287
+ alignSelf: 'center',
288
+ borderBottomLeftRadius: 1,
289
+ borderBottomRightRadius: 1,
290
+ },
291
+ });
@@ -0,0 +1,138 @@
1
+ import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
2
+ import {NavigationContainer} from '@react-navigation/native';
3
+ import {createStackNavigator} from '@react-navigation/stack';
4
+ import React, {useContext, useEffect} from 'react';
5
+ import {Platform} from 'react-native';
6
+ import {useSafeAreaInsets} from 'react-native-safe-area-context';
7
+ import {Colors} from '../../Consts';
8
+ import {Icon} from '../../Icon';
9
+ import {Text} from '../../Text';
10
+ import {ApplicationContext} from '../index';
11
+ import StackScreen from '../StackScreen';
12
+ import {BottomTabProps, NavigationScreenProps} from '../types';
13
+ import {getOptions, getStackOptions} from '../utils';
14
+ import BottomTabBar from './BottomTabBar';
15
+
16
+ const Tab = createBottomTabNavigator();
17
+ const Stack = createStackNavigator();
18
+
19
+ const TabScreen: React.FC<NavigationScreenProps> = ({route}) => {
20
+ const {theme} = useContext(ApplicationContext);
21
+ let options = {};
22
+ if (route.params?.options) {
23
+ options = getOptions(route.params?.options, theme);
24
+ }
25
+ if (route.params?.nested) {
26
+ return (
27
+ <Stack.Navigator>
28
+ <Stack.Screen
29
+ name="Stack"
30
+ component={StackScreen}
31
+ initialParams={{screen: route.params?.screen, options}}
32
+ options={{...getStackOptions()}}
33
+ />
34
+ </Stack.Navigator>
35
+ );
36
+ }
37
+ return (
38
+ <NavigationContainer independent={true}>
39
+ <Stack.Navigator>
40
+ <Stack.Screen
41
+ name="Stack"
42
+ component={StackScreen}
43
+ initialParams={{screen: route.params?.screen, options}}
44
+ options={{...getStackOptions(), ...route.params?.options}}
45
+ />
46
+ </Stack.Navigator>
47
+ </NavigationContainer>
48
+ );
49
+ };
50
+
51
+ const BottomTab: React.FC<BottomTabProps> = ({
52
+ nested,
53
+ tabs,
54
+ listeners,
55
+ navigation,
56
+ initialRouteName,
57
+ floatingButton,
58
+ }) => {
59
+ const {theme} = useContext(ApplicationContext);
60
+ const insets = useSafeAreaInsets();
61
+
62
+ useEffect(() => {
63
+ navigation?.setOptions({
64
+ headerShown: false,
65
+ headerTransparent: true,
66
+ headerBackground: undefined,
67
+ });
68
+ }, []);
69
+
70
+ return (
71
+ <Tab.Navigator
72
+ initialRouteName={initialRouteName}
73
+ backBehavior={'firstRoute'}
74
+ tabBar={props => (
75
+ <BottomTabBar
76
+ {...props}
77
+ accessibilityLabel={'BottomTabBar'}
78
+ floatingButton={floatingButton}
79
+ />
80
+ )}
81
+ tabBarOptions={{
82
+ style: {
83
+ height: 64 + Math.min(insets.bottom, 21),
84
+ },
85
+ tabStyle: {
86
+ height: 68,
87
+ paddingVertical: 10,
88
+ },
89
+ }}>
90
+ {tabs.map(item => {
91
+ const isNum = !isNaN(parseInt(`${item?.badgeLabel}`));
92
+ const isDot = typeof item?.badgeLabel == 'string' && !item?.badgeLabel;
93
+ return (
94
+ <Tab.Screen
95
+ key={item.name}
96
+ name={item.name}
97
+ component={TabScreen}
98
+ initialParams={{...item, nested}}
99
+ listeners={listeners}
100
+ options={{
101
+ tabBarLabel: ({color, focused}) => {
102
+ return (
103
+ <Text
104
+ numberOfLines={1}
105
+ typography={
106
+ focused ? 'label_xs_medium' : 'description_xs_regular'
107
+ }
108
+ color={color}
109
+ style={{width: '90%', textAlign: 'center'}}>
110
+ {item.label}
111
+ </Text>
112
+ );
113
+ },
114
+ tabBarBadge: item?.badgeLabel,
115
+ tabBarBadgeStyle: {
116
+ borderColor: 'white',
117
+ borderWidth: 2,
118
+ fontSize: 10,
119
+ lineHeight: Platform.OS === 'android' ? 17 : 14,
120
+ fontWeight: 'bold',
121
+ alignSelf: 'center',
122
+ fontFamily: `${theme.font}-Bold`,
123
+ left: isDot ? 8 : 4,
124
+ top: isDot ? -2 : -6,
125
+ backgroundColor: isNum ? Colors.red_03 : Colors.orange_03,
126
+ },
127
+ tabBarIcon: ({color}) => (
128
+ <Icon source={item.icon} color={color} size={28} />
129
+ ),
130
+ }}
131
+ />
132
+ );
133
+ })}
134
+ </Tab.Navigator>
135
+ );
136
+ };
137
+
138
+ export default BottomTab;