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

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 (100) 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/BottomSheet.tsx +114 -265
  5. package/Application/BottomTab/BottomTabBar.styles.ts +60 -0
  6. package/Application/BottomTab/BottomTabBar.tsx +7 -60
  7. package/Application/BottomTab/CustomBottomTabItem.tsx +5 -5
  8. package/Application/BottomTab/DuoRailTabBar.tsx +149 -0
  9. package/Application/BottomTab/DuoTabBar.parts.tsx +202 -0
  10. package/Application/BottomTab/DuoTabBar.styles.ts +123 -0
  11. package/Application/BottomTab/DuoTabBar.tsx +78 -464
  12. package/Application/BottomTab/TabScreen.tsx +177 -0
  13. package/Application/BottomTab/badge.ts +41 -0
  14. package/Application/BottomTab/hooks.ts +358 -0
  15. package/Application/BottomTab/index.styles.ts +30 -0
  16. package/Application/BottomTab/index.tsx +9 -236
  17. package/Application/Components/DuoGlassBackground.tsx +7 -9
  18. package/Application/Components/HeaderAnimated.tsx +1 -1
  19. package/Application/Components/HeaderBackground.tsx +1 -1
  20. package/Application/Components/HeaderExtendHeader.tsx +17 -11
  21. package/Application/Components/HeaderRight.styles.ts +144 -0
  22. package/Application/Components/HeaderRight.tsx +6 -528
  23. package/Application/Components/HeaderTitle.shared.ts +43 -0
  24. package/Application/Components/HeaderTitle.styles.ts +125 -0
  25. package/Application/Components/HeaderTitle.tsx +12 -576
  26. package/Application/Components/HeaderToolkitAction.tsx +208 -0
  27. package/Application/Components/NavigationButton.tsx +9 -6
  28. package/Application/Components/SearchHeader.tsx +1 -1
  29. package/Application/Components/SwipeBackHaptic.tsx +95 -0
  30. package/Application/Components/TitleJourney.tsx +110 -0
  31. package/Application/Components/TitleLocation.tsx +73 -0
  32. package/Application/Components/TitleUser.tsx +304 -0
  33. package/Application/Components/useToolkitActions.tsx +233 -0
  34. package/Application/ModalScreen.tsx +14 -10
  35. package/Application/Navigation.ts +1 -1
  36. package/Application/NavigationContainer.tsx +7 -8
  37. package/Application/StackScreen.tracking.ts +158 -0
  38. package/Application/StackScreen.tsx +10 -137
  39. package/Application/WidgetContainer.tsx +10 -3
  40. package/Application/hooks.ts +137 -0
  41. package/Application/index.ts +3 -1
  42. package/Application/navigationOptions.tsx +217 -0
  43. package/Application/types.header.ts +116 -0
  44. package/Application/types.tabs.ts +43 -0
  45. package/Application/types.theme.ts +87 -0
  46. package/Application/types.ts +12 -244
  47. package/Application/utils.ts +143 -0
  48. package/Badge/BadgeRibbon.tsx +3 -3
  49. package/Badge/styles.ts +2 -0
  50. package/Button/index.tsx +16 -136
  51. package/Button/styles.ts +1 -1
  52. package/Button/utils.ts +139 -0
  53. package/CheckBox/index.tsx +1 -6
  54. package/Divider/DashDivider.tsx +10 -11
  55. package/Divider/index.tsx +18 -8
  56. package/Image/index.tsx +1 -46
  57. package/Input/Input.tsx +7 -8
  58. package/Input/InputDropDown.tsx +3 -4
  59. package/Input/InputMoney.tsx +38 -164
  60. package/Input/InputMoneyView.tsx +187 -0
  61. package/Input/InputOTP.tsx +1 -1
  62. package/Input/InputPhoneNumber.tsx +5 -1
  63. package/Input/InputSearch.tsx +2 -108
  64. package/Input/InputTextArea.tsx +1 -5
  65. package/Input/SearchTextTyping.tsx +104 -0
  66. package/Input/common.tsx +4 -4
  67. package/Input/moneyUtils.ts +17 -0
  68. package/Input/styles.ts +26 -2
  69. package/Layout/FloatingButton.tsx +22 -21
  70. package/Layout/Screen.tsx +62 -667
  71. package/Layout/Section.tsx +1 -2
  72. package/Layout/index.ts +8 -4
  73. package/Layout/layoutMetrics.ts +58 -0
  74. package/Layout/layoutTypes.ts +86 -0
  75. package/Layout/screenContent.tsx +90 -0
  76. package/Layout/screenFooter.tsx +53 -0
  77. package/Layout/screenHeader.tsx +315 -0
  78. package/Layout/screenQuickReturn.tsx +67 -0
  79. package/Layout/screenScroll.ts +110 -0
  80. package/Layout/screenTypes.ts +134 -0
  81. package/Layout/styles.ts +2 -0
  82. package/Layout/utils.ts +10 -144
  83. package/Loader/DotLoader.tsx +7 -3
  84. package/Loader/ProgressBar.tsx +2 -6
  85. package/Loader/Spinner.tsx +7 -3
  86. package/Loader/styles.ts +4 -0
  87. package/Popup/PopupNotify.tsx +47 -11
  88. package/Popup/types.ts +7 -0
  89. package/Popup/utils.ts +61 -14
  90. package/Radio/index.tsx +2 -10
  91. package/Radio/styles.ts +6 -0
  92. package/Skeleton/index.tsx +1 -9
  93. package/Skeleton/styles.ts +1 -0
  94. package/Tag/index.tsx +3 -3
  95. package/Text/index.tsx +11 -2
  96. package/Title/index.tsx +2 -9
  97. package/Title/styles.ts +6 -0
  98. package/package.json +20 -22
  99. package/Application/device.ts +0 -29
  100. package/Application/utils.tsx +0 -497
@@ -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
+ });