@momo-kits/foundation 0.168.1-beta.29 → 0.168.1-beta.30
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.tsx +10 -5
- package/Application/ModalScreen.tsx +2 -2
- package/Application/device.ts +18 -4
- package/Popup/PopupNotify.tsx +2 -27
- package/Popup/types.ts +0 -7
- package/Popup/utils.ts +5 -50
- package/package.json +1 -1
|
@@ -41,10 +41,10 @@ import Animated, {
|
|
|
41
41
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
42
42
|
import layoutStyles from '../Layout/styles';
|
|
43
43
|
import { DuoMetrics, InnerEdgeStyles, useScreenLayout } from '../Layout/utils';
|
|
44
|
-
import { getDuoTopInset,
|
|
44
|
+
import { getDuoTopInset, isFoldableDevice } from './device';
|
|
45
45
|
import { SideBarButton } from './Components/SideBarButton';
|
|
46
46
|
|
|
47
|
-
//
|
|
47
|
+
// foldable inner display: center the sheet instead of spanning the width
|
|
48
48
|
const DUO_REGULAR_MIN_WIDTH = 600;
|
|
49
49
|
|
|
50
50
|
const INDICATOR_HEIGHT = 16;
|
|
@@ -59,7 +59,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
59
59
|
const { theme, navigator } = useContext(ApplicationContext);
|
|
60
60
|
const context = useContext<any>(MiniAppContext);
|
|
61
61
|
const windowSize = useWindowDimensions();
|
|
62
|
-
const isDuo =
|
|
62
|
+
const isDuo = isFoldableDevice();
|
|
63
63
|
const isDuoCentered =
|
|
64
64
|
isDuo &&
|
|
65
65
|
Math.min(windowSize.width, windowSize.height) >= DUO_REGULAR_MIN_WIDTH;
|
|
@@ -91,6 +91,9 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
91
91
|
const isIOS = Platform.OS === 'ios';
|
|
92
92
|
const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
|
|
93
93
|
const keyboardOffset = heightHeader - bottomInset;
|
|
94
|
+
// Android shrinks the root for the keyboard but keeps the window height and
|
|
95
|
+
// fires no keyboardWill* events, so only the root can bound the sheet
|
|
96
|
+
const fitsResizedRoot = isDuo && !isIOS;
|
|
94
97
|
|
|
95
98
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
96
99
|
|
|
@@ -209,8 +212,8 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
209
212
|
}
|
|
210
213
|
|
|
211
214
|
/**
|
|
212
|
-
* emit dismiss event, once: folding or rotating
|
|
213
|
-
* window height while the sheet stays open
|
|
215
|
+
* emit dismiss event, once: folding or rotating a foldable device changes
|
|
216
|
+
* the window height while the sheet stays open
|
|
214
217
|
*/
|
|
215
218
|
useEffect(() => {
|
|
216
219
|
openAnimation();
|
|
@@ -473,6 +476,8 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
473
476
|
maxHeight: windowSize.height - topGap - keyboardLift,
|
|
474
477
|
marginBottom: keyboardLift,
|
|
475
478
|
},
|
|
479
|
+
fitsResizedRoot && styles.shrink,
|
|
480
|
+
fitsResizedRoot && { marginTop: topGap },
|
|
476
481
|
isDuoFloating && { marginHorizontal: Spacing.M },
|
|
477
482
|
sideColumn && {
|
|
478
483
|
minHeight:
|
|
@@ -18,7 +18,7 @@ import { Styles } from '../Consts';
|
|
|
18
18
|
import Navigation from './Navigation';
|
|
19
19
|
import { ModalParams } from './types';
|
|
20
20
|
import BottomSheet from './BottomSheet';
|
|
21
|
-
import { getDuoModalBounds,
|
|
21
|
+
import { getDuoModalBounds, isFoldableDevice } from './device';
|
|
22
22
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
23
23
|
|
|
24
24
|
const ModalScreen: React.FC<any> = props => {
|
|
@@ -42,7 +42,7 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
42
42
|
const Component = useRef(screen).current;
|
|
43
43
|
const insets = useSafeAreaInsets();
|
|
44
44
|
const windowSize = useWindowDimensions();
|
|
45
|
-
const isDuo =
|
|
45
|
+
const isDuo = isFoldableDevice();
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Fire auto_popup_displayed tracking event on mount
|
package/Application/device.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Dimensions, Platform, ScaledSize } from 'react-native';
|
|
1
|
+
import { Dimensions, NativeModules, Platform, ScaledSize } from 'react-native';
|
|
2
2
|
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
3
3
|
import { Spacing } from '../Consts';
|
|
4
4
|
|
|
@@ -23,6 +23,13 @@ const isIPhoneDuo = () => {
|
|
|
23
23
|
);
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
// reported by the host; hosts without the constant count as not foldable
|
|
27
|
+
const isAndroidFoldable = () =>
|
|
28
|
+
Platform.OS === 'android' &&
|
|
29
|
+
NativeModules?.FoldStateModule?.isFoldableDevice === true;
|
|
30
|
+
|
|
31
|
+
const isFoldableDevice = () => isIPhoneDuo() || isAndroidFoldable();
|
|
32
|
+
|
|
26
33
|
// portrait side status bar is about twice as tall as its inset is wide
|
|
27
34
|
const getDuoTopInset = (insets: EdgeInsets, width: number, height: number) =>
|
|
28
35
|
Math.max(
|
|
@@ -31,8 +38,9 @@ const getDuoTopInset = (insets: EdgeInsets, width: number, height: number) =>
|
|
|
31
38
|
);
|
|
32
39
|
|
|
33
40
|
/**
|
|
34
|
-
* Max modal size on
|
|
35
|
-
* toolbar strip, and capped so it stays a dialog on the
|
|
41
|
+
* Max modal size on foldable devices: inside the safe area, so clear of the
|
|
42
|
+
* side toolbar strip on iPhone Duo, and capped so it stays a dialog on the
|
|
43
|
+
* inner display.
|
|
36
44
|
*/
|
|
37
45
|
const getDuoModalBounds = (
|
|
38
46
|
insets: EdgeInsets,
|
|
@@ -49,4 +57,10 @@ const getDuoModalBounds = (
|
|
|
49
57
|
),
|
|
50
58
|
});
|
|
51
59
|
|
|
52
|
-
export {
|
|
60
|
+
export {
|
|
61
|
+
isIPhoneDuo,
|
|
62
|
+
isAndroidFoldable,
|
|
63
|
+
isFoldableDevice,
|
|
64
|
+
getDuoTopInset,
|
|
65
|
+
getDuoModalBounds,
|
|
66
|
+
};
|
package/Popup/PopupNotify.tsx
CHANGED
|
@@ -8,18 +8,12 @@ import { Image } from '../Image';
|
|
|
8
8
|
import { Text, useScaleSize } from '../Text';
|
|
9
9
|
import { PopupNotifyProps } from './types';
|
|
10
10
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
11
|
-
import {
|
|
12
|
-
getPopupRadius,
|
|
13
|
-
splitHighlightText,
|
|
14
|
-
usePopupMaxHeight,
|
|
15
|
-
usePopupWidth,
|
|
16
|
-
} from './utils';
|
|
11
|
+
import { getPopupRadius, usePopupMaxHeight, usePopupWidth } from './utils';
|
|
17
12
|
|
|
18
13
|
const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
19
14
|
image,
|
|
20
15
|
title = 'Title',
|
|
21
16
|
description = 'Description',
|
|
22
|
-
highlightText,
|
|
23
17
|
error,
|
|
24
18
|
secondary,
|
|
25
19
|
primary,
|
|
@@ -141,25 +135,6 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
141
135
|
return renderColumn();
|
|
142
136
|
};
|
|
143
137
|
|
|
144
|
-
/**
|
|
145
|
-
* build description text, highlighted keywords keep size/lineHeight and use weight 600
|
|
146
|
-
*/
|
|
147
|
-
const renderDescription = (text: string) => {
|
|
148
|
-
const segments = splitHighlightText(text, highlightText);
|
|
149
|
-
if (!segments) {
|
|
150
|
-
return text;
|
|
151
|
-
}
|
|
152
|
-
return segments.map((segment, index) =>
|
|
153
|
-
segment.highlight ? (
|
|
154
|
-
<Text key={index} typography={'header_s_semibold'}>
|
|
155
|
-
{segment.text}
|
|
156
|
-
</Text>
|
|
157
|
-
) : (
|
|
158
|
-
segment.text
|
|
159
|
-
),
|
|
160
|
-
);
|
|
161
|
-
};
|
|
162
|
-
|
|
163
138
|
/**
|
|
164
139
|
* build close action
|
|
165
140
|
*/
|
|
@@ -225,7 +200,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
|
|
|
225
200
|
}
|
|
226
201
|
}}
|
|
227
202
|
>
|
|
228
|
-
{
|
|
203
|
+
{description}
|
|
229
204
|
</Text>
|
|
230
205
|
) : (
|
|
231
206
|
description
|
package/Popup/types.ts
CHANGED
|
@@ -20,13 +20,6 @@ export type PopupNotifyProps = {
|
|
|
20
20
|
*/
|
|
21
21
|
description?: string | React.ReactNode;
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* `highlightText`: Optional. List of words/phrases within `description` to be rendered in semibold.
|
|
25
|
-
* Only applies when `description` is a string. Matching is whole-word and case-sensitive;
|
|
26
|
-
* every occurrence of each string is highlighted.
|
|
27
|
-
*/
|
|
28
|
-
highlightText?: string[];
|
|
29
|
-
|
|
30
23
|
/**
|
|
31
24
|
* `error`: Optional. Represents additional error text to be displayed in the PopupNotify component.
|
|
32
25
|
*/
|
package/Popup/utils.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { Dimensions, useWindowDimensions } from 'react-native';
|
|
2
2
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
3
3
|
import { Radius, Spacing } from '../Consts';
|
|
4
|
-
import { getDuoModalBounds,
|
|
5
|
-
|
|
6
|
-
type HighlightSegment = { text: string; highlight: boolean };
|
|
4
|
+
import { getDuoModalBounds, isFoldableDevice } from '../Application/device';
|
|
7
5
|
|
|
8
6
|
const usePopupWidth = () => {
|
|
9
7
|
const insets = useSafeAreaInsets();
|
|
10
8
|
const { width, height } = useWindowDimensions();
|
|
11
|
-
if (!
|
|
9
|
+
if (!isFoldableDevice()) {
|
|
12
10
|
return Dimensions.get('window').width - Spacing.XL * 2;
|
|
13
11
|
}
|
|
14
12
|
return getDuoModalBounds(insets, width, height).maxWidth;
|
|
@@ -17,55 +15,12 @@ const usePopupWidth = () => {
|
|
|
17
15
|
const usePopupMaxHeight = () => {
|
|
18
16
|
const insets = useSafeAreaInsets();
|
|
19
17
|
const { width, height } = useWindowDimensions();
|
|
20
|
-
if (!
|
|
18
|
+
if (!isFoldableDevice()) {
|
|
21
19
|
return undefined;
|
|
22
20
|
}
|
|
23
21
|
return getDuoModalBounds(insets, width, height).maxHeight;
|
|
24
22
|
};
|
|
25
23
|
|
|
26
|
-
const getPopupRadius = () => (
|
|
27
|
-
|
|
28
|
-
const isWordChar = (char?: string) =>
|
|
29
|
-
!!char && (char.toLowerCase() !== char.toUpperCase() || /[0-9_]/.test(char));
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Split `text` into plain/highlighted segments (same matching as Information `highlightText`):
|
|
33
|
-
* whole-word, case-sensitive, every occurrence, longest keyword first.
|
|
34
|
-
* Returns `undefined` when there is nothing to highlight.
|
|
35
|
-
*/
|
|
36
|
-
const splitHighlightText = (
|
|
37
|
-
text: string,
|
|
38
|
-
highlightText?: string[],
|
|
39
|
-
): HighlightSegment[] | undefined => {
|
|
40
|
-
const keywords = highlightText?.filter(
|
|
41
|
-
keyword => typeof keyword === 'string' && keyword,
|
|
42
|
-
);
|
|
43
|
-
if (!keywords?.length) {
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
const pattern = [...keywords]
|
|
47
|
-
.sort((a, b) => b.length - a.length)
|
|
48
|
-
.map(keyword => keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
49
|
-
.join('|');
|
|
50
|
-
const regex = new RegExp(pattern, 'g');
|
|
51
|
-
const segments: HighlightSegment[] = [];
|
|
52
|
-
let lastIndex = 0;
|
|
53
|
-
let match;
|
|
54
|
-
while ((match = regex.exec(text))) {
|
|
55
|
-
const start = match.index;
|
|
56
|
-
const end = start + match[0].length;
|
|
57
|
-
if (isWordChar(text[start - 1]) || isWordChar(text[end])) {
|
|
58
|
-
regex.lastIndex = start + 1;
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (start > lastIndex) {
|
|
62
|
-
segments.push({ text: text.slice(lastIndex, start), highlight: false });
|
|
63
|
-
}
|
|
64
|
-
segments.push({ text: match[0], highlight: true });
|
|
65
|
-
lastIndex = end;
|
|
66
|
-
}
|
|
67
|
-
segments.push({ text: text.slice(lastIndex), highlight: false });
|
|
68
|
-
return segments;
|
|
69
|
-
};
|
|
24
|
+
const getPopupRadius = () => (isFoldableDevice() ? Radius.XL : Radius.L);
|
|
70
25
|
|
|
71
|
-
export { usePopupWidth, usePopupMaxHeight, getPopupRadius
|
|
26
|
+
export { usePopupWidth, usePopupMaxHeight, getPopupRadius };
|