@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
package/Layout/layoutMetrics.ts
CHANGED
|
@@ -1,66 +1,57 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Header geometry
|
|
3
|
+
* - barHeight: navigation bar row below the status bar
|
|
4
|
+
* - iPhoneMinHeight: header never shorter than this on iPhone (landscape, iPhone Duo)
|
|
5
|
+
* - statusBarMinInset: a smaller top inset means the status bar is not on the top edge
|
|
6
|
+
* - largeWidth / largeMargin: @react-navigation/elements Header gives its content row
|
|
7
|
+
* 5pt side margins on iOS from 414pt wide
|
|
8
|
+
*/
|
|
2
9
|
export const HeaderMetrics = {
|
|
3
|
-
/** Navigation bar row below the status bar. */
|
|
4
10
|
barHeight: 52,
|
|
5
|
-
/** Header never shorter than this on iPhone (landscape, iPhone Duo). */
|
|
6
11
|
iPhoneMinHeight: 72,
|
|
7
|
-
/** A smaller top inset means the status bar is not on the top edge. */
|
|
8
12
|
statusBarMinInset: 20,
|
|
9
|
-
/**
|
|
10
|
-
* From this width @react-navigation/elements Header gives its content row
|
|
11
|
-
* `largeMargin` (5pt) side margins on iOS.
|
|
12
|
-
*/
|
|
13
13
|
largeWidth: 414,
|
|
14
14
|
largeMargin: 5,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* iPhone Duo geometry, iOS 27.1 (Xcode 27.1 simulator)
|
|
19
|
+
* - maxAspectRatio: both displays are near 1.42 (inner) and 1.45 (outer), every slab
|
|
20
|
+
* iPhone is 1.77 or taller
|
|
21
|
+
* - innerMinHeight: window height from which the inner display is in use (outer display
|
|
22
|
+
* is 466pt tall in landscape, inner display 669pt)
|
|
23
|
+
* - controlSize / itemPitch: vertical bar of 48pt circular controls, item groups as 48pt
|
|
24
|
+
* wide capsules at a 50pt pitch, 12pt from the strip's inner edge and 12pt apart
|
|
25
|
+
* - firstItemTop: with the status bar in the strip, the first control sits under the
|
|
26
|
+
* camera, clock and connectivity cluster (cluster ends near y 151 on the outer display,
|
|
27
|
+
* y 98 on the inner display in landscape)
|
|
28
|
+
* - outerHeaderHeight / outerCameraCenterY: on the outer display in portrait the header
|
|
29
|
+
* is 4pt taller than iPhoneMinHeight and its title lines up with the camera punchout
|
|
30
|
+
* (y 24–66 at the top of the strip)
|
|
31
|
+
* - edgeInset: on the outer display in landscape the strip holds no status bar, so its
|
|
32
|
+
* controls start this far from the top and stay this far above the bottom, unless a
|
|
33
|
+
* reserved region (the camera at either end) asks for more
|
|
34
|
+
* - innerStatusBarCenterY / innerStatusBarWidth: on the inner display in portrait the
|
|
35
|
+
* clock and connectivity cluster sits at the top right corner (x 550–643 of 669, clock
|
|
36
|
+
* digits centered on y 47); its reserved region, when reported, is taller than the glyphs
|
|
37
|
+
* - floatingTabBarHeight / floatingTabBarMargin / floatingTabBarInsetOverlap: the host
|
|
38
|
+
* app's iOS 26 system tab bar on the inner display in portrait, a 62pt Liquid Glass pill
|
|
39
|
+
* sized to its tabs with the QR as a pinned 62pt platter beside it, centered and at
|
|
40
|
+
* least 20pt from the side edges; it overlaps the home indicator inset by 13pt (21pt
|
|
41
|
+
* above the screen bottom with a 34pt inset)
|
|
42
|
+
*/
|
|
18
43
|
export const DuoMetrics = {
|
|
19
|
-
/**
|
|
20
|
-
* Both displays are near 1.42 (inner) and 1.45 (outer); every slab iPhone is
|
|
21
|
-
* 1.77 or taller.
|
|
22
|
-
*/
|
|
23
44
|
maxAspectRatio: 1.6,
|
|
24
|
-
/**
|
|
25
|
-
* Window height from which the inner display is in use (outer display is
|
|
26
|
-
* 466pt tall in landscape, inner display 669pt).
|
|
27
|
-
*/
|
|
28
45
|
innerMinHeight: 600,
|
|
29
|
-
/**
|
|
30
|
-
* Vertical bar of 48pt circular controls; item groups are 48pt wide capsules
|
|
31
|
-
* at a 50pt pitch, 12pt from the strip's inner edge and 12pt apart.
|
|
32
|
-
*/
|
|
33
46
|
controlSize: 48,
|
|
34
47
|
itemPitch: 50,
|
|
35
|
-
/**
|
|
36
|
-
* With the status bar in the strip, the first control sits under the camera,
|
|
37
|
-
* clock and connectivity cluster (ends near y 151 outer, y 98 inner
|
|
38
|
-
* landscape).
|
|
39
|
-
*/
|
|
40
48
|
firstItemTop: 170,
|
|
41
49
|
firstItemTopLandscape: 120,
|
|
42
|
-
/**
|
|
43
|
-
* Outer display in portrait: the header is 4pt taller than iPhoneMinHeight
|
|
44
|
-
* and its title lines up with the camera punchout (y 24–66 at the strip's
|
|
45
|
-
* top).
|
|
46
|
-
*/
|
|
47
50
|
outerHeaderHeight: 76,
|
|
48
51
|
outerCameraCenterY: 45,
|
|
49
|
-
/**
|
|
50
|
-
* Outer display in landscape: no status bar in the strip, so controls keep
|
|
51
|
-
* this gap from both ends unless a reserved region (the camera) needs more.
|
|
52
|
-
*/
|
|
53
52
|
edgeInset: 24,
|
|
54
|
-
/**
|
|
55
|
-
* Inner display in portrait: clock and connectivity cluster at the top right
|
|
56
|
-
* (x 550–643 of 669, digits on y 47); a reported reserved region is taller.
|
|
57
|
-
*/
|
|
58
53
|
innerStatusBarCenterY: 47,
|
|
59
54
|
innerStatusBarWidth: 120,
|
|
60
|
-
/**
|
|
61
|
-
* Host app's iOS 26 tab bar, inner display in portrait: a 62pt Liquid Glass
|
|
62
|
-
* pill plus 62pt QR platter, ≥ 20pt from the sides, 13pt over the home inset.
|
|
63
|
-
*/
|
|
64
55
|
floatingTabBarHeight: 62,
|
|
65
56
|
floatingTabBarMargin: 20,
|
|
66
57
|
floatingTabBarInsetOverlap: 13,
|
package/Layout/screenHeader.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
import { StatusBar, StatusBarStyle } from 'react-native';
|
|
3
3
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
|
4
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
4
5
|
import Navigation from '../Application/Navigation';
|
|
5
6
|
import { AnimatedHeader, SearchHeaderProps } from '../Application/types';
|
|
6
7
|
import { exportHeaderTitle } from '../Application/navigationOptions';
|
|
@@ -12,6 +13,11 @@ import {
|
|
|
12
13
|
SearchHeader,
|
|
13
14
|
} from '../Application';
|
|
14
15
|
import { DuoGlassBackground } from '../Application/Components/DuoGlassBackground';
|
|
16
|
+
import {
|
|
17
|
+
type HeaderContentLayout,
|
|
18
|
+
useHeaderFrameOptions,
|
|
19
|
+
useHeaderLayout,
|
|
20
|
+
} from '../Application/headerLayout';
|
|
15
21
|
import { getHeaderMargin } from './utils';
|
|
16
22
|
import { ANIMATED_BANNER_HEIGHT } from './screenTypes';
|
|
17
23
|
import { HeaderType } from './types';
|
|
@@ -29,6 +35,31 @@ type ScreenHeaderParams = {
|
|
|
29
35
|
gradientColor?: string;
|
|
30
36
|
headerBackground?: string;
|
|
31
37
|
layoutOffset: number;
|
|
38
|
+
contentLayout?: HeaderContentLayout;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Transparent header for an `inputSearchProps` Screen: its title fades out over the
|
|
43
|
+
* first 50pt of scroll while the search collapses into the bar.
|
|
44
|
+
*/
|
|
45
|
+
const getSearchTitleOptions = (
|
|
46
|
+
sharedValue: SharedValue<number>,
|
|
47
|
+
): StackNavigationOptions => {
|
|
48
|
+
return {
|
|
49
|
+
headerShown: true,
|
|
50
|
+
headerTransparent: true,
|
|
51
|
+
headerBackground: () => null,
|
|
52
|
+
headerTitle: (props: any) => (
|
|
53
|
+
<HeaderTitle
|
|
54
|
+
{...props}
|
|
55
|
+
interpolate={{
|
|
56
|
+
inputRange: [0, 50],
|
|
57
|
+
outputRange: [1, 0],
|
|
58
|
+
}}
|
|
59
|
+
animatedValue={sharedValue}
|
|
60
|
+
/>
|
|
61
|
+
),
|
|
62
|
+
};
|
|
32
63
|
};
|
|
33
64
|
|
|
34
65
|
/**
|
|
@@ -48,9 +79,12 @@ export const useScreenHeader = ({
|
|
|
48
79
|
gradientColor,
|
|
49
80
|
headerBackground,
|
|
50
81
|
layoutOffset,
|
|
82
|
+
contentLayout,
|
|
51
83
|
}: ScreenHeaderParams) => {
|
|
52
84
|
const currentTint = useRef<string | undefined>(undefined);
|
|
53
85
|
const appliedHeaderMode = useRef<string | undefined>(undefined);
|
|
86
|
+
const headerLayout = useHeaderLayout(contentLayout);
|
|
87
|
+
const frameOptions = useHeaderFrameOptions(contentLayout);
|
|
54
88
|
|
|
55
89
|
// keepTitle leaves a title the screen set itself when the same type is re-applied
|
|
56
90
|
const setHeaderType = useCallback(
|
|
@@ -79,21 +113,7 @@ export const useScreenHeader = ({
|
|
|
79
113
|
headerTitle: (props: any) => <HeaderTitle {...props} />,
|
|
80
114
|
};
|
|
81
115
|
if (inputSearchProps) {
|
|
82
|
-
options =
|
|
83
|
-
headerShown: true,
|
|
84
|
-
headerTransparent: true,
|
|
85
|
-
headerBackground: () => null,
|
|
86
|
-
headerTitle: (props: any) => (
|
|
87
|
-
<HeaderTitle
|
|
88
|
-
{...props}
|
|
89
|
-
interpolate={{
|
|
90
|
-
inputRange: [0, 50],
|
|
91
|
-
outputRange: [1, 0],
|
|
92
|
-
}}
|
|
93
|
-
animatedValue={sharedValue}
|
|
94
|
-
/>
|
|
95
|
-
),
|
|
96
|
-
};
|
|
116
|
+
options = getSearchTitleOptions(sharedValue);
|
|
97
117
|
}
|
|
98
118
|
break;
|
|
99
119
|
|
|
@@ -127,21 +147,7 @@ export const useScreenHeader = ({
|
|
|
127
147
|
headerTitle: (props: any) => <HeaderTitle {...props} />,
|
|
128
148
|
};
|
|
129
149
|
if (inputSearchProps) {
|
|
130
|
-
options =
|
|
131
|
-
headerShown: true,
|
|
132
|
-
headerTransparent: true,
|
|
133
|
-
headerBackground: () => null,
|
|
134
|
-
headerTitle: (props: any) => (
|
|
135
|
-
<HeaderTitle
|
|
136
|
-
{...props}
|
|
137
|
-
interpolate={{
|
|
138
|
-
inputRange: [0, 50],
|
|
139
|
-
outputRange: [1, 0],
|
|
140
|
-
}}
|
|
141
|
-
animatedValue={sharedValue}
|
|
142
|
-
/>
|
|
143
|
-
),
|
|
144
|
-
};
|
|
150
|
+
options = getSearchTitleOptions(sharedValue);
|
|
145
151
|
}
|
|
146
152
|
}
|
|
147
153
|
|
|
@@ -149,7 +155,11 @@ export const useScreenHeader = ({
|
|
|
149
155
|
delete options.headerTitle;
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
navigation?.instance?.setOptions({
|
|
158
|
+
navigation?.instance?.setOptions({
|
|
159
|
+
header: undefined,
|
|
160
|
+
...frameOptions,
|
|
161
|
+
...options,
|
|
162
|
+
});
|
|
153
163
|
},
|
|
154
164
|
[
|
|
155
165
|
gradientColor,
|
|
@@ -160,6 +170,7 @@ export const useScreenHeader = ({
|
|
|
160
170
|
useShadowHeader,
|
|
161
171
|
hasSideBar,
|
|
162
172
|
quickReturnOptions,
|
|
173
|
+
frameOptions,
|
|
163
174
|
],
|
|
164
175
|
);
|
|
165
176
|
|
|
@@ -207,7 +218,7 @@ export const useScreenHeader = ({
|
|
|
207
218
|
};
|
|
208
219
|
}
|
|
209
220
|
|
|
210
|
-
navigation?.instance?.setOptions(options);
|
|
221
|
+
navigation?.instance?.setOptions({ ...frameOptions, ...options });
|
|
211
222
|
},
|
|
212
223
|
[
|
|
213
224
|
gradientColor,
|
|
@@ -218,32 +229,23 @@ export const useScreenHeader = ({
|
|
|
218
229
|
hasSideBar,
|
|
219
230
|
layoutOffset,
|
|
220
231
|
quickReturnOptions,
|
|
232
|
+
frameOptions,
|
|
221
233
|
],
|
|
222
234
|
);
|
|
223
235
|
|
|
224
236
|
const setAnimatedSearch = useCallback(() => {
|
|
225
|
-
const options
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
interpolate={{
|
|
233
|
-
inputRange: [0, 50],
|
|
234
|
-
outputRange: [1, 0],
|
|
235
|
-
}}
|
|
236
|
-
animatedValue={sharedValue}
|
|
237
|
-
/>
|
|
238
|
-
),
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
navigation?.instance?.setOptions({ header: undefined, ...options });
|
|
242
|
-
}, [navigation?.instance, sharedValue]);
|
|
237
|
+
const options = getSearchTitleOptions(sharedValue);
|
|
238
|
+
navigation?.instance?.setOptions({
|
|
239
|
+
header: undefined,
|
|
240
|
+
...frameOptions,
|
|
241
|
+
...options,
|
|
242
|
+
});
|
|
243
|
+
}, [navigation?.instance, sharedValue, frameOptions]);
|
|
243
244
|
|
|
244
|
-
// the search field spans the content, clear of the side bar
|
|
245
|
+
// the search field spans the content (or the stretch frame), clear of the side bar
|
|
246
|
+
// and status bar clusters
|
|
245
247
|
const setSearchHeader = (params: SearchHeaderProps) => {
|
|
246
|
-
let searchWidth =
|
|
248
|
+
let searchWidth = headerLayout.frameWidth;
|
|
247
249
|
let searchLeft = Spacing.M;
|
|
248
250
|
if (layout.sideBar) {
|
|
249
251
|
searchWidth = layout.contentWidth - Spacing.M * 2;
|
|
@@ -254,6 +256,7 @@ export const useScreenHeader = ({
|
|
|
254
256
|
searchWidth -= clearance.left + clearance.right;
|
|
255
257
|
}
|
|
256
258
|
const options: StackNavigationOptions = {
|
|
259
|
+
...frameOptions,
|
|
257
260
|
headerRight: undefined,
|
|
258
261
|
headerTitleContainerStyle: {
|
|
259
262
|
width: searchWidth,
|
package/Layout/screenTypes.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { FloatingButtonProps } from './FloatingButton';
|
|
10
10
|
import { HeaderType } from './types';
|
|
11
11
|
import { InputRef } from '../Input';
|
|
12
|
+
import type { HeaderContentLayout } from '../Application/headerLayout';
|
|
12
13
|
|
|
13
14
|
export const ANIMATED_BANNER_HEIGHT = 210;
|
|
14
15
|
|
|
@@ -59,8 +60,9 @@ export interface ScreenProps extends ViewProps {
|
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
62
|
* Optional. If `true`, the footer is inset by the standard horizontal margin
|
|
62
|
-
* (12pt each side)
|
|
63
|
-
*
|
|
63
|
+
* (12pt each side). Set `false` for a footer that should span the full width —
|
|
64
|
+
* e.g. one that already carries its own horizontal padding.
|
|
65
|
+
* Defaults to `true`, on every platform.
|
|
64
66
|
*/
|
|
65
67
|
marginFooter?: boolean;
|
|
66
68
|
|
|
@@ -76,10 +78,17 @@ export interface ScreenProps extends ViewProps {
|
|
|
76
78
|
layoutOffset?: -8 | -24 | -56 | number;
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
|
-
* Optional. Width of the right header. Defaults to the toolkit width (
|
|
81
|
+
* Optional. Width of the right header. Defaults to the toolkit width (84), or 0 when
|
|
80
82
|
* iPhone Duo floats the toolkit into its side bar.
|
|
81
83
|
*/
|
|
82
|
-
headerRightWidth?: 0 |
|
|
84
|
+
headerRightWidth?: 0 | 84 | 124 | number;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Optional. `stretch` centers back, title or search and the right cluster of the
|
|
88
|
+
* header in a 600pt frame on wide windows (foldable inner display, tablet); the
|
|
89
|
+
* background and status bar stay full width. Defaults to `grid`.
|
|
90
|
+
*/
|
|
91
|
+
contentLayout?: HeaderContentLayout;
|
|
83
92
|
|
|
84
93
|
/**
|
|
85
94
|
* Optional. Props for the floating action button.
|
|
@@ -13,8 +13,6 @@ import { ApplicationContext, MiniAppContext } from '../Context';
|
|
|
13
13
|
|
|
14
14
|
const INDICATOR_WIDTH = 24;
|
|
15
15
|
const INDICATOR_CONTAINER_WIDTH = 72;
|
|
16
|
-
// one frame at 60 fps
|
|
17
|
-
const SCROLL_EVENT_THROTTLE = 16;
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* Horizontal scroll view with a small indicator that tracks the scroll offset.
|
|
@@ -33,7 +31,7 @@ const PaginationScroll: FC<ScrollIndicatorProps> = ({
|
|
|
33
31
|
|
|
34
32
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
35
33
|
|
|
36
|
-
const
|
|
34
|
+
const onScroll = useAnimatedScrollHandler({
|
|
37
35
|
onScroll: (event) => {
|
|
38
36
|
left.value = event.contentOffset.x;
|
|
39
37
|
},
|
|
@@ -56,8 +54,8 @@ const PaginationScroll: FC<ScrollIndicatorProps> = ({
|
|
|
56
54
|
return (
|
|
57
55
|
<Animated.ScrollView
|
|
58
56
|
ref={scrollViewRef as any}
|
|
59
|
-
onScroll={
|
|
60
|
-
scrollEventThrottle={
|
|
57
|
+
onScroll={onScroll}
|
|
58
|
+
scrollEventThrottle={16}
|
|
61
59
|
alwaysBounceHorizontal={false}
|
|
62
60
|
showsHorizontalScrollIndicator={false}
|
|
63
61
|
horizontal
|
package/Pagination/types.ts
CHANGED
|
@@ -3,9 +3,11 @@ import {Ref} from 'react';
|
|
|
3
3
|
|
|
4
4
|
export type PaginationProps = {
|
|
5
5
|
/**
|
|
6
|
-
* Optional.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Optional. Defines the visual style of the Pagination component.
|
|
7
|
+
* - 'default': Represents the default style.
|
|
8
|
+
* - 'black_white': Represents dots on a translucent dark background.
|
|
9
|
+
* - 'number': Represents a style where pages are represented by numbers.
|
|
10
|
+
* - 'scroll': Rendered as 'default'; use `PaginationScroll` instead.
|
|
9
11
|
*/
|
|
10
12
|
type?: 'default' | 'black_white' | 'number' | 'scroll';
|
|
11
13
|
|
package/Popup/PopupNotify.tsx
CHANGED
|
@@ -12,9 +12,6 @@ import { ModalIconButton } from '../Application/ModalIconButton';
|
|
|
12
12
|
import { splitHighlightText } from './utils';
|
|
13
13
|
|
|
14
14
|
const MAX_ROW_TITLE_LENGTH = 12;
|
|
15
|
-
const DESCRIPTION_MAX_HEIGHT = 172;
|
|
16
|
-
const MAX_DESCRIPTION_LINES = 8.5;
|
|
17
|
-
const IMAGE_ASPECT_RATIO = 1.7777;
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
17
|
* Notify popup body: optional image, title, description, error code and one or
|
|
@@ -35,10 +32,10 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
35
32
|
}) => {
|
|
36
33
|
const context = useContext<any>(MiniAppContext);
|
|
37
34
|
const { theme, navigator, translate } = useContext(ApplicationContext);
|
|
38
|
-
const [
|
|
35
|
+
const [scrollContent, setScrollContent] = useState(false);
|
|
39
36
|
const { isFoldable, popup } = useModalLayout();
|
|
40
37
|
const { width: popupWidth, maxHeight, radius } = popup;
|
|
41
|
-
const scaledMaxHeight = useScaleSize(
|
|
38
|
+
const scaledMaxHeight = useScaleSize(172);
|
|
42
39
|
|
|
43
40
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
44
41
|
const closeAction = useRef('touch_outside');
|
|
@@ -48,12 +45,12 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
48
45
|
// foldables cap the popup height, the description takes what is left
|
|
49
46
|
Description = ScrollView;
|
|
50
47
|
descriptionStyle = styles.shrink;
|
|
51
|
-
} else if (
|
|
48
|
+
} else if (scrollContent) {
|
|
52
49
|
Description = ScrollView;
|
|
53
50
|
descriptionStyle = { maxHeight: scaledMaxHeight };
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
const
|
|
53
|
+
const onAction = (callback?: () => void) => {
|
|
57
54
|
if (typeof onRequestClose === 'function') {
|
|
58
55
|
onRequestClose?.(callback);
|
|
59
56
|
} else {
|
|
@@ -75,7 +72,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
75
72
|
size="medium"
|
|
76
73
|
onPress={() => {
|
|
77
74
|
closeAction.current = 'button_action';
|
|
78
|
-
|
|
75
|
+
onAction(secondary?.onPress);
|
|
79
76
|
}}
|
|
80
77
|
accessibilityLabel={'btn_popup_cancel'}
|
|
81
78
|
/>
|
|
@@ -89,7 +86,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
89
86
|
size="medium"
|
|
90
87
|
onPress={() => {
|
|
91
88
|
closeAction.current = 'button_action';
|
|
92
|
-
|
|
89
|
+
onAction(primary.onPress);
|
|
93
90
|
}}
|
|
94
91
|
accessibilityLabel={'btn_popup_allow'}
|
|
95
92
|
/>
|
|
@@ -106,7 +103,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
106
103
|
size="medium"
|
|
107
104
|
onPress={() => {
|
|
108
105
|
closeAction.current = 'button_action';
|
|
109
|
-
|
|
106
|
+
onAction(primary.onPress);
|
|
110
107
|
}}
|
|
111
108
|
/>
|
|
112
109
|
{!!secondary?.title && (
|
|
@@ -119,7 +116,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
119
116
|
size="medium"
|
|
120
117
|
onPress={() => {
|
|
121
118
|
closeAction.current = 'button_action';
|
|
122
|
-
|
|
119
|
+
onAction(secondary?.onPress);
|
|
123
120
|
}}
|
|
124
121
|
/>
|
|
125
122
|
</>
|
|
@@ -153,7 +150,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
153
150
|
return text;
|
|
154
151
|
}
|
|
155
152
|
return segments.map((segment, index) => {
|
|
156
|
-
if (segment.
|
|
153
|
+
if (segment.highlight) {
|
|
157
154
|
return (
|
|
158
155
|
<Text key={index} typography={'header_s_semibold'}>
|
|
159
156
|
{segment.text}
|
|
@@ -175,7 +172,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
175
172
|
style={styles.iconClose}
|
|
176
173
|
onPress={() => {
|
|
177
174
|
closeAction.current = 'icon_close';
|
|
178
|
-
|
|
175
|
+
onAction(onIconClose);
|
|
179
176
|
}}
|
|
180
177
|
/>
|
|
181
178
|
);
|
|
@@ -192,9 +189,9 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
192
189
|
accessibilityLabel={'description_popup_notify'}
|
|
193
190
|
typography={'body_default_regular'}
|
|
194
191
|
onTextLayout={({ nativeEvent: { lines } }) => {
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
|
|
192
|
+
const enableScroll = lines.length > 8.5;
|
|
193
|
+
if (enableScroll !== scrollContent) {
|
|
194
|
+
setScrollContent(enableScroll);
|
|
198
195
|
}
|
|
199
196
|
}}
|
|
200
197
|
>
|
|
@@ -263,7 +260,7 @@ const styles = StyleSheet.create({
|
|
|
263
260
|
// clips the image and content to the card radius
|
|
264
261
|
card: { overflow: 'hidden' },
|
|
265
262
|
image: {
|
|
266
|
-
aspectRatio:
|
|
263
|
+
aspectRatio: 1.7777,
|
|
267
264
|
},
|
|
268
265
|
shrink: { flexShrink: 1 },
|
|
269
266
|
content: { padding: Spacing.XL },
|
package/Popup/PopupPromotion.tsx
CHANGED
|
@@ -9,7 +9,6 @@ import { useModalLayout } from '../Application/modalLayout';
|
|
|
9
9
|
|
|
10
10
|
const ASPECT_RATIO = 0.72;
|
|
11
11
|
const ICON_CLOSE_SIZE = 20;
|
|
12
|
-
const CLOSE_GLYPH_SIZE = 14;
|
|
13
12
|
// close button row under the image: marginTop + vertical padding + icon
|
|
14
13
|
const CLOSE_ROW_HEIGHT = Spacing.S * 3 + ICON_CLOSE_SIZE;
|
|
15
14
|
|
|
@@ -38,7 +37,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
|
|
|
38
37
|
|
|
39
38
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
40
39
|
|
|
41
|
-
const
|
|
40
|
+
const onAction = (callback?: () => void) => {
|
|
42
41
|
if (typeof onRequestClose === 'function') {
|
|
43
42
|
onRequestClose?.(callback);
|
|
44
43
|
} else {
|
|
@@ -54,7 +53,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
|
|
|
54
53
|
accessibilityLabel={'ic_popup_close'}
|
|
55
54
|
onPress={() => {
|
|
56
55
|
closeAction.current = 'icon_close';
|
|
57
|
-
|
|
56
|
+
onAction(onIconClose);
|
|
58
57
|
}}
|
|
59
58
|
style={styles.iconButton}
|
|
60
59
|
>
|
|
@@ -70,7 +69,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
|
|
|
70
69
|
<Icon
|
|
71
70
|
source="navigation_close"
|
|
72
71
|
color={theme.colors.background.surface}
|
|
73
|
-
size={
|
|
72
|
+
size={14}
|
|
74
73
|
/>
|
|
75
74
|
</View>
|
|
76
75
|
</TouchableOpacity>
|
|
@@ -83,7 +82,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
|
|
|
83
82
|
accessibilityLabel={'popup_promotion'}
|
|
84
83
|
style={[{ width: imageWidth }, showBaseLineDebug && styles.debugBaseLine]}
|
|
85
84
|
>
|
|
86
|
-
<TouchableOpacity onPress={() =>
|
|
85
|
+
<TouchableOpacity onPress={() => onAction(onActionClick)}>
|
|
87
86
|
<Image
|
|
88
87
|
source={{
|
|
89
88
|
uri: image,
|
package/Popup/types.ts
CHANGED
|
@@ -43,8 +43,10 @@ export type PopupNotifyProps = {
|
|
|
43
43
|
primary: PopupAction;
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* `buttonDirection`: Optional.
|
|
47
|
-
*
|
|
46
|
+
* `buttonDirection`: Optional. Represents the direction in which the action buttons are laid out.
|
|
47
|
+
* - 'row': Buttons are laid out horizontally.
|
|
48
|
+
* - 'column': Buttons are laid out vertically.
|
|
49
|
+
* - 'auto': The layout is automatically determined by title length.
|
|
48
50
|
*/
|
|
49
51
|
buttonDirection?: PopupActionDirection;
|
|
50
52
|
|
package/Popup/utils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type HighlightSegment = { text: string;
|
|
1
|
+
type HighlightSegment = { text: string; highlight: boolean };
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* True for cased-script letters, digits and `_`; caseless scripts (CJK, Thai)
|
|
@@ -46,15 +46,12 @@ const splitHighlightText = (
|
|
|
46
46
|
continue;
|
|
47
47
|
}
|
|
48
48
|
if (start > lastIndex) {
|
|
49
|
-
segments.push({
|
|
50
|
-
text: text.slice(lastIndex, start),
|
|
51
|
-
isHighlighted: false,
|
|
52
|
-
});
|
|
49
|
+
segments.push({ text: text.slice(lastIndex, start), highlight: false });
|
|
53
50
|
}
|
|
54
|
-
segments.push({ text: match[0],
|
|
51
|
+
segments.push({ text: match[0], highlight: true });
|
|
55
52
|
lastIndex = end;
|
|
56
53
|
}
|
|
57
|
-
segments.push({ text: text.slice(lastIndex),
|
|
54
|
+
segments.push({ text: text.slice(lastIndex), highlight: false });
|
|
58
55
|
return segments;
|
|
59
56
|
};
|
|
60
57
|
|
package/Skeleton/index.tsx
CHANGED
|
@@ -21,12 +21,6 @@ import { Colors, Styles } from '../Consts';
|
|
|
21
21
|
import styles from './styles';
|
|
22
22
|
import { ScreenContext, SkeletonContext } from '../Context';
|
|
23
23
|
|
|
24
|
-
const PRIMARY_COLOR = Colors.black_05;
|
|
25
|
-
const HIGHLIGHT_COLOR1 = Colors.black_05;
|
|
26
|
-
const HIGHLIGHT_COLOR2 = Colors.black_03;
|
|
27
|
-
const SHIMMER_DURATION = 1000;
|
|
28
|
-
const LAYOUT_ANIMATION_DURATION = 500;
|
|
29
|
-
|
|
30
24
|
/**
|
|
31
25
|
* Shimmering placeholder that fills its parent. Reports loading to the screen
|
|
32
26
|
* context while mounted.
|
|
@@ -34,6 +28,9 @@ const LAYOUT_ANIMATION_DURATION = 500;
|
|
|
34
28
|
const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
|
|
35
29
|
const screen = useContext<any>(ScreenContext);
|
|
36
30
|
const [width, setWidth] = useState(0);
|
|
31
|
+
const PRIMARY_COLOR = Colors.black_05;
|
|
32
|
+
const HIGHLIGHT_COLOR1 = Colors.black_05;
|
|
33
|
+
const HIGHLIGHT_COLOR2 = Colors.black_03;
|
|
37
34
|
const progress = useSharedValue(0);
|
|
38
35
|
|
|
39
36
|
const shimmerColors = [HIGHLIGHT_COLOR1, HIGHLIGHT_COLOR2, HIGHLIGHT_COLOR1];
|
|
@@ -42,7 +39,7 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
|
|
|
42
39
|
progress.value = 0;
|
|
43
40
|
progress.value = withRepeat(
|
|
44
41
|
withTiming(1, {
|
|
45
|
-
duration:
|
|
42
|
+
duration: 1000,
|
|
46
43
|
easing: Easing.linear,
|
|
47
44
|
}),
|
|
48
45
|
-1,
|
|
@@ -60,7 +57,7 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
|
|
|
60
57
|
return { transform: [{ translateX }] };
|
|
61
58
|
});
|
|
62
59
|
|
|
63
|
-
const
|
|
60
|
+
const onLayout = (newWidth: number) => {
|
|
64
61
|
if (newWidth !== width) {
|
|
65
62
|
setWidth(newWidth);
|
|
66
63
|
}
|
|
@@ -69,7 +66,7 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
|
|
|
69
66
|
return (
|
|
70
67
|
<View style={[styles.container, style]}>
|
|
71
68
|
<View
|
|
72
|
-
onLayout={e =>
|
|
69
|
+
onLayout={e => onLayout(e.nativeEvent.layout.width)}
|
|
73
70
|
style={[Styles.flex, { backgroundColor: PRIMARY_COLOR }]}
|
|
74
71
|
>
|
|
75
72
|
<Animated.View style={[styles.shimmer, shimmerStyle]}>
|
|
@@ -99,28 +96,28 @@ const SkeletonProvider: React.FC<{ loading: boolean; children: any }> = ({
|
|
|
99
96
|
loading = true,
|
|
100
97
|
children,
|
|
101
98
|
}) => {
|
|
102
|
-
const [
|
|
99
|
+
const [previous, setPrevious] = useState(loading);
|
|
103
100
|
|
|
104
101
|
useEffect(() => {
|
|
105
|
-
if (
|
|
102
|
+
if (previous !== loading) {
|
|
106
103
|
if (Platform.OS === 'android') {
|
|
107
104
|
if (UIManager.setLayoutAnimationEnabledExperimental) {
|
|
108
105
|
UIManager.setLayoutAnimationEnabledExperimental(true);
|
|
109
106
|
}
|
|
110
107
|
}
|
|
111
108
|
LayoutAnimation.configureNext({
|
|
112
|
-
duration:
|
|
109
|
+
duration: 500,
|
|
113
110
|
create: {
|
|
114
111
|
type: LayoutAnimation.Types.easeInEaseOut,
|
|
115
112
|
property: LayoutAnimation.Properties.opacity,
|
|
116
113
|
},
|
|
117
114
|
});
|
|
118
|
-
|
|
115
|
+
setPrevious(loading);
|
|
119
116
|
}
|
|
120
|
-
}, [loading,
|
|
117
|
+
}, [loading, previous]);
|
|
121
118
|
|
|
122
119
|
return (
|
|
123
|
-
<SkeletonContext.Provider value={{ loading:
|
|
120
|
+
<SkeletonContext.Provider value={{ loading: previous }}>
|
|
124
121
|
{children}
|
|
125
122
|
</SkeletonContext.Provider>
|
|
126
123
|
);
|