@momo-kits/foundation 0.168.1-rn862.1 → 0.169.2

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 (109) hide show
  1. package/Application/BottomSheet.hooks.ts +195 -0
  2. package/Application/BottomSheet.parts.tsx +147 -0
  3. package/Application/BottomSheet.styles.ts +43 -0
  4. package/Application/BottomSheet.tsx +76 -279
  5. package/Application/BottomTab/ActiveIndicator.tsx +90 -0
  6. package/Application/BottomTab/BottomTabBar.styles.ts +65 -0
  7. package/Application/BottomTab/BottomTabBar.tsx +229 -276
  8. package/Application/BottomTab/BottomTabInset.tsx +14 -0
  9. package/Application/BottomTab/CurvedContainer.tsx +153 -0
  10. package/Application/BottomTab/CustomBottomTabItem.tsx +132 -108
  11. package/Application/BottomTab/DuoBarCompression.tsx +4 -12
  12. package/Application/BottomTab/DuoRailTabBar.tsx +227 -0
  13. package/Application/BottomTab/DuoTabBar.styles.ts +81 -0
  14. package/Application/BottomTab/DuoTabBar.tsx +29 -495
  15. package/Application/BottomTab/TabBarIcon.tsx +160 -72
  16. package/Application/BottomTab/TabScreen.tsx +164 -0
  17. package/Application/BottomTab/index.tsx +84 -351
  18. package/Application/BottomTab/metrics.ts +164 -0
  19. package/Application/BottomTab/tabItems.ts +46 -0
  20. package/Application/BottomTab/usePressLift.ts +32 -0
  21. package/Application/Components/DuoGlassBackground.tsx +7 -9
  22. package/Application/Components/HeaderAnimated.tsx +1 -1
  23. package/Application/Components/HeaderBackground.tsx +1 -1
  24. package/Application/Components/HeaderExtendHeader.tsx +17 -11
  25. package/Application/Components/HeaderRight.styles.ts +144 -0
  26. package/Application/Components/HeaderRight.tsx +6 -528
  27. package/Application/Components/HeaderTitle.shared.ts +43 -0
  28. package/Application/Components/HeaderTitle.styles.ts +125 -0
  29. package/Application/Components/HeaderTitle.tsx +12 -576
  30. package/Application/Components/HeaderToolkitAction.tsx +208 -0
  31. package/Application/Components/NavigationButton.tsx +9 -6
  32. package/Application/Components/SearchHeader.tsx +1 -1
  33. package/Application/Components/SwipeBackHaptic.tsx +95 -0
  34. package/Application/Components/TitleJourney.tsx +110 -0
  35. package/Application/Components/TitleLocation.tsx +73 -0
  36. package/Application/Components/TitleUser.tsx +304 -0
  37. package/Application/Components/useToolkitActions.tsx +233 -0
  38. package/Application/ModalIconButton.tsx +66 -0
  39. package/Application/ModalScreen.tsx +39 -18
  40. package/Application/Navigation.ts +1 -1
  41. package/Application/NavigationContainer.tsx +7 -8
  42. package/Application/StackScreen.tracking.ts +158 -0
  43. package/Application/StackScreen.tsx +10 -137
  44. package/Application/WidgetContainer.tsx +10 -3
  45. package/Application/__tests__/modalLayout.test.ts +128 -0
  46. package/Application/hooks.ts +137 -0
  47. package/Application/index.ts +13 -1
  48. package/Application/modalLayout.ts +177 -0
  49. package/Application/navigationOptions.tsx +217 -0
  50. package/Application/types.header.ts +116 -0
  51. package/Application/types.tabs.ts +48 -0
  52. package/Application/types.theme.ts +87 -0
  53. package/Application/types.ts +12 -244
  54. package/Application/utils.ts +107 -0
  55. package/Badge/BadgeRibbon.tsx +3 -3
  56. package/Badge/styles.ts +2 -0
  57. package/Button/index.tsx +16 -136
  58. package/Button/styles.ts +1 -1
  59. package/Button/utils.ts +139 -0
  60. package/CheckBox/index.tsx +1 -6
  61. package/Divider/DashDivider.tsx +10 -11
  62. package/Divider/index.tsx +18 -8
  63. package/Image/index.tsx +1 -46
  64. package/Input/Input.tsx +7 -8
  65. package/Input/InputDropDown.tsx +3 -4
  66. package/Input/InputMoney.tsx +38 -164
  67. package/Input/InputMoneyView.tsx +187 -0
  68. package/Input/InputOTP.tsx +1 -1
  69. package/Input/InputPhoneNumber.tsx +5 -1
  70. package/Input/InputSearch.tsx +2 -108
  71. package/Input/InputTextArea.tsx +1 -5
  72. package/Input/SearchTextTyping.tsx +104 -0
  73. package/Input/common.tsx +4 -4
  74. package/Input/moneyUtils.ts +17 -0
  75. package/Input/styles.ts +26 -2
  76. package/Layout/FloatingButton.tsx +22 -21
  77. package/Layout/ReservedRegions.tsx +21 -4
  78. package/Layout/Screen.tsx +74 -670
  79. package/Layout/Section.tsx +1 -2
  80. package/Layout/index.ts +10 -4
  81. package/Layout/layoutMetrics.ts +58 -0
  82. package/Layout/layoutTypes.ts +86 -0
  83. package/Layout/screenContent.tsx +90 -0
  84. package/Layout/screenFooter.tsx +53 -0
  85. package/Layout/screenHeader.tsx +315 -0
  86. package/Layout/screenQuickReturn.tsx +67 -0
  87. package/Layout/screenScroll.ts +110 -0
  88. package/Layout/screenTypes.ts +134 -0
  89. package/Layout/styles.ts +2 -0
  90. package/Layout/utils.ts +10 -144
  91. package/Loader/DotLoader.tsx +7 -3
  92. package/Loader/ProgressBar.tsx +2 -6
  93. package/Loader/Spinner.tsx +7 -3
  94. package/Loader/styles.ts +4 -0
  95. package/Popup/PopupNotify.tsx +70 -64
  96. package/Popup/PopupPromotion.tsx +5 -4
  97. package/Popup/types.ts +7 -0
  98. package/Popup/utils.ts +46 -24
  99. package/Radio/index.tsx +2 -10
  100. package/Radio/styles.ts +6 -0
  101. package/Skeleton/index.tsx +1 -9
  102. package/Skeleton/styles.ts +1 -0
  103. package/Tag/index.tsx +3 -3
  104. package/Text/index.tsx +11 -2
  105. package/Title/index.tsx +2 -9
  106. package/Title/styles.ts +6 -0
  107. package/package.json +21 -22
  108. package/Application/device.ts +0 -29
  109. package/Application/utils.tsx +0 -497
@@ -1,69 +1,50 @@
1
- import React, {
2
- useCallback,
3
- useContext,
4
- useEffect,
5
- useRef,
6
- useState,
7
- } from 'react';
1
+ import React, { useCallback, useContext, useEffect, useRef } from 'react';
8
2
  import {
9
- BackHandler,
10
- Dimensions,
11
- Keyboard,
12
3
  KeyboardAvoidingView,
13
- KeyboardEvent,
14
- LayoutAnimation,
15
4
  Modal,
16
5
  PanResponder,
17
6
  Platform,
18
7
  Pressable,
19
8
  StyleSheet,
20
- TouchableOpacity,
21
- useWindowDimensions,
22
9
  View,
23
10
  } from 'react-native';
24
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
25
11
  import { ApplicationContext, MiniAppContext } from '../Context';
26
12
  import { BottomSheetParams } from './types';
27
- import { Colors, Radius, Spacing, Styles } from '../Consts';
28
- import { Text } from '../Text';
29
- import { Icon } from '../Icon';
30
- import { useHeaderHeight } from '@react-navigation/elements';
31
13
  import Animated, {
32
14
  Easing,
33
- Extrapolation,
34
- interpolate,
35
- useAnimatedStyle,
36
15
  useSharedValue,
37
16
  withSpring,
38
17
  withTiming,
39
18
  } from 'react-native-reanimated';
40
19
  import { scheduleOnRN } from 'react-native-worklets';
41
- import layoutStyles from '../Layout/styles';
42
- import { getDuoTopInset, isIPhoneDuo } from './device';
43
-
44
- // iPhone Duo inner display: center the sheet instead of spanning the width
45
- const DUO_REGULAR_MIN_WIDTH = 600;
20
+ import { DUO_ORIENTATIONS, styles } from './BottomSheet.styles';
21
+ import {
22
+ useBottomSheetLayout,
23
+ useDuoKeyboardLift,
24
+ useSheetDismiss,
25
+ useSheetAnimatedStyles,
26
+ } from './BottomSheet.hooks';
27
+ import {
28
+ getSheetStyle,
29
+ renderSheetFooter,
30
+ renderSheetHeader,
31
+ } from './BottomSheet.parts';
46
32
 
47
33
  const BottomSheet: React.FC<BottomSheetParams> = props => {
48
34
  const { theme, navigator } = useContext(ApplicationContext);
49
35
  const context = useContext<any>(MiniAppContext);
50
- const windowSize = useWindowDimensions();
51
- const isDuo = isIPhoneDuo();
52
- const isDuoCentered = isDuo && windowSize.width >= DUO_REGULAR_MIN_WIDTH;
53
- const isPortrait = windowSize.height > windowSize.width;
54
- // 'screen' is the outer display on iPhone Duo, even when unfolded
55
- const screenHeight = Dimensions.get('screen').height;
56
- const heightDevice = isDuo ? windowSize.height : screenHeight;
36
+ const {
37
+ windowSize,
38
+ isDuo,
39
+ isFoldable,
40
+ placement,
41
+ sheet,
42
+ scrimOpacity,
43
+ heightDevice,
44
+ bottomInset,
45
+ keyboardOffset,
46
+ } = useBottomSheetLayout();
57
47
  const action = useRef<undefined | string>(undefined);
58
- const insets = useSafeAreaInsets();
59
- const heightHeader = useHeaderHeight();
60
- const topGap = Math.max(
61
- getDuoTopInset(insets, windowSize.width, windowSize.height),
62
- Spacing.XL,
63
- );
64
- const isIOS = Platform.OS === 'ios';
65
- const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
66
- const keyboardOffset = heightHeader - bottomInset;
67
48
 
68
49
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
69
50
 
@@ -83,43 +64,15 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
83
64
  }: BottomSheetParams = props.route.params;
84
65
 
85
66
  const translateY = useSharedValue(heightDevice);
86
- const [keyboardLift, setKeyboardLift] = useState(0);
87
-
88
- // KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
89
- // so lift it here and shrink maxHeight by the same amount instead
90
- useEffect(() => {
91
- if (!isDuo || !useKeyboardAvoidingView) {
92
- return;
93
- }
94
- const onKeyboardChange = (e: KeyboardEvent) => {
95
- const overlap = windowSize.height - e.endCoordinates.screenY;
96
- const lift = Math.max(
97
- overlap - bottomInset - (keyboardVerticalOffset ?? 0),
98
- 0,
99
- );
100
- if (e.duration) {
101
- LayoutAnimation.configureNext({
102
- duration: e.duration,
103
- update: {
104
- duration: e.duration,
105
- type: LayoutAnimation.Types[e.easing] || 'keyboard',
106
- },
107
- });
108
- }
109
- setKeyboardLift(lift);
110
- };
111
- const subscriptions = [
112
- Keyboard.addListener('keyboardWillChangeFrame', onKeyboardChange),
113
- Keyboard.addListener('keyboardWillHide', onKeyboardChange),
114
- ];
115
- return () => subscriptions.forEach(sub => sub.remove());
116
- }, [
117
- bottomInset,
67
+ const onDismissRef = useRef(props.route.params?.onDismiss);
68
+ onDismissRef.current = props.route.params?.onDismiss;
69
+ const keyboardLift = useDuoKeyboardLift(
118
70
  isDuo,
119
- keyboardVerticalOffset,
120
71
  useKeyboardAvoidingView,
121
72
  windowSize.height,
122
- ]);
73
+ bottomInset,
74
+ keyboardVerticalOffset,
75
+ );
123
76
 
124
77
  const openAnimation = useCallback(() => {
125
78
  translateY.value = withTiming(0, {
@@ -179,145 +132,46 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
179
132
  }
180
133
 
181
134
  /**
182
- * emit dismiss event
135
+ * emit dismiss event, once: folding or rotating iPhone Duo changes the
136
+ * window height while the sheet stays open
183
137
  */
184
138
  useEffect(() => {
185
- translateY.value = heightDevice;
186
139
  openAnimation();
187
140
  return () => {
188
- props.route.params?.onDismiss?.(action.current);
141
+ onDismissRef.current?.(action.current);
189
142
  };
190
- }, [heightDevice, openAnimation, props.route.params, translateY]);
191
-
192
- /**
193
- * handler dismiss
194
- */
195
- const onDismiss = useCallback(
196
- (force = false, callback?: () => void) => {
197
- if (barrierDismissible && !force) {
198
- return;
199
- }
200
- closeAnimation(() => {
201
- navigator?.pop();
202
- scheduleOnRN(() => {
203
- callback?.();
204
- });
205
- });
206
- },
207
- [barrierDismissible, closeAnimation, navigator],
208
- );
209
-
210
- /**
211
- * on request close
212
- */
213
- const onRequestClose = useCallback(
214
- (callback?: () => void) => {
215
- onDismiss(true, callback);
216
- },
217
- [onDismiss],
218
- );
219
-
220
- useEffect(() => {
221
- const backHandler = BackHandler.addEventListener(
222
- 'hardwareBackPress',
223
- () => {
224
- onDismiss();
225
- return true;
226
- },
227
- );
228
- return () => backHandler.remove();
229
- }, [barrierDismissible, onDismiss]);
143
+ }, [openAnimation]);
144
+
145
+ const { onDismiss, onRequestClose, onPressBack, onPressClose } =
146
+ useSheetDismiss({
147
+ barrierDismissible,
148
+ closeAnimation,
149
+ navigator,
150
+ leftOptions,
151
+ action,
152
+ });
230
153
 
231
- /**
232
- * render header
233
- */
234
- const renderHeader = useCallback(() => {
235
- return (
236
- <View {...panResponder.panHandlers}>
237
- <View style={{ height: 16 }}>
238
- <View style={styles.indicator} />
239
- </View>
240
- <View
241
- style={[
242
- styles.header,
243
- useDivider && {
244
- borderBottomColor: theme.colors.border.default,
245
- borderBottomWidth: 1,
246
- },
247
- ]}
248
- >
249
- {leftOptions?.onPressIconLeft ? (
250
- <TouchableOpacity
251
- style={styles.iconButton}
252
- onPress={() => {
253
- onDismiss(true, leftOptions.onPressIconLeft);
254
- }}
255
- >
256
- <Icon source={leftOptions.iconLeft ?? 'ic_back'} />
257
- </TouchableOpacity>
258
- ) : null}
154
+ const showBack = !!leftOptions?.onPressIconLeft;
155
+ const iconBack = leftOptions?.iconLeft ?? 'ic_back';
259
156
 
260
- {options.header ? (
261
- <View style={Styles.flex}>{options.header}</View>
262
- ) : (
263
- <>
264
- {!leftOptions ? <View style={styles.iconButton} /> : null}
265
- <Text
266
- style={[Styles.flex, { textAlign: 'center' }]}
267
- numberOfLines={1}
268
- typography="header_default_bold"
269
- >
270
- {options.title ?? ''}
271
- </Text>
272
- </>
273
- )}
274
- <TouchableOpacity
275
- style={styles.iconButton}
276
- onPress={() => {
277
- action.current = 'icon_close';
278
- onDismiss(true);
279
- }}
280
- >
281
- <Icon source="navigation_close" />
282
- </TouchableOpacity>
283
- </View>
284
- </View>
285
- );
286
- }, [
287
- onDismiss,
288
- leftOptions,
289
- options.header,
290
- options.title,
291
- panResponder.panHandlers,
292
- theme.colors.border.default,
157
+ const headerParams = {
158
+ panHandlers: panResponder.panHandlers,
293
159
  useDivider,
294
- ]);
295
-
296
- const animatedStyle = useAnimatedStyle(() => {
297
- return {
298
- transform: [{ translateY: translateY.value }],
299
- };
300
- });
301
-
302
- const animatedOverlayStyle = useAnimatedStyle(() => {
303
- return {
304
- opacity: interpolate(
305
- translateY.value,
306
- [0, heightDevice],
307
- [1, 0],
308
- Extrapolation.CLAMP,
309
- ),
310
- };
311
- });
312
-
313
- // landscape side buttons span the full height, so inset the content; in
314
- // portrait the side status bar only covers the top corner
315
- const needsSideInset = isDuo && !isDuoCentered && !isPortrait;
316
- const sideInsetStyle = needsSideInset && {
317
- paddingLeft: insets.left,
318
- paddingRight: insets.right,
160
+ theme,
161
+ showBack,
162
+ onPressBack,
163
+ onPressClose,
164
+ iconBack,
165
+ options,
166
+ leftOptions,
319
167
  };
320
168
 
169
+ const { animatedStyle, animatedOverlayStyle } = useSheetAnimatedStyles(
170
+ translateY,
171
+ heightDevice,
172
+ scrimOpacity,
173
+ );
174
+
321
175
  const screen = (
322
176
  <Screen
323
177
  {...props}
@@ -333,8 +187,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
333
187
  onRequestClose={() => {
334
188
  onDismiss();
335
189
  }}
336
- style={StyleSheet.absoluteFill}
190
+ style={StyleSheet.absoluteFillObject}
337
191
  isModalKit={true}
192
+ supportedOrientations={isDuo ? DUO_ORIENTATIONS : undefined}
338
193
  >
339
194
  <KeyboardAvoidingView
340
195
  behavior={Platform.select({
@@ -347,7 +202,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
347
202
  style={styles.container}
348
203
  >
349
204
  <Pressable
350
- style={StyleSheet.absoluteFill}
205
+ style={StyleSheet.absoluteFillObject}
351
206
  onPress={() => {
352
207
  action.current = 'touch';
353
208
  onDismiss();
@@ -359,54 +214,30 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
359
214
  <Animated.View
360
215
  style={[
361
216
  animatedStyle,
362
- {
217
+ getSheetStyle({
363
218
  backgroundColor,
364
- borderTopLeftRadius: Radius.M,
365
- borderTopRightRadius: Radius.M,
366
- overflow: 'hidden',
367
- },
368
- isDuo && {
369
- maxHeight: windowSize.height - topGap - keyboardLift,
370
- marginBottom: keyboardLift,
371
- },
372
- isDuoCentered && {
373
- // keeps the portrait width in landscape, like system sheets
374
- width: Math.min(
375
- Math.min(windowSize.width, windowSize.height) - Spacing.L * 2,
376
- windowSize.width -
377
- Math.max(insets.left, insets.right, Spacing.L) * 2,
378
- ),
379
- alignSelf: 'center',
380
- },
381
- showBaseLineDebug && styles.debugBaseLine,
219
+ isDuo,
220
+ isFoldable,
221
+ placement,
222
+ sheet,
223
+ keyboardLift,
224
+ showBaseLineDebug,
225
+ }),
382
226
  ]}
383
227
  >
384
- {isDuo ? (
228
+ {isFoldable ? (
385
229
  // content shrinks so a tall sheet keeps its header and footer
386
- <View style={[styles.shrink, sideInsetStyle]}>
387
- {renderHeader()}
230
+ <View style={styles.shrink}>
231
+ {renderSheetHeader(headerParams)}
388
232
  <View style={styles.shrink}>{screen}</View>
389
233
  </View>
390
234
  ) : (
391
235
  <>
392
- {renderHeader()}
236
+ {renderSheetHeader(headerParams)}
393
237
  {screen}
394
238
  </>
395
239
  )}
396
- {footerComponent && (
397
- <View
398
- style={[
399
- layoutStyles.shadow,
400
- {
401
- paddingBottom: Spacing.S,
402
- backgroundColor: theme.colors.background.surface,
403
- },
404
- sideInsetStyle,
405
- ]}
406
- >
407
- {footerComponent}
408
- </View>
409
- )}
240
+ {renderSheetFooter({ footerComponent, theme })}
410
241
  {useBottomInset && (
411
242
  <View
412
243
  style={{
@@ -421,38 +252,4 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
421
252
  );
422
253
  };
423
254
 
424
- const styles = StyleSheet.create({
425
- container: {
426
- flex: 1,
427
- justifyContent: 'flex-end',
428
- },
429
- overlay: {
430
- flex: 1,
431
- backgroundColor: 'rgba(0, 0, 0, 0.6)',
432
- },
433
- indicator: {
434
- width: 40,
435
- height: 4,
436
- borderRadius: Radius.S,
437
- backgroundColor: Colors.black_06,
438
- position: 'absolute',
439
- alignSelf: 'center',
440
- top: Spacing.S,
441
- },
442
- header: {
443
- height: 56,
444
- flexDirection: 'row',
445
- alignItems: 'center',
446
- },
447
- iconButton: {
448
- width: 24,
449
- height: 24,
450
- alignItems: 'center',
451
- justifyContent: 'center',
452
- marginHorizontal: Spacing.M,
453
- },
454
- shrink: { flexShrink: 1 },
455
- debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
456
- });
457
-
458
255
  export default BottomSheet;
@@ -0,0 +1,90 @@
1
+ import React, { useContext, useEffect, useRef } from 'react';
2
+ import { StyleProp, StyleSheet, ViewStyle } from 'react-native';
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ withTiming,
7
+ } from 'react-native-reanimated';
8
+ import { ApplicationContext } from '../../Context';
9
+ import { getIndicatorShadow, TabBarMotion } from './metrics';
10
+
11
+ type Props = {
12
+ axis: 'x' | 'y';
13
+ // position of the selected item along the axis, undefined until it is measured
14
+ offset?: number;
15
+ // horizontal bar: width of the selected item
16
+ extent?: number;
17
+ radius: number;
18
+ style: StyleProp<ViewStyle>;
19
+ };
20
+
21
+ /**
22
+ * one active indicator per bar (Background/tonal with its inner shadows) sliding to the
23
+ * selected item; it lands in place on the first measure and slides from then on
24
+ */
25
+ const ActiveIndicator: React.FC<Props> = ({
26
+ axis,
27
+ offset,
28
+ extent,
29
+ radius,
30
+ style,
31
+ }) => {
32
+ const { theme } = useContext(ApplicationContext);
33
+ const position = useSharedValue(offset ?? 0);
34
+ const size = useSharedValue(extent ?? 0);
35
+ const visible = useSharedValue(offset === undefined ? 0 : 1);
36
+ const placed = useRef(offset !== undefined);
37
+
38
+ useEffect(() => {
39
+ if (offset === undefined) {
40
+ return;
41
+ }
42
+ size.value = extent ?? 0;
43
+ if (!placed.current) {
44
+ placed.current = true;
45
+ position.value = offset;
46
+ visible.value = 1;
47
+ return;
48
+ }
49
+ position.value = withTiming(offset, TabBarMotion.indicator);
50
+ }, [offset, extent, position, size, visible]);
51
+
52
+ const animatedStyle = useAnimatedStyle(() =>
53
+ axis === 'x'
54
+ ? {
55
+ opacity: visible.value,
56
+ width: size.value,
57
+ transform: [{ translateX: position.value }],
58
+ }
59
+ : {
60
+ opacity: visible.value,
61
+ transform: [{ translateY: position.value }],
62
+ },
63
+ );
64
+
65
+ return (
66
+ <Animated.View
67
+ pointerEvents={'none'}
68
+ style={[
69
+ styles.indicator,
70
+ {
71
+ borderRadius: radius,
72
+ backgroundColor: theme.colors.background.tonal,
73
+ boxShadow: getIndicatorShadow(theme.colors.primary),
74
+ },
75
+ style,
76
+ animatedStyle,
77
+ ]}
78
+ />
79
+ );
80
+ };
81
+
82
+ const styles = StyleSheet.create({
83
+ indicator: {
84
+ position: 'absolute',
85
+ left: 0,
86
+ top: 0,
87
+ },
88
+ });
89
+
90
+ export { ActiveIndicator };
@@ -0,0 +1,65 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors, Spacing } from '../../Consts';
3
+ import { CTA_SHADOW, TabBarMetrics } from './metrics';
4
+
5
+ export const styles = StyleSheet.create({
6
+ bar: {
7
+ position: 'absolute',
8
+ left: 0,
9
+ right: 0,
10
+ bottom: 0,
11
+ alignItems: 'center',
12
+ },
13
+ // transparent strip that keeps the rising center action touchable
14
+ barCtaRise: {
15
+ paddingTop: TabBarMetrics.ctaLift,
16
+ },
17
+ container: {
18
+ height: TabBarMetrics.height,
19
+ },
20
+ content: {
21
+ flex: 1,
22
+ flexDirection: 'row',
23
+ alignItems: 'center',
24
+ gap: TabBarMetrics.itemGap,
25
+ paddingVertical: TabBarMetrics.paddingVertical,
26
+ paddingHorizontal: TabBarMetrics.paddingHorizontal,
27
+ },
28
+ indicator: {
29
+ height: TabBarMetrics.indicatorHeight,
30
+ },
31
+ ctaSlot: {
32
+ width: TabBarMetrics.ctaSlotWidth,
33
+ alignSelf: 'stretch',
34
+ },
35
+ // the button rises ctaLift above the container top, the label ends on the item baseline
36
+ cta: {
37
+ position: 'absolute',
38
+ left: 0,
39
+ right: 0,
40
+ top: -TabBarMetrics.ctaLift - TabBarMetrics.paddingVertical,
41
+ alignItems: 'center',
42
+ gap: TabBarMetrics.ctaLabelGap,
43
+ },
44
+ ctaButton: {
45
+ boxShadow: CTA_SHADOW,
46
+ width: TabBarMetrics.ctaSize,
47
+ height: TabBarMetrics.ctaSize,
48
+ borderRadius: TabBarMetrics.ctaSize / 2,
49
+ alignItems: 'center',
50
+ justifyContent: 'center',
51
+ },
52
+ // custom label content over the label text, centered and unclipped
53
+ ctaLabelOverlay: {
54
+ ...StyleSheet.absoluteFillObject,
55
+ alignItems: 'center',
56
+ justifyContent: 'center',
57
+ },
58
+ ctaLabel: {
59
+ maxWidth: TabBarMetrics.ctaSlotWidth,
60
+ minHeight: TabBarMetrics.ctaLabelHeight,
61
+ paddingHorizontal: Spacing.XXS,
62
+ justifyContent: 'center',
63
+ },
64
+ debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
65
+ });