@momo-kits/foundation 0.169.9 → 0.169.10-beta.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.
- package/Application/BottomSheet.hooks.ts +17 -27
- package/Application/BottomSheet.tsx +27 -35
- package/Application/BottomTab/BottomTabBar.tsx +6 -6
- package/Application/BottomTab/DuoRailTabBar.tsx +5 -5
- package/Application/BottomTab/TabScreen.tsx +10 -11
- package/Application/BottomTab/index.tsx +5 -5
- package/Application/BottomTab/usePressLift.ts +3 -3
- package/Application/Components/HeaderAnimated.tsx +38 -13
- package/Application/Components/HeaderBackground.tsx +16 -3
- package/Application/Components/HeaderExtendHeader.tsx +41 -28
- package/Application/Components/HeaderLeft.tsx +4 -18
- package/Application/Components/HeaderRight.styles.ts +15 -13
- package/Application/Components/HeaderTitle.shared.ts +10 -11
- package/Application/Components/HeaderToolkitAction.tsx +21 -54
- package/Application/Components/NavigationButton.tsx +9 -6
- package/Application/Components/SearchHeader.tsx +20 -7
- package/Application/Components/useToolkitActions.tsx +21 -22
- package/Application/ModalScreen.tsx +15 -19
- package/Application/StackScreen.tsx +18 -13
- package/Application/headerLayout.ts +165 -0
- package/Application/index.ts +11 -1
- package/Application/modalLayout.ts +0 -1
- package/Application/navigationOptions.tsx +6 -15
- package/Application/types.header.ts +7 -2
- package/Application/types.ts +7 -0
- package/Context/index.ts +1 -3
- package/Image/index.tsx +6 -6
- package/Input/Input.tsx +37 -40
- package/Input/InputDropDown.tsx +10 -13
- package/Input/InputMoney.tsx +17 -17
- package/Input/InputMoneyView.tsx +12 -12
- package/Input/InputOTP.tsx +34 -46
- package/Input/InputPhoneNumber.tsx +43 -48
- package/Input/InputSearch.tsx +39 -26
- package/Input/InputTextArea.tsx +23 -29
- package/Input/common.tsx +257 -0
- package/Input/index.tsx +7 -1
- package/Input/moneyUtils.ts +23 -0
- package/Input/utils.ts +7 -126
- package/Layout/FloatingButton.tsx +24 -38
- package/Layout/Screen.tsx +3 -0
- package/Layout/layoutMetrics.ts +34 -43
- package/Layout/screenHeader.tsx +55 -52
- package/Layout/screenTypes.ts +13 -4
- package/Pagination/PaginationScroll.tsx +3 -5
- package/Pagination/types.ts +5 -3
- package/Popup/PopupNotify.tsx +14 -17
- package/Popup/PopupPromotion.tsx +4 -5
- package/Popup/types.ts +4 -2
- package/Popup/utils.ts +4 -7
- package/Skeleton/index.tsx +12 -15
- package/package.json +1 -1
- package/Input/ErrorView.tsx +0 -59
- package/Input/FloatingView.tsx +0 -101
- package/Input/RenderTrailing.tsx +0 -79
- package/Input/constants.ts +0 -8
|
@@ -21,8 +21,6 @@ import { BottomSheetParams, NavigatorProps } from './types';
|
|
|
21
21
|
import { isIPhoneDuo } from './utils';
|
|
22
22
|
import { useModalLayout } from './modalLayout';
|
|
23
23
|
|
|
24
|
-
const MAX_IOS_BOTTOM_INSET = 21;
|
|
25
|
-
|
|
26
24
|
/**
|
|
27
25
|
* Sheet geometry for the current window: foldable placement and size from
|
|
28
26
|
* `useModalLayout`, plus the insets and offsets the keyboard handling needs.
|
|
@@ -37,10 +35,7 @@ export const useBottomSheetLayout = () => {
|
|
|
37
35
|
const insets = useSafeAreaInsets();
|
|
38
36
|
const heightHeader = useHeaderHeight();
|
|
39
37
|
const isIOS = Platform.OS === 'ios';
|
|
40
|
-
|
|
41
|
-
if (isIOS) {
|
|
42
|
-
bottomInset = Math.min(insets.bottom, MAX_IOS_BOTTOM_INSET);
|
|
43
|
-
}
|
|
38
|
+
const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
|
|
44
39
|
const keyboardOffset = heightHeader - bottomInset;
|
|
45
40
|
|
|
46
41
|
return {
|
|
@@ -73,7 +68,7 @@ export const useDuoKeyboardLift = (
|
|
|
73
68
|
if (!isDuo || !useKeyboardAvoidingView) {
|
|
74
69
|
return;
|
|
75
70
|
}
|
|
76
|
-
const
|
|
71
|
+
const onKeyboardChange = (e: KeyboardEvent) => {
|
|
77
72
|
const overlap = windowHeight - e.endCoordinates.screenY;
|
|
78
73
|
const lift = Math.max(
|
|
79
74
|
overlap - bottomInset - (keyboardVerticalOffset ?? 0),
|
|
@@ -91,8 +86,8 @@ export const useDuoKeyboardLift = (
|
|
|
91
86
|
setKeyboardLift(lift);
|
|
92
87
|
};
|
|
93
88
|
const subscriptions = [
|
|
94
|
-
Keyboard.addListener('keyboardWillChangeFrame',
|
|
95
|
-
Keyboard.addListener('keyboardWillHide',
|
|
89
|
+
Keyboard.addListener('keyboardWillChangeFrame', onKeyboardChange),
|
|
90
|
+
Keyboard.addListener('keyboardWillHide', onKeyboardChange),
|
|
96
91
|
];
|
|
97
92
|
return () => subscriptions.forEach(sub => sub.remove());
|
|
98
93
|
}, [
|
|
@@ -154,7 +149,7 @@ export const useSheetDismiss = ({
|
|
|
154
149
|
leftOptions,
|
|
155
150
|
action,
|
|
156
151
|
}: SheetDismissParams) => {
|
|
157
|
-
const
|
|
152
|
+
const onDismiss = useCallback(
|
|
158
153
|
(force = false, callback?: () => void) => {
|
|
159
154
|
if (barrierDismissible && !force) {
|
|
160
155
|
return;
|
|
@@ -169,37 +164,32 @@ export const useSheetDismiss = ({
|
|
|
169
164
|
[barrierDismissible, closeAnimation, navigator],
|
|
170
165
|
);
|
|
171
166
|
|
|
172
|
-
const
|
|
167
|
+
const onRequestClose = useCallback(
|
|
173
168
|
(callback?: () => void) => {
|
|
174
|
-
|
|
169
|
+
onDismiss(true, callback);
|
|
175
170
|
},
|
|
176
|
-
[
|
|
171
|
+
[onDismiss],
|
|
177
172
|
);
|
|
178
173
|
|
|
179
174
|
useEffect(() => {
|
|
180
175
|
const backHandler = BackHandler.addEventListener(
|
|
181
176
|
'hardwareBackPress',
|
|
182
177
|
() => {
|
|
183
|
-
|
|
178
|
+
onDismiss();
|
|
184
179
|
return true;
|
|
185
180
|
},
|
|
186
181
|
);
|
|
187
182
|
return () => backHandler.remove();
|
|
188
|
-
}, [barrierDismissible,
|
|
183
|
+
}, [barrierDismissible, onDismiss]);
|
|
189
184
|
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
}, [leftOptions,
|
|
185
|
+
const onPressBack = useCallback(() => {
|
|
186
|
+
onDismiss(true, leftOptions?.onPressIconLeft);
|
|
187
|
+
}, [leftOptions, onDismiss]);
|
|
193
188
|
|
|
194
|
-
const
|
|
189
|
+
const onPressClose = useCallback(() => {
|
|
195
190
|
action.current = 'icon_close';
|
|
196
|
-
|
|
197
|
-
}, [action,
|
|
191
|
+
onDismiss(true);
|
|
192
|
+
}, [action, onDismiss]);
|
|
198
193
|
|
|
199
|
-
return {
|
|
200
|
-
handleDismiss,
|
|
201
|
-
handleRequestClose,
|
|
202
|
-
handlePressBack,
|
|
203
|
-
handlePressClose,
|
|
204
|
-
};
|
|
194
|
+
return { onDismiss, onRequestClose, onPressBack, onPressClose };
|
|
205
195
|
};
|
|
@@ -30,23 +30,6 @@ import {
|
|
|
30
30
|
renderSheetHeader,
|
|
31
31
|
} from './BottomSheet.parts';
|
|
32
32
|
|
|
33
|
-
const OPEN_TIMING = {
|
|
34
|
-
duration: 350,
|
|
35
|
-
easing: Easing.bezier(0.05, 0.7, 0.1, 1),
|
|
36
|
-
};
|
|
37
|
-
const CLOSE_TIMING = {
|
|
38
|
-
duration: 200,
|
|
39
|
-
easing: Easing.bezier(0.3, 0.0, 0.8, 0.15),
|
|
40
|
-
};
|
|
41
|
-
const SETTLE_SPRING = {
|
|
42
|
-
damping: 20,
|
|
43
|
-
mass: 1,
|
|
44
|
-
stiffness: 100,
|
|
45
|
-
overshootClamping: false,
|
|
46
|
-
energyThreshold: 1e-10,
|
|
47
|
-
};
|
|
48
|
-
const DISMISS_DRAG_DISTANCE = 100;
|
|
49
|
-
|
|
50
33
|
/**
|
|
51
34
|
* Bottom sheet route of the modal stack: slides the given screen up under a
|
|
52
35
|
* drag handle and header, and reports how it was closed through `onDismiss`.
|
|
@@ -96,14 +79,22 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
96
79
|
);
|
|
97
80
|
|
|
98
81
|
const openAnimation = useCallback(() => {
|
|
99
|
-
translateY.value = withTiming(0,
|
|
82
|
+
translateY.value = withTiming(0, {
|
|
83
|
+
duration: 350,
|
|
84
|
+
easing: Easing.bezier(0.05, 0.7, 0.1, 1),
|
|
85
|
+
});
|
|
100
86
|
}, [translateY]);
|
|
101
87
|
|
|
102
88
|
const closeAnimation = useCallback(
|
|
103
89
|
(callback = () => {}) => {
|
|
104
|
-
translateY.value = withTiming(
|
|
105
|
-
|
|
106
|
-
|
|
90
|
+
translateY.value = withTiming(
|
|
91
|
+
heightDevice,
|
|
92
|
+
{
|
|
93
|
+
duration: 200,
|
|
94
|
+
easing: Easing.bezier(0.3, 0.0, 0.8, 0.15),
|
|
95
|
+
},
|
|
96
|
+
() => scheduleOnRN(callback),
|
|
97
|
+
);
|
|
107
98
|
},
|
|
108
99
|
[heightDevice, translateY],
|
|
109
100
|
);
|
|
@@ -120,11 +111,17 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
120
111
|
}
|
|
121
112
|
},
|
|
122
113
|
onPanResponderRelease: (_, gestureState) => {
|
|
123
|
-
if (gestureState.dy >
|
|
114
|
+
if (gestureState.dy > 100) {
|
|
124
115
|
action.current = 'gesture';
|
|
125
|
-
|
|
116
|
+
onDismiss(true);
|
|
126
117
|
} else {
|
|
127
|
-
translateY.value = withSpring(0,
|
|
118
|
+
translateY.value = withSpring(0, {
|
|
119
|
+
damping: 20,
|
|
120
|
+
mass: 1,
|
|
121
|
+
stiffness: 100,
|
|
122
|
+
overshootClamping: false,
|
|
123
|
+
energyThreshold: 1e-10,
|
|
124
|
+
});
|
|
128
125
|
}
|
|
129
126
|
},
|
|
130
127
|
}),
|
|
@@ -154,12 +151,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
154
151
|
leftOptions,
|
|
155
152
|
action,
|
|
156
153
|
});
|
|
157
|
-
const {
|
|
158
|
-
handleDismiss,
|
|
159
|
-
handleRequestClose,
|
|
160
|
-
handlePressBack,
|
|
161
|
-
handlePressClose,
|
|
162
|
-
} = handlers;
|
|
154
|
+
const { onDismiss, onRequestClose, onPressBack, onPressClose } = handlers;
|
|
163
155
|
|
|
164
156
|
const showBack = !!leftOptions?.onPressIconLeft;
|
|
165
157
|
const iconBack = leftOptions?.iconLeft ?? 'ic_back';
|
|
@@ -169,8 +161,8 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
169
161
|
useDivider,
|
|
170
162
|
theme,
|
|
171
163
|
showBack,
|
|
172
|
-
onPressBack
|
|
173
|
-
onPressClose
|
|
164
|
+
onPressBack,
|
|
165
|
+
onPressClose,
|
|
174
166
|
iconBack,
|
|
175
167
|
options,
|
|
176
168
|
leftOptions,
|
|
@@ -186,7 +178,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
186
178
|
<Screen
|
|
187
179
|
{...props}
|
|
188
180
|
{...props.route.params}
|
|
189
|
-
onRequestClose={
|
|
181
|
+
onRequestClose={onRequestClose}
|
|
190
182
|
/>
|
|
191
183
|
);
|
|
192
184
|
|
|
@@ -213,7 +205,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
213
205
|
transparent
|
|
214
206
|
visible={true}
|
|
215
207
|
onRequestClose={() => {
|
|
216
|
-
|
|
208
|
+
onDismiss();
|
|
217
209
|
}}
|
|
218
210
|
style={StyleSheet.absoluteFillObject}
|
|
219
211
|
isModalKit={true}
|
|
@@ -233,7 +225,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
233
225
|
style={StyleSheet.absoluteFillObject}
|
|
234
226
|
onPress={() => {
|
|
235
227
|
action.current = 'touch';
|
|
236
|
-
|
|
228
|
+
onDismiss();
|
|
237
229
|
}}
|
|
238
230
|
>
|
|
239
231
|
<Animated.View style={[styles.overlay, animatedOverlayStyle]} />
|
|
@@ -54,16 +54,16 @@ const useCenterActionPress = () => {
|
|
|
54
54
|
const buttonStyle = useAnimatedStyle(() => ({
|
|
55
55
|
transform: [{ scale: scale.value }],
|
|
56
56
|
}));
|
|
57
|
-
const
|
|
57
|
+
const onPressIn = () => {
|
|
58
58
|
scale.value = withTiming(
|
|
59
59
|
TabBarMotion.ctaPressedScale,
|
|
60
60
|
TabBarMotion.ctaPress,
|
|
61
61
|
);
|
|
62
62
|
};
|
|
63
|
-
const
|
|
63
|
+
const onPressOut = () => {
|
|
64
64
|
scale.value = withTiming(1, TabBarMotion.ctaPress);
|
|
65
65
|
};
|
|
66
|
-
return { buttonStyle,
|
|
66
|
+
return { buttonStyle, onPressIn, onPressOut };
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
/**
|
|
@@ -77,7 +77,7 @@ const CenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
77
77
|
const context = useContext<any>(MiniAppContext);
|
|
78
78
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
79
79
|
const { icon, label, onPress, labelContent } = floatingButton;
|
|
80
|
-
const { buttonStyle,
|
|
80
|
+
const { buttonStyle, onPressIn, onPressOut } = useCenterActionPress();
|
|
81
81
|
let iconNode = icon;
|
|
82
82
|
if (typeof icon === 'string') {
|
|
83
83
|
iconNode = (
|
|
@@ -95,8 +95,8 @@ const CenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
95
95
|
accessibilityRole={'button'}
|
|
96
96
|
accessibilityLabel={label}
|
|
97
97
|
onPress={onPress}
|
|
98
|
-
onPressIn={
|
|
99
|
-
onPressOut={
|
|
98
|
+
onPressIn={onPressIn}
|
|
99
|
+
onPressOut={onPressOut}
|
|
100
100
|
style={styles.cta}
|
|
101
101
|
>
|
|
102
102
|
<Animated.View
|
|
@@ -94,14 +94,14 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
94
94
|
floatingButton,
|
|
95
95
|
}) => {
|
|
96
96
|
const { theme } = useContext(ApplicationContext);
|
|
97
|
-
const { buttonStyle,
|
|
97
|
+
const { buttonStyle, onPressIn, onPressOut } = useCenterActionPress();
|
|
98
98
|
return (
|
|
99
99
|
<Pressable
|
|
100
100
|
accessibilityRole={'button'}
|
|
101
101
|
accessibilityLabel={floatingButton.label}
|
|
102
102
|
onPress={floatingButton.onPress}
|
|
103
|
-
onPressIn={
|
|
104
|
-
onPressOut={
|
|
103
|
+
onPressIn={onPressIn}
|
|
104
|
+
onPressOut={onPressOut}
|
|
105
105
|
style={styles.railCta}
|
|
106
106
|
>
|
|
107
107
|
<Animated.View
|
|
@@ -190,8 +190,8 @@ const DuoRailTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
190
190
|
accessibilityLabel={current?.label}
|
|
191
191
|
accessibilityHint={'bottom_tab_bar'}
|
|
192
192
|
activeOpacity={1}
|
|
193
|
-
onPressIn={compactLift.
|
|
194
|
-
onPressOut={compactLift.
|
|
193
|
+
onPressIn={compactLift.onPressIn}
|
|
194
|
+
onPressOut={compactLift.onPressOut}
|
|
195
195
|
onPress={expandTabBar}
|
|
196
196
|
style={styles.railCompact}
|
|
197
197
|
>
|
|
@@ -15,8 +15,6 @@ import { useScreenLayout } from '../../Layout';
|
|
|
15
15
|
import { getHeaderRowOptions } from '../../Layout/utils';
|
|
16
16
|
import { BottomTabInsetContext } from './BottomTabInset';
|
|
17
17
|
|
|
18
|
-
const NAVIGATED_REPORT_DELAY = 100;
|
|
19
|
-
|
|
20
18
|
const Stack = createStackNavigator();
|
|
21
19
|
|
|
22
20
|
/**
|
|
@@ -31,15 +29,16 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
|
|
|
31
29
|
|
|
32
30
|
const screenName = screen?.name || screen?.type?.name || 'Invalid';
|
|
33
31
|
const { isBackgroundToForeground } = useAppState();
|
|
32
|
+
// Pad clear of the floating bar unless a Screen inside claims the inset to scroll under it.
|
|
34
33
|
const { inset: tabBarInset } = useContext(BottomTabInsetContext);
|
|
35
|
-
const [
|
|
34
|
+
const [insetClaimed, setInsetClaimed] = useState(false);
|
|
36
35
|
const tabInsetValue = useMemo(
|
|
37
|
-
() => ({ inset: tabBarInset, claim: () =>
|
|
36
|
+
() => ({ inset: tabBarInset, claim: () => setInsetClaimed(true) }),
|
|
38
37
|
[tabBarInset],
|
|
39
38
|
);
|
|
40
|
-
const tabInsetStyle = { paddingBottom:
|
|
39
|
+
const tabInsetStyle = { paddingBottom: insetClaimed ? 0 : tabBarInset };
|
|
41
40
|
|
|
42
|
-
const
|
|
41
|
+
const onScreenNavigated = (pre: string, current: string) => {
|
|
43
42
|
if (!isBackgroundToForeground) {
|
|
44
43
|
const data: any = {
|
|
45
44
|
preScreenName: pre,
|
|
@@ -69,13 +68,13 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
|
|
|
69
68
|
navigator?.maxApi?.getDataObserver?.(
|
|
70
69
|
'current_screen',
|
|
71
70
|
(data: any) => {
|
|
72
|
-
|
|
71
|
+
onScreenNavigated(data?.screenName, screenName);
|
|
73
72
|
navigator?.maxApi?.setObserver?.('current_screen', {
|
|
74
73
|
screenName,
|
|
75
74
|
});
|
|
76
75
|
},
|
|
77
76
|
);
|
|
78
|
-
},
|
|
77
|
+
}, 100);
|
|
79
78
|
},
|
|
80
79
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
80
|
[],
|
|
@@ -84,12 +83,12 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
|
|
|
84
83
|
|
|
85
84
|
useEffect(
|
|
86
85
|
() => {
|
|
87
|
-
const
|
|
86
|
+
const onFocusApp = navigator?.maxApi?.listen?.('onFocusApp', () => {
|
|
88
87
|
if (navigation.isFocused()) {
|
|
89
88
|
navigator?.maxApi?.getDataObserver?.(
|
|
90
89
|
'current_screen',
|
|
91
90
|
(data: any) => {
|
|
92
|
-
|
|
91
|
+
onScreenNavigated(data?.screenName, screenName);
|
|
93
92
|
navigator?.maxApi?.setObserver?.('current_screen', {
|
|
94
93
|
screenName,
|
|
95
94
|
});
|
|
@@ -99,7 +98,7 @@ const TabScreen: React.FC<any> = ({ route, navigation }) => {
|
|
|
99
98
|
});
|
|
100
99
|
|
|
101
100
|
return () => {
|
|
102
|
-
|
|
101
|
+
onFocusApp?.remove?.();
|
|
103
102
|
};
|
|
104
103
|
},
|
|
105
104
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -99,16 +99,16 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
99
99
|
}, []),
|
|
100
100
|
);
|
|
101
101
|
|
|
102
|
-
const
|
|
102
|
+
const onFocus = (e: any) => {
|
|
103
103
|
activeIndex.current = tabs.findIndex(i => e.target.includes(i.name));
|
|
104
104
|
|
|
105
105
|
const isFirstTab = activeIndex.current === 0;
|
|
106
|
-
const
|
|
106
|
+
const gestureEnabled = Platform.OS === 'ios' && isFirstTab;
|
|
107
107
|
navigation?.setOptions({
|
|
108
|
-
gestureEnabled:
|
|
108
|
+
gestureEnabled: gestureEnabled,
|
|
109
109
|
});
|
|
110
110
|
NativeModules?.MiniAppModule?.setShouldUseFloatingArrowBack?.(
|
|
111
|
-
!
|
|
111
|
+
!gestureEnabled,
|
|
112
112
|
);
|
|
113
113
|
};
|
|
114
114
|
|
|
@@ -123,7 +123,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
123
123
|
listeners?.tabPress?.(e);
|
|
124
124
|
},
|
|
125
125
|
focus: e => {
|
|
126
|
-
|
|
126
|
+
onFocus(e);
|
|
127
127
|
StatusBar.setBarStyle('dark-content', true);
|
|
128
128
|
listeners?.focus?.(e);
|
|
129
129
|
},
|
|
@@ -18,15 +18,15 @@ const usePressLift = () => {
|
|
|
18
18
|
const style = useAnimatedStyle(() => ({
|
|
19
19
|
transform: [{ scale: 1 + lift.value * (PRESSED_SCALE - 1) }],
|
|
20
20
|
}));
|
|
21
|
-
const
|
|
21
|
+
const onPressIn = () => {
|
|
22
22
|
if (!reduceMotion) {
|
|
23
23
|
lift.value = withSpring(1, SPRING);
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
|
-
const
|
|
26
|
+
const onPressOut = () => {
|
|
27
27
|
lift.value = withSpring(0, SPRING);
|
|
28
28
|
};
|
|
29
|
-
return { style,
|
|
29
|
+
return { style, onPressIn, onPressOut };
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export { usePressLift };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { StyleSheet } from 'react-native';
|
|
1
|
+
import { StyleSheet, View } from 'react-native';
|
|
2
|
+
import LinearGradient from 'react-native-linear-gradient';
|
|
2
3
|
import Animated, {
|
|
3
4
|
Extrapolation,
|
|
4
5
|
interpolate,
|
|
@@ -9,6 +10,7 @@ import React from 'react';
|
|
|
9
10
|
import { Image } from '../../Image';
|
|
10
11
|
import { Colors } from '../../Consts';
|
|
11
12
|
import { useNormalizedSharedValue } from '../hooks';
|
|
13
|
+
import { HeaderBarMetrics, useHeaderLayout } from '../headerLayout';
|
|
12
14
|
|
|
13
15
|
const ASPECT_RATIOS: Record<
|
|
14
16
|
NonNullable<HeaderAnimatedProps['aspectRatio']>,
|
|
@@ -21,16 +23,20 @@ const ASPECT_RATIOS: Record<
|
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Parallax header image (or `children`): stretches when pulled down and fades out
|
|
24
|
-
* as the content scrolls up.
|
|
26
|
+
* as the content scrolls up. Wide windows and the iPhone Duo inner display keep
|
|
27
|
+
* it at phone size, centered.
|
|
25
28
|
*/
|
|
26
29
|
const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
27
30
|
animatedValue,
|
|
28
31
|
image,
|
|
29
32
|
useScale = true,
|
|
30
33
|
aspectRatio = '16/9',
|
|
34
|
+
bannerBackgroundColor,
|
|
31
35
|
children,
|
|
32
36
|
}) => {
|
|
33
37
|
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
38
|
+
const { isBannerCentered, bannerWidth } = useHeaderLayout();
|
|
39
|
+
const ratio = ASPECT_RATIOS[aspectRatio];
|
|
34
40
|
const animatedStyle = useAnimatedStyle(() => {
|
|
35
41
|
let scale = 1;
|
|
36
42
|
if (useScale) {
|
|
@@ -59,22 +65,36 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
|
59
65
|
};
|
|
60
66
|
});
|
|
61
67
|
|
|
68
|
+
const content = children ?? (
|
|
69
|
+
<Image
|
|
70
|
+
source={{
|
|
71
|
+
uri: image,
|
|
72
|
+
}}
|
|
73
|
+
style={styles.full}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (!isBannerCentered) {
|
|
78
|
+
return (
|
|
79
|
+
<Animated.View
|
|
80
|
+
style={[styles.container, { aspectRatio: ratio }, animatedStyle]}
|
|
81
|
+
>
|
|
82
|
+
{content}
|
|
83
|
+
</Animated.View>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
62
87
|
return (
|
|
63
88
|
<Animated.View
|
|
64
|
-
style={[
|
|
65
|
-
styles.container,
|
|
66
|
-
{ aspectRatio: ASPECT_RATIOS[aspectRatio] },
|
|
67
|
-
animatedStyle,
|
|
68
|
-
]}
|
|
89
|
+
style={[styles.container, { height: bannerWidth / ratio }, animatedStyle]}
|
|
69
90
|
>
|
|
70
|
-
{
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}}
|
|
75
|
-
style={styles.full}
|
|
91
|
+
{!!bannerBackgroundColor && (
|
|
92
|
+
<LinearGradient
|
|
93
|
+
colors={[bannerBackgroundColor, bannerBackgroundColor + '00']}
|
|
94
|
+
style={StyleSheet.absoluteFill}
|
|
76
95
|
/>
|
|
77
96
|
)}
|
|
97
|
+
<View style={styles.phoneBanner}>{content}</View>
|
|
78
98
|
</Animated.View>
|
|
79
99
|
);
|
|
80
100
|
};
|
|
@@ -90,6 +110,11 @@ const styles = StyleSheet.create({
|
|
|
90
110
|
alignSelf: 'center',
|
|
91
111
|
},
|
|
92
112
|
full: { width: '100%', height: '100%' },
|
|
113
|
+
phoneBanner: {
|
|
114
|
+
width: HeaderBarMetrics.bannerWidth,
|
|
115
|
+
height: '100%',
|
|
116
|
+
alignSelf: 'center',
|
|
117
|
+
},
|
|
93
118
|
});
|
|
94
119
|
|
|
95
120
|
export { HeaderAnimated };
|
|
@@ -8,10 +8,12 @@ import { Colors, Styles } from '../../Consts';
|
|
|
8
8
|
import { Image } from '../../Image';
|
|
9
9
|
import BackgroundImageView from './BackgroundImageView';
|
|
10
10
|
import { useNormalizedSharedValue } from '../hooks';
|
|
11
|
+
import { HeaderBarMetrics, useHeaderLayout } from '../headerLayout';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Header background: surface and image that fade in on scroll, with an optional
|
|
14
|
-
* gradient (custom or theme) on top
|
|
15
|
+
* gradient (custom or theme) on top, at phone height on wide windows. Outlined
|
|
16
|
+
* when `showBaseLineDebug` is on.
|
|
15
17
|
*/
|
|
16
18
|
const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
17
19
|
animatedValue,
|
|
@@ -25,6 +27,7 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
|
25
27
|
const gradientColor = customGradientColor ?? theme.colors.gradient;
|
|
26
28
|
const headerImage = headerBackground ?? theme.assets?.headerBackground;
|
|
27
29
|
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
30
|
+
const { isWide } = useHeaderLayout();
|
|
28
31
|
|
|
29
32
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
30
33
|
|
|
@@ -38,14 +41,19 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
|
38
41
|
/>
|
|
39
42
|
<View style={styles.gradientContainer}>
|
|
40
43
|
{useGradient && !!gradientColor && (
|
|
41
|
-
<Animated.View
|
|
44
|
+
<Animated.View
|
|
45
|
+
style={[styles.extendedHeader, isWide && styles.phoneHeight]}
|
|
46
|
+
>
|
|
42
47
|
<LinearGradient
|
|
43
48
|
colors={[gradientColor, gradientColor + '00']}
|
|
44
49
|
style={StyleSheet.absoluteFill}
|
|
45
50
|
>
|
|
46
51
|
{!!headerImage && (
|
|
47
52
|
<Image
|
|
48
|
-
style={
|
|
53
|
+
style={[
|
|
54
|
+
styles.headerBackground,
|
|
55
|
+
isWide && styles.phoneHeight,
|
|
56
|
+
]}
|
|
49
57
|
source={{ uri: headerImage }}
|
|
50
58
|
loading={false}
|
|
51
59
|
/>
|
|
@@ -75,6 +83,11 @@ const styles = StyleSheet.create({
|
|
|
75
83
|
position: 'absolute',
|
|
76
84
|
aspectRatio: 375 / 154,
|
|
77
85
|
},
|
|
86
|
+
// wide windows keep the phone gradient height instead of scaling with the width
|
|
87
|
+
phoneHeight: {
|
|
88
|
+
aspectRatio: undefined,
|
|
89
|
+
height: HeaderBarMetrics.gradientHeight,
|
|
90
|
+
},
|
|
78
91
|
debugBaseLine: { borderWidth: 1, borderColor: Colors.green_06 },
|
|
79
92
|
});
|
|
80
93
|
|