@momo-kits/foundation 0.168.1-beta.34 → 0.168.1-beta.35

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 (86) hide show
  1. package/Application/BottomSheet.hooks.ts +215 -0
  2. package/Application/BottomSheet.parts.tsx +231 -0
  3. package/Application/BottomSheet.styles.ts +59 -0
  4. package/Application/BottomTab/ActiveIndicator.tsx +90 -0
  5. package/Application/BottomTab/BottomTabBar.tsx +274 -253
  6. package/Application/BottomTab/BottomTabInset.tsx +14 -0
  7. package/Application/BottomTab/CurvedContainer.tsx +153 -0
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +132 -108
  9. package/Application/BottomTab/DuoBarCompression.tsx +4 -12
  10. package/Application/BottomTab/DuoTabBar.tsx +218 -431
  11. package/Application/BottomTab/TabBarIcon.tsx +160 -72
  12. package/Application/BottomTab/index.tsx +131 -241
  13. package/Application/BottomTab/metrics.ts +164 -0
  14. package/Application/BottomTab/tabItems.ts +46 -0
  15. package/Application/BottomTab/usePressLift.ts +32 -0
  16. package/Application/Components/DuoGlassBackground.tsx +2 -1
  17. package/Application/Components/HeaderAnimated.tsx +1 -1
  18. package/Application/Components/HeaderBackground.tsx +1 -1
  19. package/Application/Components/HeaderExtendHeader.tsx +17 -11
  20. package/Application/Components/HeaderRight.styles.ts +144 -0
  21. package/Application/Components/HeaderRight.tsx +6 -528
  22. package/Application/Components/HeaderTitle.shared.ts +43 -0
  23. package/Application/Components/HeaderTitle.styles.ts +125 -0
  24. package/Application/Components/HeaderTitle.tsx +12 -576
  25. package/Application/Components/HeaderToolkitAction.tsx +208 -0
  26. package/Application/Components/NavigationButton.tsx +9 -6
  27. package/Application/Components/SearchHeader.tsx +1 -1
  28. package/Application/Components/SwipeBackHaptic.tsx +95 -0
  29. package/Application/Components/TitleJourney.tsx +110 -0
  30. package/Application/Components/TitleLocation.tsx +73 -0
  31. package/Application/Components/TitleUser.tsx +304 -0
  32. package/Application/Components/useToolkitActions.tsx +233 -0
  33. package/Application/Navigation.ts +1 -1
  34. package/Application/NavigationContainer.tsx +7 -8
  35. package/Application/StackScreen.tracking.ts +158 -0
  36. package/Application/StackScreen.tsx +10 -137
  37. package/Application/WidgetContainer.tsx +10 -3
  38. package/Application/hooks.ts +137 -0
  39. package/Application/index.ts +3 -1
  40. package/Application/navigationOptions.tsx +217 -0
  41. package/Application/types.header.ts +116 -0
  42. package/Application/types.tabs.ts +43 -0
  43. package/Application/types.theme.ts +87 -0
  44. package/Application/types.ts +5 -2
  45. package/Application/utils.ts +143 -0
  46. package/Button/index.tsx +16 -136
  47. package/Button/styles.ts +1 -1
  48. package/Button/utils.ts +139 -0
  49. package/CheckBox/index.tsx +1 -6
  50. package/Divider/DashDivider.tsx +10 -11
  51. package/Divider/index.tsx +17 -7
  52. package/Input/Input.tsx +7 -8
  53. package/Input/InputDropDown.tsx +3 -4
  54. package/Input/InputMoney.tsx +33 -150
  55. package/Input/InputMoneyView.tsx +187 -0
  56. package/Input/InputOTP.tsx +1 -1
  57. package/Input/InputPhoneNumber.tsx +5 -1
  58. package/Input/InputSearch.tsx +2 -108
  59. package/Input/InputTextArea.tsx +1 -5
  60. package/Input/SearchTextTyping.tsx +104 -0
  61. package/Input/common.tsx +4 -4
  62. package/Input/moneyUtils.ts +17 -0
  63. package/Input/styles.ts +25 -0
  64. package/Layout/FloatingButton.tsx +22 -21
  65. package/Layout/Screen.tsx +13 -3
  66. package/Layout/Section.tsx +1 -2
  67. package/Layout/index.ts +1 -2
  68. package/Layout/layoutMetrics.ts +58 -0
  69. package/Layout/layoutTypes.ts +86 -0
  70. package/Layout/styles.ts +2 -0
  71. package/Layout/utils.ts +9 -144
  72. package/Loader/DotLoader.tsx +7 -3
  73. package/Loader/ProgressBar.tsx +2 -6
  74. package/Loader/Spinner.tsx +7 -3
  75. package/Loader/styles.ts +4 -0
  76. package/Radio/index.tsx +2 -10
  77. package/Radio/styles.ts +6 -0
  78. package/Skeleton/index.tsx +1 -9
  79. package/Skeleton/styles.ts +1 -0
  80. package/Tag/index.tsx +3 -3
  81. package/Text/index.tsx +11 -2
  82. package/Title/index.tsx +2 -9
  83. package/Title/styles.ts +6 -0
  84. package/package.json +37 -36
  85. package/Application/BottomTab/useLiquidSelection.ts +0 -374
  86. package/Application/utils.tsx +0 -493
@@ -0,0 +1,215 @@
1
+ import { MutableRefObject, useCallback, useEffect, useState } from 'react';
2
+ import {
3
+ Extrapolation,
4
+ interpolate,
5
+ SharedValue,
6
+ useAnimatedStyle,
7
+ } from 'react-native-reanimated';
8
+ import {
9
+ BackHandler,
10
+ Dimensions,
11
+ Keyboard,
12
+ KeyboardEvent,
13
+ LayoutAnimation,
14
+ Platform,
15
+ useWindowDimensions,
16
+ } from 'react-native';
17
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
18
+ import { useHeaderHeight } from '@react-navigation/elements';
19
+ import { Spacing } from '../Consts';
20
+ import { DuoMetrics, useScreenLayout } from '../Layout/utils';
21
+ import { scheduleOnRN } from 'react-native-worklets';
22
+ import { BottomSheetParams, NavigatorProps } from './types';
23
+ import { getDuoTopInset, isIPhoneDuo } from './utils';
24
+ import { DUO_REGULAR_MIN_WIDTH, getSideInset } from './BottomSheet.styles';
25
+
26
+ export const useBottomSheetLayout = () => {
27
+ const windowSize = useWindowDimensions();
28
+ const isDuo = isIPhoneDuo();
29
+ const isDuoCentered =
30
+ isDuo &&
31
+ Math.min(windowSize.width, windowSize.height) >= DUO_REGULAR_MIN_WIDTH;
32
+ const isDuoFloating = isDuo && !isDuoCentered;
33
+ const { sideBar } = useScreenLayout();
34
+ const sideColumnWidth = (sideBar?.width ?? 0) - Spacing.M;
35
+ const sideColumn =
36
+ isDuoFloating &&
37
+ sideColumnWidth >= DuoMetrics.controlSize + Spacing.M &&
38
+ sideBar
39
+ ? sideBar
40
+ : undefined;
41
+ const hasSideColumn = !!sideColumn;
42
+ // 'screen' is the outer display on iPhone Duo, even when unfolded
43
+ const screenHeight = Dimensions.get('screen').height;
44
+ const heightDevice = isDuo ? windowSize.height : screenHeight;
45
+ const insets = useSafeAreaInsets();
46
+ const heightHeader = useHeaderHeight();
47
+ const topGap = Math.max(
48
+ sideColumn
49
+ ? insets.top
50
+ : getDuoTopInset(insets, windowSize.width, windowSize.height),
51
+ Spacing.XL,
52
+ );
53
+ const sideInsetLeft = isDuoFloating ? getSideInset(insets.left) : 0;
54
+ const sideInsetRight = isDuoFloating ? getSideInset(insets.right) : 0;
55
+ const indicatorShift = (sideInsetRight - sideInsetLeft) / 2;
56
+ const isIOS = Platform.OS === 'ios';
57
+ const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
58
+ const keyboardOffset = heightHeader - bottomInset;
59
+
60
+ return {
61
+ windowSize,
62
+ isDuo,
63
+ isDuoCentered,
64
+ isDuoFloating,
65
+ sideColumn,
66
+ sideColumnWidth,
67
+ hasSideColumn,
68
+ heightDevice,
69
+ insets,
70
+ topGap,
71
+ sideInsetLeft,
72
+ sideInsetRight,
73
+ indicatorShift,
74
+ bottomInset,
75
+ keyboardOffset,
76
+ };
77
+ };
78
+
79
+ // KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
80
+ // so lift it here and shrink maxHeight by the same amount instead
81
+ export const useDuoKeyboardLift = (
82
+ isDuo: boolean,
83
+ useKeyboardAvoidingView: boolean,
84
+ windowHeight: number,
85
+ bottomInset: number,
86
+ keyboardVerticalOffset?: number,
87
+ ) => {
88
+ const [keyboardLift, setKeyboardLift] = useState(0);
89
+
90
+ useEffect(() => {
91
+ if (!isDuo || !useKeyboardAvoidingView) {
92
+ return;
93
+ }
94
+ const onKeyboardChange = (e: KeyboardEvent) => {
95
+ const overlap = windowHeight - 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,
118
+ isDuo,
119
+ keyboardVerticalOffset,
120
+ useKeyboardAvoidingView,
121
+ windowHeight,
122
+ ]);
123
+
124
+ return keyboardLift;
125
+ };
126
+
127
+ export const useSheetAnimatedStyles = (
128
+ translateY: SharedValue<number>,
129
+ heightDevice: number,
130
+ ) => {
131
+ const animatedStyle = useAnimatedStyle(() => {
132
+ return {
133
+ transform: [{ translateY: translateY.value }],
134
+ };
135
+ });
136
+
137
+ const animatedOverlayStyle = useAnimatedStyle(() => {
138
+ return {
139
+ opacity: interpolate(
140
+ translateY.value,
141
+ [0, heightDevice],
142
+ [1, 0],
143
+ Extrapolation.CLAMP,
144
+ ),
145
+ };
146
+ });
147
+
148
+ return { animatedStyle, animatedOverlayStyle };
149
+ };
150
+
151
+ type SheetDismissParams = {
152
+ barrierDismissible: boolean;
153
+ closeAnimation: (callback?: () => void) => void;
154
+ navigator?: NavigatorProps;
155
+ leftOptions: BottomSheetParams['leftOptions'];
156
+ action: MutableRefObject<undefined | string>;
157
+ };
158
+
159
+ export const useSheetDismiss = ({
160
+ barrierDismissible,
161
+ closeAnimation,
162
+ navigator,
163
+ leftOptions,
164
+ action,
165
+ }: SheetDismissParams) => {
166
+ /**
167
+ * handler dismiss
168
+ */
169
+ const onDismiss = useCallback(
170
+ (force = false, callback?: () => void) => {
171
+ if (barrierDismissible && !force) {
172
+ return;
173
+ }
174
+ closeAnimation(() => {
175
+ navigator?.pop();
176
+ scheduleOnRN(() => {
177
+ callback?.();
178
+ });
179
+ });
180
+ },
181
+ [barrierDismissible, closeAnimation, navigator],
182
+ );
183
+
184
+ /**
185
+ * on request close
186
+ */
187
+ const onRequestClose = useCallback(
188
+ (callback?: () => void) => {
189
+ onDismiss(true, callback);
190
+ },
191
+ [onDismiss],
192
+ );
193
+
194
+ useEffect(() => {
195
+ const backHandler = BackHandler.addEventListener(
196
+ 'hardwareBackPress',
197
+ () => {
198
+ onDismiss();
199
+ return true;
200
+ },
201
+ );
202
+ return () => backHandler.remove();
203
+ }, [barrierDismissible, onDismiss]);
204
+
205
+ const onPressBack = useCallback(() => {
206
+ onDismiss(true, leftOptions?.onPressIconLeft);
207
+ }, [leftOptions, onDismiss]);
208
+
209
+ const onPressClose = useCallback(() => {
210
+ action.current = 'icon_close';
211
+ onDismiss(true);
212
+ }, [action, onDismiss]);
213
+
214
+ return { onDismiss, onRequestClose, onPressBack, onPressClose };
215
+ };
@@ -0,0 +1,231 @@
1
+ import React from 'react';
2
+ import {
3
+ PanResponderInstance,
4
+ ScaledSize,
5
+ TouchableOpacity,
6
+ View,
7
+ } from 'react-native';
8
+ import type { EdgeInsets } from 'react-native-safe-area-context';
9
+ import { Radius, Spacing, Styles } from '../Consts';
10
+ import layoutStyles from '../Layout/styles';
11
+ import { Text } from '../Text';
12
+ import { Icon } from '../Icon';
13
+ import { InnerEdgeStyles, type SideBarLayout } from '../Layout/utils';
14
+ import { SideBarButton } from './Components/SideBarButton';
15
+ import {
16
+ INDICATOR_HEIGHT,
17
+ SIDE_COLUMN_OFFSET,
18
+ styles,
19
+ } from './BottomSheet.styles';
20
+ import { BottomSheetParams, Theme } from './types';
21
+
22
+ type SheetPartsParams = {
23
+ panHandlers: PanResponderInstance['panHandlers'];
24
+ indicatorShift: number;
25
+ useDivider: boolean;
26
+ theme: Theme;
27
+ hasSideColumn: boolean;
28
+ showBack: boolean;
29
+ onPressBack: () => void;
30
+ onPressClose: () => void;
31
+ iconBack: string;
32
+ options: BottomSheetParams['options'];
33
+ leftOptions: BottomSheetParams['leftOptions'];
34
+ sideColumn?: SideBarLayout;
35
+ windowHeight: number;
36
+ sheetTop: number;
37
+ sideColumnHeight: number;
38
+ sideColumnWidth: number;
39
+ };
40
+
41
+ export const renderSheetHeader = ({
42
+ panHandlers,
43
+ indicatorShift,
44
+ useDivider,
45
+ theme,
46
+ hasSideColumn,
47
+ showBack,
48
+ onPressBack,
49
+ onPressClose,
50
+ iconBack,
51
+ options,
52
+ leftOptions,
53
+ }: SheetPartsParams) => {
54
+ const showButtons = !hasSideColumn;
55
+ return (
56
+ <View {...panHandlers}>
57
+ <View style={{ height: INDICATOR_HEIGHT }}>
58
+ <View
59
+ style={[
60
+ styles.indicator,
61
+ indicatorShift !== 0 && {
62
+ transform: [{ translateX: indicatorShift }],
63
+ },
64
+ ]}
65
+ />
66
+ </View>
67
+ <View
68
+ style={[
69
+ styles.header,
70
+ useDivider && styles.headerDivider,
71
+ useDivider && {
72
+ borderBottomColor: theme.colors.border.default,
73
+ },
74
+ ]}
75
+ >
76
+ {showButtons && showBack ? (
77
+ <TouchableOpacity style={styles.iconButton} onPress={onPressBack}>
78
+ <Icon source={iconBack} />
79
+ </TouchableOpacity>
80
+ ) : null}
81
+
82
+ {options.header ? (
83
+ <View style={Styles.flex}>{options.header}</View>
84
+ ) : (
85
+ <>
86
+ {showButtons && !leftOptions ? (
87
+ <View style={styles.iconButton} />
88
+ ) : null}
89
+ <Text
90
+ style={[
91
+ Styles.flex,
92
+ styles.title,
93
+ !showButtons && styles.titleInset,
94
+ ]}
95
+ numberOfLines={1}
96
+ typography="header_default_bold"
97
+ >
98
+ {options.title ?? ''}
99
+ </Text>
100
+ </>
101
+ )}
102
+ {showButtons ? (
103
+ <TouchableOpacity style={styles.iconButton} onPress={onPressClose}>
104
+ <Icon source="navigation_close" />
105
+ </TouchableOpacity>
106
+ ) : null}
107
+ </View>
108
+ </View>
109
+ );
110
+ };
111
+
112
+ /**
113
+ * iPhone Duo folded: back and close move to the side bar column, drawn on
114
+ * the part of the sheet under the side safe area. They line up with the
115
+ * header row, stay below the status bar cluster on a tall sheet and above a
116
+ * camera at the bottom end of the strip.
117
+ */
118
+ export const renderSheetSideColumn = ({
119
+ sideColumn,
120
+ windowHeight,
121
+ sheetTop,
122
+ sideColumnHeight,
123
+ sideColumnWidth,
124
+ showBack,
125
+ iconBack,
126
+ onPressBack,
127
+ onPressClose,
128
+ }: SheetPartsParams) => {
129
+ if (!sideColumn) {
130
+ return null;
131
+ }
132
+ const lowestTop =
133
+ windowHeight - sideColumn.bottom - sideColumnHeight - sheetTop;
134
+ const top = Math.min(
135
+ Math.max(sideColumn.top - sheetTop, SIDE_COLUMN_OFFSET),
136
+ lowestTop,
137
+ );
138
+
139
+ return (
140
+ <View
141
+ pointerEvents={'box-none'}
142
+ style={[
143
+ styles.sideColumn,
144
+ sideColumn.edge === 'right' ? styles.alignRight : styles.alignLeft,
145
+ InnerEdgeStyles[sideColumn.edge],
146
+ { top, width: sideColumnWidth },
147
+ ]}
148
+ >
149
+ {showBack ? (
150
+ <SideBarButton icon={iconBack} onPress={onPressBack} />
151
+ ) : null}
152
+ <SideBarButton icon="navigation_close" onPress={onPressClose} />
153
+ </View>
154
+ );
155
+ };
156
+
157
+ export const renderSheetFooter = ({
158
+ footerComponent,
159
+ theme,
160
+ sideInsetStyle,
161
+ }: {
162
+ footerComponent: BottomSheetParams['footerComponent'];
163
+ theme: Theme;
164
+ sideInsetStyle: any;
165
+ }) =>
166
+ footerComponent && (
167
+ <View
168
+ style={[
169
+ layoutStyles.shadow,
170
+ {
171
+ paddingBottom: Spacing.S,
172
+ backgroundColor: theme.colors.background.surface,
173
+ },
174
+ sideInsetStyle,
175
+ ]}
176
+ >
177
+ {footerComponent}
178
+ </View>
179
+ );
180
+
181
+ type SheetStyleParams = {
182
+ backgroundColor: string;
183
+ isDuo: boolean;
184
+ isDuoFloating: boolean;
185
+ isDuoCentered: boolean;
186
+ windowSize: ScaledSize;
187
+ topGap: number;
188
+ keyboardLift: number;
189
+ sideColumn?: SideBarLayout;
190
+ sideColumnHeight: number;
191
+ insets: EdgeInsets;
192
+ showBaseLineDebug: boolean;
193
+ };
194
+
195
+ export const getSheetStyle = ({
196
+ backgroundColor,
197
+ isDuo,
198
+ isDuoFloating,
199
+ isDuoCentered,
200
+ windowSize,
201
+ topGap,
202
+ keyboardLift,
203
+ sideColumn,
204
+ sideColumnHeight,
205
+ insets,
206
+ showBaseLineDebug,
207
+ }: SheetStyleParams) => [
208
+ styles.sheetContainer,
209
+ {
210
+ backgroundColor,
211
+ borderTopLeftRadius: isDuo ? Radius.XL : Radius.M,
212
+ borderTopRightRadius: isDuo ? Radius.XL : Radius.M,
213
+ },
214
+ isDuo && {
215
+ maxHeight: windowSize.height - topGap - keyboardLift,
216
+ marginBottom: keyboardLift,
217
+ },
218
+ isDuoFloating && { marginHorizontal: Spacing.M },
219
+ sideColumn && {
220
+ minHeight: SIDE_COLUMN_OFFSET + sideColumnHeight + sideColumn.bottom,
221
+ },
222
+ isDuoCentered && styles.centered,
223
+ isDuoCentered && {
224
+ // keeps the portrait width in landscape, like system sheets
225
+ width: Math.min(
226
+ Math.min(windowSize.width, windowSize.height) - Spacing.L * 2,
227
+ windowSize.width - Math.max(insets.left, insets.right, Spacing.L) * 2,
228
+ ),
229
+ },
230
+ showBaseLineDebug && styles.debugBaseLine,
231
+ ];
@@ -0,0 +1,59 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors, Radius, Spacing } from '../Consts';
3
+ import { DuoMetrics } from '../Layout/utils';
4
+
5
+ // iPhone Duo inner display: center the sheet instead of spanning the width
6
+ export const DUO_REGULAR_MIN_WIDTH = 600;
7
+
8
+ export const INDICATOR_HEIGHT = 16;
9
+ export const HEADER_HEIGHT = 56;
10
+ export const DUO_ORIENTATIONS = ['portrait', 'landscape'];
11
+ export const SIDE_COLUMN_OFFSET =
12
+ INDICATOR_HEIGHT + (HEADER_HEIGHT - DuoMetrics.controlSize) / 2;
13
+
14
+ export const getSideInset = (inset: number) => Math.max(inset - Spacing.M, 0);
15
+
16
+ export const styles = StyleSheet.create({
17
+ container: {
18
+ flex: 1,
19
+ justifyContent: 'flex-end',
20
+ },
21
+ overlay: {
22
+ flex: 1,
23
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
24
+ },
25
+ indicator: {
26
+ width: 40,
27
+ height: 4,
28
+ borderRadius: Radius.S,
29
+ backgroundColor: Colors.black_06,
30
+ position: 'absolute',
31
+ alignSelf: 'center',
32
+ top: Spacing.S,
33
+ },
34
+ header: {
35
+ height: HEADER_HEIGHT,
36
+ flexDirection: 'row',
37
+ alignItems: 'center',
38
+ },
39
+ title: { textAlign: 'center' },
40
+ titleInset: { paddingHorizontal: Spacing.M },
41
+ iconButton: {
42
+ width: 24,
43
+ height: 24,
44
+ alignItems: 'center',
45
+ justifyContent: 'center',
46
+ marginHorizontal: Spacing.M,
47
+ },
48
+ shrink: { flexShrink: 1 },
49
+ sideColumn: {
50
+ position: 'absolute',
51
+ gap: Spacing.M,
52
+ },
53
+ alignRight: { right: 0 },
54
+ alignLeft: { left: 0 },
55
+ debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
56
+ headerDivider: { borderBottomWidth: 1 },
57
+ sheetContainer: { overflow: 'hidden' },
58
+ centered: { alignSelf: 'center' },
59
+ });
@@ -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 };