@momo-kits/foundation 0.167.1-duotest.2 → 0.167.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 (43) hide show
  1. package/Application/BottomSheet.tsx +14 -115
  2. package/Application/BottomTab/BottomTabBar.tsx +3 -6
  3. package/Application/BottomTab/index.tsx +135 -162
  4. package/Application/Components/HeaderExtendHeader.tsx +28 -75
  5. package/Application/Components/HeaderLeft.tsx +0 -38
  6. package/Application/Components/HeaderRight.tsx +9 -142
  7. package/Application/Components/HeaderTitle.tsx +7 -43
  8. package/Application/Components/SearchHeader.tsx +15 -22
  9. package/Application/ModalScreen.tsx +2 -16
  10. package/Application/NavigationContainer.tsx +38 -22
  11. package/Application/Navigator.ts +3 -1
  12. package/Application/StackScreen.tsx +9 -63
  13. package/Application/types.ts +1 -27
  14. package/Application/utils.tsx +6 -242
  15. package/Consts/theme.ts +4 -3
  16. package/Input/Input.tsx +4 -3
  17. package/Input/InputMoney.tsx +8 -5
  18. package/Input/InputPhoneNumber.tsx +4 -3
  19. package/Input/InputSearch.tsx +4 -3
  20. package/Input/InputTextArea.tsx +4 -3
  21. package/Layout/Card.tsx +26 -8
  22. package/Layout/FloatingButton.tsx +3 -3
  23. package/Layout/GridSystem.tsx +137 -0
  24. package/Layout/Item.tsx +10 -1
  25. package/Layout/Screen.tsx +52 -199
  26. package/Layout/Section.tsx +18 -5
  27. package/Layout/index.ts +4 -4
  28. package/Layout/styles.ts +0 -5
  29. package/Layout/utils.ts +5 -465
  30. package/Popup/PopupNotify.tsx +22 -33
  31. package/Popup/PopupPromotion.tsx +8 -20
  32. package/Text/utils.ts +2 -1
  33. package/package.json +2 -3
  34. package/Application/BottomTab/DuoBarCompression.tsx +0 -135
  35. package/Application/BottomTab/DuoTabBar.tsx +0 -462
  36. package/Application/Components/DuoGlassBackground.tsx +0 -179
  37. package/Application/Components/DuoQuickReturnHeader.tsx +0 -42
  38. package/Application/Components/OptionalGlassView.ts +0 -11
  39. package/Application/Components/OptionalScrollObserver.ts +0 -12
  40. package/Application/Components/SideBarButton.tsx +0 -139
  41. package/Application/device.ts +0 -29
  42. package/Layout/ReservedRegions.tsx +0 -47
  43. package/Popup/utils.ts +0 -32
@@ -1,179 +0,0 @@
1
- import React, { useContext, useState } from 'react';
2
- import { StyleSheet, View } from 'react-native';
3
- import LinearGradient from 'react-native-linear-gradient';
4
- import Animated, {
5
- Extrapolation,
6
- interpolate,
7
- runOnJS,
8
- useAnimatedReaction,
9
- useAnimatedStyle,
10
- } from 'react-native-reanimated';
11
- import { ApplicationContext } from '../../Context';
12
- import { Shadow } from '../../Consts';
13
- import { Image } from '../../Image';
14
- import { AnimatedCompatValue, useNormalizedSharedValue } from '../utils';
15
- import { GlassView } from './OptionalGlassView';
16
-
17
- // extended headers run their gradient this far below the header, as off Duo
18
- const EXTENDED_GRADIENT_EXTRA = 52;
19
- // scroll distance over which the extended gradient collapses to the header
20
- const EXTENDED_COLLAPSE_DISTANCE = 100;
21
- // scroll distance over which the bar material fades in
22
- const FADE_DISTANCE = 16;
23
- const TRANSLUCENT_ALPHA = 'CC';
24
-
25
- /**
26
- * iPhone Duo header background: glass running edge to edge, behind the status bar
27
- * column too, shown once content scrolls under it
28
- */
29
- const DuoGlassBackground: React.FC<{
30
- animatedValue?: AnimatedCompatValue;
31
- // scroll offset where the bar starts to show, e.g. below an animated banner
32
- fadeStart?: number;
33
- }> = ({ animatedValue, fadeStart = 0 }) => {
34
- const { theme } = useContext(ApplicationContext);
35
- const sharedValue = useNormalizedSharedValue(animatedValue);
36
- const [scrolled, setScrolled] = useState(false);
37
-
38
- useAnimatedReaction(
39
- () => sharedValue.value > fadeStart,
40
- (isScrolled, wasScrolled) => {
41
- if (GlassView && isScrolled !== wasScrolled) {
42
- runOnJS(setScrolled)(isScrolled);
43
- }
44
- },
45
- [fadeStart],
46
- );
47
-
48
- const fadeStyle = useAnimatedStyle(() => ({
49
- opacity: interpolate(
50
- sharedValue.value,
51
- [fadeStart, fadeStart + FADE_DISTANCE],
52
- [0, 1],
53
- Extrapolation.CLAMP,
54
- ),
55
- }));
56
-
57
- return (
58
- <View pointerEvents={'none'} style={StyleSheet.absoluteFill}>
59
- {GlassView ? (
60
- // UIVisualEffectView breaks under alpha, so glass toggles its effect instead
61
- <GlassView
62
- effect={scrolled ? 'glass' : 'none'}
63
- useShadow
64
- style={StyleSheet.absoluteFill}
65
- />
66
- ) : (
67
- <Animated.View
68
- style={[
69
- StyleSheet.absoluteFill,
70
- styles.translucentBar,
71
- {
72
- backgroundColor:
73
- theme.colors.background.surface + TRANSLUCENT_ALPHA,
74
- borderBottomColor: theme.colors.border.default,
75
- },
76
- fadeStyle,
77
- ]}
78
- />
79
- )}
80
- </View>
81
- );
82
- };
83
-
84
- /**
85
- * pink header gradient drawn under the content, fading out on scroll as main's extended
86
- * header does: header-high for default headers, 52 taller (collapsing) for extended
87
- */
88
- const DuoScrollGradient: React.FC<{
89
- animatedValue?: AnimatedCompatValue;
90
- gradientColor?: string;
91
- headerBackground?: string;
92
- heightHeader: number;
93
- extended?: boolean;
94
- // quick return header: the gradient leaves with the content instead of fading
95
- followScroll?: boolean;
96
- }> = ({
97
- animatedValue,
98
- gradientColor,
99
- headerBackground,
100
- heightHeader,
101
- extended,
102
- followScroll,
103
- }) => {
104
- const { theme } = useContext(ApplicationContext);
105
- const sharedValue = useNormalizedSharedValue(animatedValue);
106
- const color = gradientColor ?? theme.colors.gradient;
107
- const image = headerBackground ?? theme.assets?.headerBackground;
108
-
109
- const scrollStyle = useAnimatedStyle(() => {
110
- if (followScroll) {
111
- return {
112
- height: heightHeader + (extended ? EXTENDED_GRADIENT_EXTRA : 0),
113
- transform: [{ translateY: -Math.max(sharedValue.value, 0) }],
114
- };
115
- }
116
- return {
117
- height: extended
118
- ? interpolate(
119
- sharedValue.value,
120
- [0, EXTENDED_COLLAPSE_DISTANCE],
121
- [heightHeader + EXTENDED_GRADIENT_EXTRA, heightHeader],
122
- Extrapolation.CLAMP,
123
- )
124
- : heightHeader,
125
- opacity: interpolate(
126
- sharedValue.value,
127
- [0, EXTENDED_GRADIENT_EXTRA],
128
- [1, 0],
129
- Extrapolation.CLAMP,
130
- ),
131
- };
132
- });
133
-
134
- if (!color) {
135
- return null;
136
- }
137
-
138
- return (
139
- <Animated.View
140
- pointerEvents={'none'}
141
- style={[styles.gradient, scrollStyle]}
142
- >
143
- <LinearGradient
144
- colors={[color, color + '00']}
145
- style={StyleSheet.absoluteFill}
146
- >
147
- {!!image && (
148
- <Image
149
- style={styles.gradientFill}
150
- source={{ uri: image }}
151
- loading={false}
152
- />
153
- )}
154
- </LinearGradient>
155
- </Animated.View>
156
- );
157
- };
158
-
159
- const styles = StyleSheet.create({
160
- translucentBar: {
161
- borderBottomWidth: StyleSheet.hairlineWidth,
162
- ...Shadow.Light,
163
- },
164
- gradient: {
165
- position: 'absolute',
166
- top: 0,
167
- left: 0,
168
- right: 0,
169
- overflow: 'hidden',
170
- },
171
- // banner image keeps its 375:154 shape, the gradient box clips it
172
- gradientFill: {
173
- width: '100%',
174
- position: 'absolute',
175
- aspectRatio: 375 / 154,
176
- },
177
- });
178
-
179
- export { DuoGlassBackground, DuoScrollGradient };
@@ -1,42 +0,0 @@
1
- import React, { createContext, useContext } from 'react';
2
- import { StackHeaderProps, Header } from '@react-navigation/stack';
3
- import Animated, {
4
- SharedValue,
5
- useAnimatedStyle,
6
- } from 'react-native-reanimated';
7
-
8
- // how far the header is pushed above the top edge
9
- const DuoHeaderOffsetContext = createContext<SharedValue<number> | undefined>(
10
- undefined,
11
- );
12
-
13
- /**
14
- * iPhone Duo header that leaves with the content and comes back when scrolling back
15
- */
16
- const DuoQuickReturnHeader: React.FC<
17
- StackHeaderProps & { offset: SharedValue<number> }
18
- > = ({ offset, ...props }) => {
19
- const style = useAnimatedStyle(() => ({
20
- transform: [{ translateY: -offset.value }],
21
- }));
22
-
23
- return (
24
- <DuoHeaderOffsetContext.Provider value={offset}>
25
- <Animated.View pointerEvents={'box-none'} style={style}>
26
- <Header {...props} />
27
- </Animated.View>
28
- </DuoHeaderOffsetContext.Provider>
29
- );
30
- };
31
-
32
- /**
33
- * side bar controls render inside the header; this cancels its offset so they stay put
34
- */
35
- const useSideBarCounterStyle = () => {
36
- const offset = useContext(DuoHeaderOffsetContext);
37
- return useAnimatedStyle(() => ({
38
- transform: [{ translateY: offset ? offset.value : 0 }],
39
- }));
40
- };
41
-
42
- export { DuoQuickReturnHeader, useSideBarCounterStyle };
@@ -1,11 +0,0 @@
1
- import type React from 'react';
2
-
3
- // native glass is optional so apps without @momo-kits/glass-view still bundle
4
- let GlassView: React.ComponentType<any> | undefined;
5
- try {
6
- GlassView = require('@momo-kits/glass-view').GlassView;
7
- } catch {
8
- GlassView = undefined;
9
- }
10
-
11
- export { GlassView };
@@ -1,12 +0,0 @@
1
- import type React from 'react';
2
-
3
- // native observer is optional so apps without it in @momo-kits/reserved-regions still bundle
4
- let ScrollObserverView: React.ComponentType<any> | undefined;
5
- try {
6
- ScrollObserverView =
7
- require('@momo-kits/reserved-regions').ScrollObserverView;
8
- } catch {
9
- ScrollObserverView = undefined;
10
- }
11
-
12
- export { ScrollObserverView };
@@ -1,139 +0,0 @@
1
- import React, { ReactNode, useContext } from 'react';
2
- import {
3
- StyleProp,
4
- StyleSheet,
5
- TouchableOpacity,
6
- View,
7
- ViewStyle,
8
- } from 'react-native';
9
- import { NavigationButtonProps } from '../types';
10
- import { ApplicationContext } from '../../Context';
11
- import { Colors, Radius, Shadow, Spacing } from '../../Consts';
12
- import { Icon } from '../../Icon';
13
- import { BadgeDot } from '../../Badge';
14
- import { DuoMetrics } from '../../Layout/utils';
15
-
16
- const SIDE_BAR_ICON_SIZE = 24;
17
-
18
- /**
19
- * glass material for side bar controls: dark glass under light icons (banner headers),
20
- * light glass otherwise
21
- */
22
- const getGlassStyle = (tintColor?: string) =>
23
- tintColor === Colors.black_01 || tintColor === 'white'
24
- ? styles.glassDark
25
- : styles.glassLight;
26
-
27
- /**
28
- * 48pt glass circle used for a single control in the iPhone Duo side bar
29
- */
30
- const SideBarButton: React.FC<NavigationButtonProps> = ({
31
- icon,
32
- tintColor,
33
- showBadge = false,
34
- style,
35
- ...props
36
- }) => {
37
- const { theme } = useContext(ApplicationContext);
38
-
39
- return (
40
- <View>
41
- <TouchableOpacity
42
- {...props}
43
- style={[styles.control, getGlassStyle(tintColor), style]}
44
- >
45
- <Icon
46
- source={icon}
47
- color={tintColor ?? theme.colors.text.default}
48
- size={SIDE_BAR_ICON_SIZE}
49
- />
50
- </TouchableOpacity>
51
- {showBadge && <BadgeDot size={'small'} style={styles.badgeDot} />}
52
- </View>
53
- );
54
- };
55
-
56
- /**
57
- * 48pt glass capsule grouping related controls, one item per 50pt, laid out vertically
58
- * in the side bar or horizontally on a header row
59
- */
60
- const SideBarGroup: React.FC<{
61
- tintColor?: string;
62
- horizontal?: boolean;
63
- style?: StyleProp<ViewStyle>;
64
- children: ReactNode;
65
- }> = ({ tintColor, horizontal = false, style, children }) => {
66
- const count = React.Children.toArray(children).length;
67
- const length =
68
- count > 1 ? count * DuoMetrics.itemPitch : DuoMetrics.controlSize;
69
-
70
- return (
71
- <View
72
- style={[
73
- styles.control,
74
- getGlassStyle(tintColor),
75
- horizontal
76
- ? { width: length, flexDirection: 'row' }
77
- : { height: length },
78
- style,
79
- ]}
80
- >
81
- {children}
82
- </View>
83
- );
84
- };
85
-
86
- /**
87
- * icon item inside a SideBarGroup
88
- */
89
- const SideBarGroupItem: React.FC<NavigationButtonProps> = ({
90
- icon,
91
- tintColor,
92
- ...props
93
- }) => {
94
- const { theme } = useContext(ApplicationContext);
95
-
96
- return (
97
- <TouchableOpacity {...props} style={styles.groupItem}>
98
- <Icon
99
- source={icon}
100
- color={tintColor ?? theme.colors.text.default}
101
- size={SIDE_BAR_ICON_SIZE}
102
- />
103
- </TouchableOpacity>
104
- );
105
- };
106
-
107
- const styles = StyleSheet.create({
108
- control: {
109
- width: DuoMetrics.controlSize,
110
- height: DuoMetrics.controlSize,
111
- borderRadius: Radius.XL,
112
- justifyContent: 'center',
113
- alignItems: 'center',
114
- },
115
- glassLight: {
116
- backgroundColor: Colors.black_01 + 'D9',
117
- borderWidth: StyleSheet.hairlineWidth,
118
- borderColor: Colors.black_20 + '14',
119
- ...Shadow.Light,
120
- },
121
- glassDark: {
122
- backgroundColor: Colors.black_20 + '80',
123
- borderWidth: StyleSheet.hairlineWidth,
124
- borderColor: Colors.black_01 + '33',
125
- },
126
- groupItem: {
127
- flex: 1,
128
- alignSelf: 'stretch',
129
- justifyContent: 'center',
130
- alignItems: 'center',
131
- },
132
- badgeDot: {
133
- position: 'absolute',
134
- top: Spacing.XXS,
135
- right: Spacing.XXS,
136
- },
137
- });
138
-
139
- export { SideBarButton, SideBarGroup, SideBarGroupItem };
@@ -1,29 +0,0 @@
1
- import { Dimensions, Platform, ScaledSize } from 'react-native';
2
- import type { EdgeInsets } from 'react-native-safe-area-context';
3
-
4
- // widest short side of any single-display iPhone
5
- const IPHONE_MAX_SHORT_SIDE = 440;
6
-
7
- // iPhone Duo reports idiom "phone"; 'screen' stays on the outer display, so it
8
- // still matches when Split View narrows the window
9
- const isIPhoneDuo = () => {
10
- if (Platform.OS !== 'ios' || Platform.isPad) {
11
- return false;
12
- }
13
- const shortSide = (size: ScaledSize) => Math.min(size.width, size.height);
14
- return (
15
- Math.max(
16
- shortSide(Dimensions.get('screen')),
17
- shortSide(Dimensions.get('window')),
18
- ) > IPHONE_MAX_SHORT_SIDE
19
- );
20
- };
21
-
22
- // portrait side status bar is about twice as tall as its inset is wide
23
- const getDuoTopInset = (insets: EdgeInsets, width: number, height: number) =>
24
- Math.max(
25
- insets.top,
26
- height > width ? Math.max(insets.left, insets.right) * 2 : 0,
27
- );
28
-
29
- export { isIPhoneDuo, getDuoTopInset };
@@ -1,47 +0,0 @@
1
- import React, { createContext, ReactNode, useState } from 'react';
2
- import { Platform, StyleSheet } from 'react-native';
3
-
4
- export type ReservedRegion = {
5
- x: number;
6
- y: number;
7
- width: number;
8
- height: number;
9
- };
10
-
11
- // native probe is optional so apps without @momo-kits/reserved-regions still bundle
12
- let ReservedRegionsView: React.ComponentType<any> | undefined;
13
- try {
14
- ReservedRegionsView =
15
- require('@momo-kits/reserved-regions').ReservedRegionsView;
16
- } catch {
17
- ReservedRegionsView = undefined;
18
- }
19
-
20
- /**
21
- * screen regions the system currently occludes, in window coordinates
22
- */
23
- const ReservedRegionsContext = createContext<ReservedRegion[]>([]);
24
-
25
- /**
26
- * tracks the system occlusion regions (iOS 27.1+), e.g. the iPhone Duo status bar
27
- * cluster growing for a call or a Live Activity
28
- */
29
- const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
30
- children,
31
- }) => {
32
- const [regions, setRegions] = useState<ReservedRegion[]>([]);
33
-
34
- return (
35
- <ReservedRegionsContext.Provider value={regions}>
36
- {children}
37
- {Platform.OS === 'ios' && !!ReservedRegionsView && (
38
- <ReservedRegionsView
39
- style={StyleSheet.absoluteFill}
40
- onRegionsChange={setRegions}
41
- />
42
- )}
43
- </ReservedRegionsContext.Provider>
44
- );
45
- };
46
-
47
- export { ReservedRegionsContext, ReservedRegionsProvider };
package/Popup/utils.ts DELETED
@@ -1,32 +0,0 @@
1
- import { Dimensions, useWindowDimensions } from 'react-native';
2
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
3
- import { Spacing } from '../Consts';
4
- import { getDuoTopInset, isIPhoneDuo } from '../Application/device';
5
-
6
- // width of a system alert on the iPhone Duo inner display, in both orientations
7
- const DUO_POPUP_MAX_WIDTH = 440;
8
-
9
- const usePopupWidth = () => {
10
- const windowSize = useWindowDimensions();
11
- if (!isIPhoneDuo()) {
12
- return Dimensions.get('window').width - Spacing.XL * 2;
13
- }
14
- return Math.min(windowSize.width - Spacing.XL * 2, DUO_POPUP_MAX_WIDTH);
15
- };
16
-
17
- // room left for a popup on iPhone Duo, where landscape is too short for it
18
- const usePopupMaxHeight = () => {
19
- const insets = useSafeAreaInsets();
20
- const { width, height } = useWindowDimensions();
21
- if (!isIPhoneDuo()) {
22
- return undefined;
23
- }
24
- return (
25
- height -
26
- getDuoTopInset(insets, width, height) -
27
- insets.bottom -
28
- Spacing.XL * 2
29
- );
30
- };
31
-
32
- export { usePopupWidth, usePopupMaxHeight };