@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
@@ -0,0 +1,195 @@
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 { scheduleOnRN } from 'react-native-worklets';
20
+ import { BottomSheetParams, NavigatorProps } from './types';
21
+ import { isIPhoneDuo } from './utils';
22
+ import { useModalLayout } from './modalLayout';
23
+
24
+ /**
25
+ * Sheet geometry for the current window: foldable placement and size from
26
+ * `useModalLayout`, plus the insets and offsets the keyboard handling needs.
27
+ */
28
+ export const useBottomSheetLayout = () => {
29
+ const windowSize = useWindowDimensions();
30
+ const isDuo = isIPhoneDuo();
31
+ const { isFoldable, placement, sheet, scrimOpacity } = useModalLayout();
32
+ // 'screen' is the outer display on iPhone Duo, even when unfolded
33
+ const screenHeight = Dimensions.get('screen').height;
34
+ const heightDevice = isDuo ? windowSize.height : screenHeight;
35
+ const insets = useSafeAreaInsets();
36
+ const heightHeader = useHeaderHeight();
37
+ const isIOS = Platform.OS === 'ios';
38
+ const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
39
+ const keyboardOffset = heightHeader - bottomInset;
40
+
41
+ return {
42
+ windowSize,
43
+ isDuo,
44
+ isFoldable,
45
+ placement,
46
+ sheet,
47
+ scrimOpacity,
48
+ heightDevice,
49
+ bottomInset,
50
+ keyboardOffset,
51
+ };
52
+ };
53
+
54
+ // KeyboardAvoidingView lifts the whole sheet past the top on iPhone Duo,
55
+ // so lift it here and shrink maxHeight by the same amount instead
56
+ export const useDuoKeyboardLift = (
57
+ isDuo: boolean,
58
+ useKeyboardAvoidingView: boolean,
59
+ windowHeight: number,
60
+ bottomInset: number,
61
+ keyboardVerticalOffset?: number,
62
+ ) => {
63
+ const [keyboardLift, setKeyboardLift] = useState(0);
64
+
65
+ useEffect(() => {
66
+ if (!isDuo || !useKeyboardAvoidingView) {
67
+ return;
68
+ }
69
+ const onKeyboardChange = (e: KeyboardEvent) => {
70
+ const overlap = windowHeight - e.endCoordinates.screenY;
71
+ const lift = Math.max(
72
+ overlap - bottomInset - (keyboardVerticalOffset ?? 0),
73
+ 0,
74
+ );
75
+ if (e.duration) {
76
+ LayoutAnimation.configureNext({
77
+ duration: e.duration,
78
+ update: {
79
+ duration: e.duration,
80
+ type: LayoutAnimation.Types[e.easing] || 'keyboard',
81
+ },
82
+ });
83
+ }
84
+ setKeyboardLift(lift);
85
+ };
86
+ const subscriptions = [
87
+ Keyboard.addListener('keyboardWillChangeFrame', onKeyboardChange),
88
+ Keyboard.addListener('keyboardWillHide', onKeyboardChange),
89
+ ];
90
+ return () => subscriptions.forEach(sub => sub.remove());
91
+ }, [
92
+ bottomInset,
93
+ isDuo,
94
+ keyboardVerticalOffset,
95
+ useKeyboardAvoidingView,
96
+ windowHeight,
97
+ ]);
98
+
99
+ return keyboardLift;
100
+ };
101
+
102
+ /**
103
+ * Slide of the sheet and fade of the scrim behind it, up to the
104
+ * `scrimOpacity` of the modal spec (lighter on foldables).
105
+ */
106
+ export const useSheetAnimatedStyles = (
107
+ translateY: SharedValue<number>,
108
+ heightDevice: number,
109
+ scrimOpacity: number,
110
+ ) => {
111
+ const animatedStyle = useAnimatedStyle(() => {
112
+ return {
113
+ transform: [{ translateY: translateY.value }],
114
+ };
115
+ });
116
+
117
+ const animatedOverlayStyle = useAnimatedStyle(() => {
118
+ return {
119
+ opacity: interpolate(
120
+ translateY.value,
121
+ [0, heightDevice],
122
+ [scrimOpacity, 0],
123
+ Extrapolation.CLAMP,
124
+ ),
125
+ };
126
+ });
127
+
128
+ return { animatedStyle, animatedOverlayStyle };
129
+ };
130
+
131
+ type SheetDismissParams = {
132
+ barrierDismissible: boolean;
133
+ closeAnimation: (callback?: () => void) => void;
134
+ navigator?: NavigatorProps;
135
+ leftOptions: BottomSheetParams['leftOptions'];
136
+ action: MutableRefObject<undefined | string>;
137
+ };
138
+
139
+ export const useSheetDismiss = ({
140
+ barrierDismissible,
141
+ closeAnimation,
142
+ navigator,
143
+ leftOptions,
144
+ action,
145
+ }: SheetDismissParams) => {
146
+ /**
147
+ * handler dismiss
148
+ */
149
+ const onDismiss = useCallback(
150
+ (force = false, callback?: () => void) => {
151
+ if (barrierDismissible && !force) {
152
+ return;
153
+ }
154
+ closeAnimation(() => {
155
+ navigator?.pop();
156
+ scheduleOnRN(() => {
157
+ callback?.();
158
+ });
159
+ });
160
+ },
161
+ [barrierDismissible, closeAnimation, navigator],
162
+ );
163
+
164
+ /**
165
+ * on request close
166
+ */
167
+ const onRequestClose = useCallback(
168
+ (callback?: () => void) => {
169
+ onDismiss(true, callback);
170
+ },
171
+ [onDismiss],
172
+ );
173
+
174
+ useEffect(() => {
175
+ const backHandler = BackHandler.addEventListener(
176
+ 'hardwareBackPress',
177
+ () => {
178
+ onDismiss();
179
+ return true;
180
+ },
181
+ );
182
+ return () => backHandler.remove();
183
+ }, [barrierDismissible, onDismiss]);
184
+
185
+ const onPressBack = useCallback(() => {
186
+ onDismiss(true, leftOptions?.onPressIconLeft);
187
+ }, [leftOptions, onDismiss]);
188
+
189
+ const onPressClose = useCallback(() => {
190
+ action.current = 'icon_close';
191
+ onDismiss(true);
192
+ }, [action, onDismiss]);
193
+
194
+ return { onDismiss, onRequestClose, onPressBack, onPressClose };
195
+ };
@@ -0,0 +1,147 @@
1
+ import React from 'react';
2
+ import { PanResponderInstance, View, ViewStyle } from 'react-native';
3
+ import { Spacing, Styles } from '../Consts';
4
+ import layoutStyles from '../Layout/styles';
5
+ import { Text } from '../Text';
6
+ import { INDICATOR_HEIGHT, styles } from './BottomSheet.styles';
7
+ import { ModalIconButton } from './ModalIconButton';
8
+ import { type ModalLayout } from './modalLayout';
9
+ import { BottomSheetParams, Theme } from './types';
10
+
11
+ type SheetHeaderParams = {
12
+ panHandlers: PanResponderInstance['panHandlers'];
13
+ useDivider: boolean;
14
+ theme: Theme;
15
+ showBack: boolean;
16
+ onPressBack: () => void;
17
+ onPressClose: () => void;
18
+ iconBack: string;
19
+ options: BottomSheetParams['options'];
20
+ leftOptions: BottomSheetParams['leftOptions'];
21
+ };
22
+
23
+ /**
24
+ * Drag handle and header row: back, title (or a custom header) and close, with
25
+ * the 32 modal icon buttons on every device.
26
+ */
27
+ export const renderSheetHeader = ({
28
+ panHandlers,
29
+ useDivider,
30
+ theme,
31
+ showBack,
32
+ onPressBack,
33
+ onPressClose,
34
+ iconBack,
35
+ options,
36
+ leftOptions,
37
+ }: SheetHeaderParams) => (
38
+ <View {...panHandlers}>
39
+ <View style={{ height: INDICATOR_HEIGHT }}>
40
+ <View style={styles.indicator} />
41
+ </View>
42
+ <View
43
+ style={[
44
+ styles.header,
45
+ useDivider && styles.headerDivider,
46
+ useDivider && {
47
+ borderBottomColor: theme.colors.border.default,
48
+ },
49
+ ]}
50
+ >
51
+ {showBack ? (
52
+ <ModalIconButton icon={iconBack} onPress={onPressBack} />
53
+ ) : null}
54
+
55
+ {options.header ? (
56
+ <View style={Styles.flex}>{options.header}</View>
57
+ ) : (
58
+ <>
59
+ {!leftOptions ? <View style={styles.iconButtonSpace} /> : null}
60
+ <Text
61
+ style={[Styles.flex, styles.title]}
62
+ numberOfLines={1}
63
+ typography="header_default_bold"
64
+ >
65
+ {options.title ?? ''}
66
+ </Text>
67
+ </>
68
+ )}
69
+ <ModalIconButton icon="navigation_close" onPress={onPressClose} />
70
+ </View>
71
+ </View>
72
+ );
73
+
74
+ /**
75
+ * Footer pinned under the sheet content, `null` when the sheet has none.
76
+ */
77
+ export const renderSheetFooter = ({
78
+ footerComponent,
79
+ theme,
80
+ }: {
81
+ footerComponent: BottomSheetParams['footerComponent'];
82
+ theme: Theme;
83
+ }) => {
84
+ if (!footerComponent) {
85
+ return null;
86
+ }
87
+ return (
88
+ <View
89
+ style={[
90
+ layoutStyles.shadow,
91
+ {
92
+ paddingBottom: Spacing.S,
93
+ backgroundColor: theme.colors.background.surface,
94
+ },
95
+ ]}
96
+ >
97
+ {footerComponent}
98
+ </View>
99
+ );
100
+ };
101
+
102
+ type SheetStyleParams = {
103
+ backgroundColor: string;
104
+ isDuo: boolean;
105
+ isFoldable: boolean;
106
+ placement: ModalLayout['placement'];
107
+ sheet: ModalLayout['sheet'];
108
+ keyboardLift: number;
109
+ showBaseLineDebug: boolean;
110
+ };
111
+
112
+ /**
113
+ * Sheet container style. On foldables the sheet is centered in the placement
114
+ * area, clear of the iPhone Duo control column and on the right half when half
115
+ * folded; the keyboard lift comes off its max height.
116
+ */
117
+ export const getSheetStyle = ({
118
+ backgroundColor,
119
+ isDuo,
120
+ isFoldable,
121
+ placement,
122
+ sheet,
123
+ keyboardLift,
124
+ showBaseLineDebug,
125
+ }: SheetStyleParams) => {
126
+ const { width, maxHeight } = sheet;
127
+ let foldableStyle: ViewStyle | undefined;
128
+ if (isFoldable && width !== undefined && maxHeight !== undefined) {
129
+ foldableStyle = {
130
+ width,
131
+ marginLeft: placement.x + (placement.width - width) / 2,
132
+ maxHeight: maxHeight - keyboardLift,
133
+ minHeight: sheet.minHeight,
134
+ };
135
+ }
136
+ return [
137
+ styles.sheetContainer,
138
+ {
139
+ backgroundColor,
140
+ borderTopLeftRadius: sheet.radius,
141
+ borderTopRightRadius: sheet.radius,
142
+ },
143
+ isDuo && { marginBottom: keyboardLift },
144
+ foldableStyle,
145
+ showBaseLineDebug && styles.debugBaseLine,
146
+ ];
147
+ };
@@ -0,0 +1,43 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors, Radius, Spacing } from '../Consts';
3
+ import { MODAL_ICON_BUTTON_SIZE } from './ModalIconButton';
4
+
5
+ export const INDICATOR_HEIGHT = 16;
6
+ export const HEADER_HEIGHT = 56;
7
+ export const DUO_ORIENTATIONS = ['portrait', 'landscape'];
8
+
9
+ export const styles = StyleSheet.create({
10
+ container: {
11
+ flex: 1,
12
+ justifyContent: 'flex-end',
13
+ },
14
+ overlay: {
15
+ flex: 1,
16
+ backgroundColor: Colors.black_20,
17
+ },
18
+ indicator: {
19
+ width: 40,
20
+ height: 4,
21
+ borderRadius: Radius.S,
22
+ backgroundColor: Colors.black_06,
23
+ position: 'absolute',
24
+ alignSelf: 'center',
25
+ top: Spacing.S,
26
+ },
27
+ header: {
28
+ height: HEADER_HEIGHT,
29
+ flexDirection: 'row',
30
+ alignItems: 'center',
31
+ paddingHorizontal: Spacing.M,
32
+ gap: Spacing.S,
33
+ },
34
+ title: { textAlign: 'center' },
35
+ iconButtonSpace: {
36
+ width: MODAL_ICON_BUTTON_SIZE,
37
+ height: MODAL_ICON_BUTTON_SIZE,
38
+ },
39
+ shrink: { flexShrink: 1 },
40
+ debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
41
+ headerDivider: { borderBottomWidth: 1 },
42
+ sheetContainer: { overflow: 'hidden' },
43
+ });