@momo-kits/foundation 0.169.5-beta.1 → 0.169.5
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 +8 -8
- package/Application/BottomSheet.parts.tsx +44 -34
- package/Application/BottomSheet.tsx +40 -37
- package/Application/BottomTab/ActiveIndicator.tsx +15 -14
- package/Application/BottomTab/BottomTabBar.tsx +43 -34
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +14 -12
- package/Application/BottomTab/CustomBottomTabItem.tsx +10 -6
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +13 -13
- package/Application/BottomTab/DuoTabBar.tsx +8 -9
- package/Application/BottomTab/TabBarIcon.tsx +13 -8
- package/Application/BottomTab/TabScreen.tsx +1 -5
- package/Application/BottomTab/index.tsx +38 -30
- package/Application/BottomTab/metrics.ts +21 -20
- package/Application/BottomTab/tabItems.ts +1 -1
- package/Application/BottomTab/usePressLift.ts +2 -2
- package/Application/Components/BackgroundImageView.tsx +4 -0
- package/Application/Components/DuoGlassBackground.tsx +5 -4
- package/Application/Components/HeaderAnimated.tsx +4 -0
- package/Application/Components/HeaderBackground.tsx +4 -0
- package/Application/Components/HeaderExtendHeader.tsx +11 -10
- package/Application/Components/HeaderLeft.tsx +2 -1
- package/Application/Components/HeaderRight.tsx +2 -1
- package/Application/Components/HeaderTitle.shared.ts +11 -9
- package/Application/Components/HeaderTitle.tsx +4 -3
- package/Application/Components/HeaderToolkitAction.tsx +17 -20
- package/Application/Components/NavigationButton.tsx +2 -1
- package/Application/Components/SearchHeader.tsx +17 -10
- package/Application/Components/TitleJourney.tsx +2 -2
- package/Application/Components/TitleLocation.tsx +2 -2
- package/Application/Components/TitleUser.tsx +2 -2
- package/Application/Components/useToolkitActions.tsx +17 -32
- package/Application/Localize.ts +7 -0
- package/Application/ModalScreen.tsx +10 -10
- package/Application/Navigation.ts +14 -16
- package/Application/NavigationContainer.tsx +19 -18
- package/Application/Navigator.ts +20 -48
- package/Application/StackScreen.tracking.ts +11 -26
- package/Application/StackScreen.tsx +9 -39
- package/Application/TooltipPortal.tsx +12 -5
- package/Application/WidgetContainer.tsx +10 -4
- package/Application/modalLayout.ts +13 -14
- package/Application/navigationOptions.tsx +21 -17
- package/Badge/Badge.tsx +5 -0
- package/Badge/BadgeDot.tsx +1 -0
- package/Badge/BadgeDotAnimation.tsx +15 -7
- package/Badge/BadgeRibbon.tsx +46 -38
- package/Badge/Shape.tsx +2 -0
- package/Badge/types.ts +2 -2
- package/Button/index.tsx +5 -16
- package/Button/utils.ts +9 -15
- package/CheckBox/index.tsx +4 -0
- package/Consts/theme.ts +10 -10
- package/Context/index.ts +1 -0
- package/Divider/DashDivider.tsx +1 -0
- package/Divider/index.tsx +4 -0
- package/FoundationList/index.tsx +4 -0
- package/Icon/index.tsx +6 -6
- package/Icon/types.ts +1 -1
- package/IconButton/index.tsx +4 -12
- package/Image/index.tsx +9 -7
- package/Input/Input.tsx +8 -9
- package/Input/InputDropDown.tsx +10 -12
- package/Input/InputMoney.tsx +12 -8
- package/Input/InputMoneyView.tsx +14 -7
- package/Input/InputOTP.tsx +35 -33
- package/Input/InputPhoneNumber.tsx +8 -9
- package/Input/InputSearch.tsx +4 -6
- package/Input/InputTextArea.tsx +4 -0
- package/Input/SearchTextTyping.tsx +4 -0
- package/Input/SystemTextInput.tsx +7 -2
- package/Input/TextTyping.tsx +4 -0
- package/Input/common.tsx +26 -12
- package/Input/index.tsx +17 -20
- package/Input/moneyUtils.ts +10 -4
- package/Input/styles.ts +0 -5
- package/Input/utils.ts +14 -12
- package/Layout/Card.tsx +7 -4
- package/Layout/FloatingButton.tsx +4 -0
- package/Layout/Item.tsx +6 -2
- package/Layout/ItemList.tsx +9 -7
- package/Layout/ItemSectionList.tsx +2 -1
- package/Layout/ReservedRegions.tsx +25 -25
- package/Layout/Screen.tsx +56 -58
- package/Layout/Section.tsx +8 -5
- package/Layout/TrackingScope.tsx +1 -0
- package/Layout/screenContent.tsx +4 -2
- package/Layout/screenFooter.tsx +28 -18
- package/Layout/screenHeader.tsx +34 -31
- package/Layout/screenQuickReturn.tsx +17 -20
- package/Layout/screenScroll.ts +4 -7
- package/Layout/screenTypes.ts +3 -2
- package/Layout/types.ts +2 -4
- package/Layout/utils.ts +31 -37
- package/Loader/DotLoader.tsx +4 -0
- package/Loader/Loader.tsx +4 -0
- package/Loader/ProgressBar.tsx +1 -0
- package/Loader/Spinner.tsx +4 -0
- package/Loader/utils.ts +11 -7
- package/Pagination/Dot.tsx +4 -0
- package/Pagination/PaginationDot.tsx +1 -0
- package/Pagination/PaginationNumber.tsx +5 -3
- package/Pagination/PaginationScroll.tsx +5 -0
- package/Pagination/PaginationWhiteDot.tsx +1 -0
- package/Pagination/index.tsx +4 -0
- package/Pagination/types.ts +2 -2
- package/Popup/PopupNotify.tsx +44 -48
- package/Popup/PopupPromotion.tsx +4 -7
- package/Popup/types.ts +4 -4
- package/Popup/utils.ts +4 -0
- package/Radio/index.tsx +4 -0
- package/Skeleton/index.tsx +8 -0
- package/Switch/index.tsx +1 -0
- package/Tag/index.tsx +5 -1
- package/Tag/types.ts +3 -3
- package/Text/index.tsx +14 -3
- package/Text/styles.ts +1 -1
- package/Text/types.ts +1 -6
- package/Text/utils.ts +9 -0
- package/Title/index.tsx +41 -32
- package/ViewBoundary/index.tsx +4 -2
- package/index.ts +0 -6
- package/package.json +1 -1
- package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
- package/Application/KitAdoption/AdoptionProbe.tsx +0 -145
- package/Application/KitAdoption/AdoptionStore.ts +0 -80
- package/Application/KitAdoption/Authorship/utils.ts +0 -137
- package/Application/KitAdoption/constants.ts +0 -118
- package/Application/KitAdoption/index.ts +0 -5
- package/Application/KitAdoption/styles.ts +0 -55
- package/Application/KitAdoption/types.ts +0 -48
- package/Application/KitAdoption/utils.ts +0 -375
- package/Application/__tests__/KitAdoption.integration.test.tsx +0 -235
- package/Application/__tests__/KitAdoption.test.ts +0 -283
- package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
- package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
- package/Application/__tests__/modalLayout.test.ts +0 -128
package/Text/index.tsx
CHANGED
|
@@ -111,6 +111,10 @@ const deprecatedValues: Typography[] = [
|
|
|
111
111
|
'action_s',
|
|
112
112
|
];
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Text style of a typography token with sizes scaled by `useScaleSize` and the
|
|
116
|
+
* font file name for its weight. Calls hooks, so it only runs during render.
|
|
117
|
+
*/
|
|
114
118
|
const TypoStyles = (typo: Typography, newFontFamily?: string) => {
|
|
115
119
|
const { theme } = useContext(ApplicationContext);
|
|
116
120
|
const styleSheet: {
|
|
@@ -158,9 +162,7 @@ const TypoStyles = (typo: Typography, newFontFamily?: string) => {
|
|
|
158
162
|
fontSize: useScaleSize(fontSize),
|
|
159
163
|
lineHeight: useScaleSize(lineHeight),
|
|
160
164
|
fontStyle,
|
|
161
|
-
// fontFamily
|
|
162
|
-
// `fonts/<family>_bold.ttf` khi fontWeight >= 700; file do khong ton tai nen
|
|
163
|
-
// font se roi ve font he thong. Bo fontWeight de Android dung dung file.
|
|
165
|
+
// Weight is in fontFamily; fontWeight >= 700 makes Android seek a missing <family>_bold.ttf.
|
|
164
166
|
fontWeight: undefined,
|
|
165
167
|
};
|
|
166
168
|
};
|
|
@@ -169,6 +171,11 @@ const skeletonStyles = StyleSheet.create({
|
|
|
169
171
|
overlay: { position: 'absolute' },
|
|
170
172
|
});
|
|
171
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Design-system text: renders a `typography` token in the theme font with
|
|
176
|
+
* scaled sizes and OS font scaling off. Warns on deprecated tokens and shows a
|
|
177
|
+
* skeleton overlay while a SkeletonContext is loading.
|
|
178
|
+
*/
|
|
172
179
|
const Text: React.FC<TextProps> = ({
|
|
173
180
|
typography = 'body_default_regular',
|
|
174
181
|
color,
|
|
@@ -228,6 +235,10 @@ const Text: React.FC<TextProps> = ({
|
|
|
228
235
|
);
|
|
229
236
|
};
|
|
230
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Theme font family for a loose weight name ('bold', 'semibold', 'regular').
|
|
240
|
+
* The `typography` argument is ignored; it calls hooks, so use it during render.
|
|
241
|
+
*/
|
|
231
242
|
const exportFontFamily = (
|
|
232
243
|
fontWeight: string,
|
|
233
244
|
typography?: Typography,
|
package/Text/styles.ts
CHANGED
|
@@ -39,7 +39,7 @@ export default {
|
|
|
39
39
|
action_s_bold: { fontSize: 14, lineHeight: 20, fontWeight: '700' },
|
|
40
40
|
action_xs_bold: { fontSize: 12, lineHeight: 18, fontWeight: '700' },
|
|
41
41
|
action_xxs_bold: { fontSize: 10, lineHeight: 14, fontWeight: '700' },
|
|
42
|
-
|
|
42
|
+
// Legacy tokens kept for old callers; not in the current type scale.
|
|
43
43
|
headline_default: {
|
|
44
44
|
fontSize: 28,
|
|
45
45
|
lineHeight: 34,
|
package/Text/types.ts
CHANGED
|
@@ -14,9 +14,7 @@ export type TypographyWeight = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export type Typography =
|
|
17
|
-
|
|
18
|
-
* @deprecated
|
|
19
|
-
**/
|
|
17
|
+
// Deprecated up to 'action_s': Text warns at runtime when one is used.
|
|
20
18
|
| 'headline_default'
|
|
21
19
|
| 'headline_l'
|
|
22
20
|
| 'headline_xl'
|
|
@@ -29,9 +27,6 @@ export type Typography =
|
|
|
29
27
|
| 'paragraph_italic'
|
|
30
28
|
| 'label_xxs'
|
|
31
29
|
| 'action_s'
|
|
32
|
-
/**
|
|
33
|
-
* end
|
|
34
|
-
**/
|
|
35
30
|
| 'headline_l_bold'
|
|
36
31
|
| 'headline_default_bold'
|
|
37
32
|
| 'header_m_bold'
|
package/Text/utils.ts
CHANGED
|
@@ -6,6 +6,11 @@ const DEFAULT_SCREEN_SIZE = 375;
|
|
|
6
6
|
const MAX_FONT_SCALE = 1.5;
|
|
7
7
|
const MAX_DEVICE_SCALE = 5;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Scales `size` by screen width (at most +5) and OS font scale (at most
|
|
11
|
+
* `scaleRate`, default 1.5), taking the larger; a mini app can opt out of the
|
|
12
|
+
* OS rate and use its own `fontScale.userScaleRate` instead.
|
|
13
|
+
*/
|
|
9
14
|
const useScaleSize = (size: number, scaleRate?: number) => {
|
|
10
15
|
const { width } = useWindowDimensions();
|
|
11
16
|
const context = useContext<any>(MiniAppContext);
|
|
@@ -37,6 +42,10 @@ const useScaleSize = (size: number, scaleRate?: number) => {
|
|
|
37
42
|
return Math.max(fontSizeDeviceScale, fontSizeOSScale);
|
|
38
43
|
};
|
|
39
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Non-hook `useScaleSize` with the default cap and no mini app rate, for use
|
|
47
|
+
* outside render. Reads the window width at call time, so it misses resizes.
|
|
48
|
+
*/
|
|
40
49
|
const scaleSize = (size: number) => {
|
|
41
50
|
const fontScale = PixelRatio.getFontScale();
|
|
42
51
|
const deviceScale = Dimensions.get('window').width / DEFAULT_SCREEN_SIZE;
|
package/Title/index.tsx
CHANGED
|
@@ -15,6 +15,11 @@ import styles from './styles';
|
|
|
15
15
|
import { TitleProps } from './types';
|
|
16
16
|
import { Typography } from '../Text/types';
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Section or card heading with optional icon, badge, description, trailing
|
|
20
|
+
* chevron and right action (chevron, or a text button when `buttonTitle` is
|
|
21
|
+
* set). `textOnly` renders the title text alone.
|
|
22
|
+
*/
|
|
18
23
|
const Title: FC<TitleProps> = ({
|
|
19
24
|
accessibilityLabel,
|
|
20
25
|
title = 'Title',
|
|
@@ -43,7 +48,6 @@ const Title: FC<TitleProps> = ({
|
|
|
43
48
|
|
|
44
49
|
const size18 = useScaleSize(18);
|
|
45
50
|
const size22 = useScaleSize(22);
|
|
46
|
-
// Dynamic styles using useScaleSize hook
|
|
47
51
|
const dynamicStyles = {
|
|
48
52
|
card_small: {
|
|
49
53
|
fontSize: useScaleSize(14),
|
|
@@ -204,39 +208,44 @@ const Title: FC<TitleProps> = ({
|
|
|
204
208
|
|
|
205
209
|
const lineHeight = styleSheet[typography].lineHeight;
|
|
206
210
|
|
|
211
|
+
let action = (
|
|
212
|
+
<TouchableOpacity
|
|
213
|
+
onPress={onPressRightAction}
|
|
214
|
+
style={[
|
|
215
|
+
styles.iconRight,
|
|
216
|
+
{
|
|
217
|
+
backgroundColor: theme.colors.primary + '0F',
|
|
218
|
+
},
|
|
219
|
+
]}
|
|
220
|
+
accessibilityLabel={componentId + '|label-right-action-touch'}
|
|
221
|
+
>
|
|
222
|
+
<Icon
|
|
223
|
+
source={'arrow_chevron_right_small'}
|
|
224
|
+
size={size22}
|
|
225
|
+
color={theme.colors.primary}
|
|
226
|
+
/>
|
|
227
|
+
</TouchableOpacity>
|
|
228
|
+
);
|
|
229
|
+
if (buttonTitle) {
|
|
230
|
+
action = (
|
|
231
|
+
<TouchableOpacity
|
|
232
|
+
onPress={onPressRightAction}
|
|
233
|
+
accessibilityLabel={componentId + '|label-right-action-touch'}
|
|
234
|
+
>
|
|
235
|
+
<Text
|
|
236
|
+
color={theme.colors.primary}
|
|
237
|
+
typography={buttonTypo}
|
|
238
|
+
accessibilityLabel={componentId + '|label-right-action-text'}
|
|
239
|
+
>
|
|
240
|
+
{buttonTitle}
|
|
241
|
+
</Text>
|
|
242
|
+
</TouchableOpacity>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
207
246
|
return (
|
|
208
247
|
<View style={[styles.actionRightView, { height: lineHeight }]}>
|
|
209
|
-
{
|
|
210
|
-
<TouchableOpacity
|
|
211
|
-
onPress={onPressRightAction}
|
|
212
|
-
style={[
|
|
213
|
-
styles.iconRight,
|
|
214
|
-
{
|
|
215
|
-
backgroundColor: theme.colors.primary + '0F',
|
|
216
|
-
},
|
|
217
|
-
]}
|
|
218
|
-
accessibilityLabel={componentId + '|label-right-action-touch'}
|
|
219
|
-
>
|
|
220
|
-
<Icon
|
|
221
|
-
source={'arrow_chevron_right_small'}
|
|
222
|
-
size={size22}
|
|
223
|
-
color={theme.colors.primary}
|
|
224
|
-
/>
|
|
225
|
-
</TouchableOpacity>
|
|
226
|
-
) : (
|
|
227
|
-
<TouchableOpacity
|
|
228
|
-
onPress={onPressRightAction}
|
|
229
|
-
accessibilityLabel={componentId + '|label-right-action-touch'}
|
|
230
|
-
>
|
|
231
|
-
<Text
|
|
232
|
-
color={theme.colors.primary}
|
|
233
|
-
typography={buttonTypo}
|
|
234
|
-
accessibilityLabel={componentId + '|label-right-action-text'}
|
|
235
|
-
>
|
|
236
|
-
{buttonTitle}
|
|
237
|
-
</Text>
|
|
238
|
-
</TouchableOpacity>
|
|
239
|
-
)}
|
|
248
|
+
{action}
|
|
240
249
|
</View>
|
|
241
250
|
);
|
|
242
251
|
};
|
package/ViewBoundary/index.tsx
CHANGED
|
@@ -14,6 +14,10 @@ interface ViewBoundaryState {
|
|
|
14
14
|
error: Error | null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Error boundary: renders `fallback` (or nothing) after a render error and
|
|
19
|
+
* reports it to native `MiniAppModule.onJSBoundaryCrash` when available.
|
|
20
|
+
*/
|
|
17
21
|
class ViewBoundary extends React.Component<
|
|
18
22
|
ViewBoundaryProps,
|
|
19
23
|
ViewBoundaryState
|
|
@@ -33,7 +37,6 @@ class ViewBoundary extends React.Component<
|
|
|
33
37
|
this.props.onError?.(error, errorInfo);
|
|
34
38
|
|
|
35
39
|
const context = (this as any).context ?? {};
|
|
36
|
-
// check exist native module method onJSBoundaryCrash
|
|
37
40
|
const nativeMethod = NativeModules?.MiniAppModule?.onJSBoundaryCrash;
|
|
38
41
|
if (typeof nativeMethod === 'function') {
|
|
39
42
|
try {
|
|
@@ -49,7 +52,6 @@ class ViewBoundary extends React.Component<
|
|
|
49
52
|
componentStack: errorInfo?.componentStack?.substring(0, 500) ?? '',
|
|
50
53
|
},
|
|
51
54
|
});
|
|
52
|
-
console.info('[ViewBoundary] onJSBoundaryCrash success!');
|
|
53
55
|
} catch (_e) {
|
|
54
56
|
console.warn('[ViewBoundary] onJSBoundaryCrash failed:', _e);
|
|
55
57
|
}
|
package/index.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* native export
|
|
3
|
-
*/
|
|
4
1
|
import { TouchableOpacity } from 'react-native';
|
|
5
2
|
|
|
6
3
|
export * from 'react-native-linear-gradient';
|
|
7
4
|
export * from 'react-native-safe-area-context';
|
|
8
5
|
|
|
9
|
-
/**
|
|
10
|
-
* foundation component
|
|
11
|
-
*/
|
|
12
6
|
export * from './Consts';
|
|
13
7
|
export * from './Context';
|
|
14
8
|
export * from './Layout';
|
package/package.json
CHANGED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import React, { useState, useSyncExternalStore } from 'react';
|
|
2
|
-
import { Share, TouchableOpacity, View } from 'react-native';
|
|
3
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
|
-
|
|
5
|
-
import { Colors, Spacing } from '../../Consts';
|
|
6
|
-
import { Text } from '../../Text';
|
|
7
|
-
import { adoptionStore } from './AdoptionStore';
|
|
8
|
-
import { styles } from './styles';
|
|
9
|
-
import type { ScreenSnapshot } from './types';
|
|
10
|
-
|
|
11
|
-
/** Subscribes a React listener to the adoption store. Stable identity for useSyncExternalStore. */
|
|
12
|
-
const subscribeToStore = (listener: () => void): (() => void) =>
|
|
13
|
-
adoptionStore.subscribe(listener);
|
|
14
|
-
|
|
15
|
-
const getLatestSnapshot = (): ScreenSnapshot | undefined =>
|
|
16
|
-
adoptionStore.getLatest();
|
|
17
|
-
|
|
18
|
-
const getScreenCount = (): number => adoptionStore.getAll().length;
|
|
19
|
-
|
|
20
|
-
/** Share of classified nodes that are app-authored kit: K / (K + R + O). */
|
|
21
|
-
const getAdoptionRate = (snapshot: ScreenSnapshot): number => {
|
|
22
|
-
const { K, R, O } = snapshot.counts;
|
|
23
|
-
const total = K + R + O;
|
|
24
|
-
return total === 0 ? 0 : K / total;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const formatSummary = (snapshot: ScreenSnapshot | undefined): string => {
|
|
28
|
-
if (!snapshot) return 'Kit adoption: chưa có dữ liệu';
|
|
29
|
-
const { K, R, O } = snapshot.counts;
|
|
30
|
-
const percent = Math.round(getAdoptionRate(snapshot) * 100);
|
|
31
|
-
return `Kit ${percent}% · K${K} R${R} O${O}`;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const handleRemeasure = (): void => {
|
|
35
|
-
adoptionStore.remeasureActive();
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const handleShare = (): void => {
|
|
39
|
-
Share.share({ message: JSON.stringify(adoptionStore.getAll()) }).catch(
|
|
40
|
-
() => undefined,
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const handleClear = (): void => {
|
|
45
|
-
adoptionStore.clear();
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Dev-only floating badge showing the latest screen's kit adoption. Tap to
|
|
50
|
-
* expand actions (re-measure, share JSON, clear). Renders nothing in release builds.
|
|
51
|
-
*/
|
|
52
|
-
const AdoptionBadge: React.FC = () => {
|
|
53
|
-
const insets = useSafeAreaInsets();
|
|
54
|
-
const [isExpanded, setExpanded] = useState(false);
|
|
55
|
-
const latest = useSyncExternalStore(subscribeToStore, getLatestSnapshot);
|
|
56
|
-
const screenCount = useSyncExternalStore(subscribeToStore, getScreenCount);
|
|
57
|
-
|
|
58
|
-
if (!__DEV__) return null;
|
|
59
|
-
|
|
60
|
-
const handleToggle = (): void => setExpanded(prev => !prev);
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<View
|
|
64
|
-
pointerEvents="box-none"
|
|
65
|
-
style={[styles.wrapper, { top: insets.top + Spacing.S }]}
|
|
66
|
-
>
|
|
67
|
-
<TouchableOpacity
|
|
68
|
-
accessibilityRole="button"
|
|
69
|
-
onPress={handleToggle}
|
|
70
|
-
style={styles.badge}
|
|
71
|
-
>
|
|
72
|
-
<Text typography="label_xs_medium" color={Colors.black_01}>
|
|
73
|
-
{formatSummary(latest)}
|
|
74
|
-
</Text>
|
|
75
|
-
{latest ? (
|
|
76
|
-
<Text typography="description_xs_regular" color={Colors.black_01}>
|
|
77
|
-
{latest.screen_name}
|
|
78
|
-
</Text>
|
|
79
|
-
) : null}
|
|
80
|
-
</TouchableOpacity>
|
|
81
|
-
{isExpanded ? (
|
|
82
|
-
<View style={styles.panel}>
|
|
83
|
-
<Text typography="label_xs_medium" color={Colors.black_17}>
|
|
84
|
-
{`Đã đo ${screenCount} màn hình`}
|
|
85
|
-
</Text>
|
|
86
|
-
<TouchableOpacity
|
|
87
|
-
accessibilityRole="button"
|
|
88
|
-
onPress={handleRemeasure}
|
|
89
|
-
style={styles.action}
|
|
90
|
-
>
|
|
91
|
-
<Text typography="label_xs_medium" color={Colors.blue_03}>
|
|
92
|
-
Đo lại
|
|
93
|
-
</Text>
|
|
94
|
-
</TouchableOpacity>
|
|
95
|
-
<TouchableOpacity
|
|
96
|
-
accessibilityRole="button"
|
|
97
|
-
onPress={handleShare}
|
|
98
|
-
style={styles.action}
|
|
99
|
-
>
|
|
100
|
-
<Text typography="label_xs_medium" color={Colors.blue_03}>
|
|
101
|
-
Share JSON
|
|
102
|
-
</Text>
|
|
103
|
-
</TouchableOpacity>
|
|
104
|
-
<TouchableOpacity
|
|
105
|
-
accessibilityRole="button"
|
|
106
|
-
onPress={handleClear}
|
|
107
|
-
style={styles.actionOutline}
|
|
108
|
-
>
|
|
109
|
-
<Text typography="label_xs_medium" color={Colors.black_17}>
|
|
110
|
-
Xoá
|
|
111
|
-
</Text>
|
|
112
|
-
</TouchableOpacity>
|
|
113
|
-
</View>
|
|
114
|
-
) : null}
|
|
115
|
-
</View>
|
|
116
|
-
);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
export { AdoptionBadge };
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { InteractionManager, Platform } from 'react-native';
|
|
3
|
-
|
|
4
|
-
import { MiniAppContext } from '../../Context';
|
|
5
|
-
import { adoptionStore } from './AdoptionStore';
|
|
6
|
-
import { SETTLE_MS } from './constants';
|
|
7
|
-
import type {
|
|
8
|
-
FiberLike,
|
|
9
|
-
KitRegistries,
|
|
10
|
-
ScreenSnapshot,
|
|
11
|
-
WalkResult,
|
|
12
|
-
} from './types';
|
|
13
|
-
import {
|
|
14
|
-
createRegistries,
|
|
15
|
-
getAppId,
|
|
16
|
-
getCurrentFiber,
|
|
17
|
-
walkScreen,
|
|
18
|
-
} from './utils';
|
|
19
|
-
|
|
20
|
-
type NavigationLike = {
|
|
21
|
-
addListener?: (event: string, callback: () => void) => () => void;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type ProbeProps = {
|
|
25
|
-
screenName: string;
|
|
26
|
-
navigation?: NavigationLike;
|
|
27
|
-
children: React.ReactNode;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
let registriesCache: KitRegistries | undefined;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Kit and RN identity sets, built on first use. Lazy require breaks the cycle
|
|
34
|
-
* between the foundation index and the Application screens that render this probe.
|
|
35
|
-
*/
|
|
36
|
-
const getRegistries = (): KitRegistries => {
|
|
37
|
-
if (!registriesCache) {
|
|
38
|
-
const kitModule = require('../../index') as Record<string, unknown>;
|
|
39
|
-
const rnModule = require('react-native') as Record<string, unknown>;
|
|
40
|
-
registriesCache = createRegistries([kitModule], rnModule);
|
|
41
|
-
}
|
|
42
|
-
return registriesCache;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/** Foundation version from its package.json, or undefined if it cannot be read. */
|
|
46
|
-
const readFoundationVersion = (): string | undefined => {
|
|
47
|
-
try {
|
|
48
|
-
const pkg = require('../../package.json') as { version?: string };
|
|
49
|
-
return pkg.version;
|
|
50
|
-
} catch {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const createErrorResult = (): WalkResult => ({
|
|
56
|
-
counts: { K: 0, R: 0, O: 0 },
|
|
57
|
-
kit: {},
|
|
58
|
-
rn: {},
|
|
59
|
-
other: {},
|
|
60
|
-
top_transparent: [],
|
|
61
|
-
fibers: 0,
|
|
62
|
-
truncated: false,
|
|
63
|
-
status: 'error',
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
/** Stops the walk at nested probes so each screen is measured on its own. */
|
|
67
|
-
const isProbeFiber = (fiber: FiberLike): boolean =>
|
|
68
|
-
fiber.type === AdoptionProbeDev;
|
|
69
|
-
|
|
70
|
-
/** Passes children through with no measurement. Used in release builds. */
|
|
71
|
-
const PassThrough = (props: ProbeProps): React.JSX.Element => (
|
|
72
|
-
<>{props.children}</>
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Dev-only wrapper around a screen. Measures the screen's fiber tree after
|
|
77
|
-
* the first interactions settle, and again on focus. Release builds use PassThrough.
|
|
78
|
-
*/
|
|
79
|
-
class AdoptionProbeDev extends React.Component<ProbeProps> {
|
|
80
|
-
static contextType = MiniAppContext;
|
|
81
|
-
|
|
82
|
-
private timer: ReturnType<typeof setTimeout> | undefined;
|
|
83
|
-
private interaction: { cancel: () => void } | undefined;
|
|
84
|
-
private unsubscribeFocus: (() => void) | undefined;
|
|
85
|
-
|
|
86
|
-
componentDidMount(): void {
|
|
87
|
-
adoptionStore.setActive(this.measure);
|
|
88
|
-
this.unsubscribeFocus = this.props.navigation?.addListener?.(
|
|
89
|
-
'focus',
|
|
90
|
-
this.handleFocus,
|
|
91
|
-
);
|
|
92
|
-
this.interaction = InteractionManager.runAfterInteractions(() => {
|
|
93
|
-
this.timer = setTimeout(this.measure, SETTLE_MS);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
componentWillUnmount(): void {
|
|
98
|
-
if (this.timer !== undefined) clearTimeout(this.timer);
|
|
99
|
-
this.interaction?.cancel();
|
|
100
|
-
this.unsubscribeFocus?.();
|
|
101
|
-
adoptionStore.clearActive(this.measure);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
render(): React.ReactNode {
|
|
105
|
-
return this.props.children;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private handleFocus = (): void => {
|
|
109
|
-
adoptionStore.setActive(this.measure);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
private measure = (): void => {
|
|
113
|
-
const startedAt = Date.now();
|
|
114
|
-
let result: WalkResult;
|
|
115
|
-
try {
|
|
116
|
-
const self = getCurrentFiber(
|
|
117
|
-
(this as unknown as { _reactInternals: FiberLike })._reactInternals,
|
|
118
|
-
);
|
|
119
|
-
const root = self.child;
|
|
120
|
-
if (!root) return;
|
|
121
|
-
result = walkScreen(root, getRegistries(), isProbeFiber);
|
|
122
|
-
} catch {
|
|
123
|
-
result = createErrorResult();
|
|
124
|
-
}
|
|
125
|
-
this.save(result, startedAt);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
private save(result: WalkResult, startedAt: number): void {
|
|
129
|
-
const snapshot: ScreenSnapshot = {
|
|
130
|
-
...result,
|
|
131
|
-
app_id: getAppId(this.context),
|
|
132
|
-
screen_name: this.props.screenName,
|
|
133
|
-
foundation_version: readFoundationVersion(),
|
|
134
|
-
platform: Platform.OS,
|
|
135
|
-
ts: Date.now(),
|
|
136
|
-
walk_ms: Date.now() - startedAt,
|
|
137
|
-
};
|
|
138
|
-
adoptionStore.save(snapshot);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const AdoptionProbe = __DEV__ ? AdoptionProbeDev : PassThrough;
|
|
143
|
-
|
|
144
|
-
export type { NavigationLike, ProbeProps };
|
|
145
|
-
export { AdoptionProbe, AdoptionProbeDev };
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { ScreenSnapshot } from './types';
|
|
2
|
-
|
|
3
|
-
type Listener = () => void;
|
|
4
|
-
type Measure = () => void;
|
|
5
|
-
|
|
6
|
-
/** Total classified nodes in a snapshot, used to keep the richer measurement. */
|
|
7
|
-
const getTotal = (snapshot: ScreenSnapshot): number =>
|
|
8
|
-
snapshot.counts.K + snapshot.counts.R + snapshot.counts.O;
|
|
9
|
-
|
|
10
|
-
const getKey = (snapshot: ScreenSnapshot): string =>
|
|
11
|
-
`${snapshot.app_id}/${snapshot.screen_name}`;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* In-memory, dev-only store of per-screen snapshots. Keeps one snapshot per
|
|
15
|
-
* app and screen (the one with the most nodes) and holds the active measurer.
|
|
16
|
-
*/
|
|
17
|
-
class AdoptionStore {
|
|
18
|
-
private snapshots = new Map<string, ScreenSnapshot>();
|
|
19
|
-
private listeners = new Set<Listener>();
|
|
20
|
-
private latestKey: string | undefined;
|
|
21
|
-
private activeMeasure: Measure | undefined;
|
|
22
|
-
|
|
23
|
-
/** Stores a snapshot unless the stored one for the same screen is larger. */
|
|
24
|
-
save(snapshot: ScreenSnapshot): void {
|
|
25
|
-
const key = getKey(snapshot);
|
|
26
|
-
const existing = this.snapshots.get(key);
|
|
27
|
-
if (!existing || getTotal(existing) <= getTotal(snapshot)) {
|
|
28
|
-
this.snapshots.set(key, snapshot);
|
|
29
|
-
}
|
|
30
|
-
this.latestKey = key;
|
|
31
|
-
this.notify();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
getAll(): ScreenSnapshot[] {
|
|
35
|
-
return Array.from(this.snapshots.values());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** The screen saved most recently, or undefined when nothing is saved. */
|
|
39
|
-
getLatest(): ScreenSnapshot | undefined {
|
|
40
|
-
return this.latestKey === undefined
|
|
41
|
-
? undefined
|
|
42
|
-
: this.snapshots.get(this.latestKey);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
clear(): void {
|
|
46
|
-
this.snapshots.clear();
|
|
47
|
-
this.latestKey = undefined;
|
|
48
|
-
this.notify();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Registers a change listener and returns its unsubscribe function. */
|
|
52
|
-
subscribe(listener: Listener): () => void {
|
|
53
|
-
this.listeners.add(listener);
|
|
54
|
-
return () => {
|
|
55
|
-
this.listeners.delete(listener);
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
setActive(measure: Measure): void {
|
|
60
|
-
this.activeMeasure = measure;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Clears the active measurer only if it is still the given one. */
|
|
64
|
-
clearActive(measure: Measure): void {
|
|
65
|
-
if (this.activeMeasure === measure) this.activeMeasure = undefined;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
remeasureActive(): void {
|
|
69
|
-
this.activeMeasure?.();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private notify(): void {
|
|
73
|
-
this.listeners.forEach(listener => listener());
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const adoptionStore = new AdoptionStore();
|
|
78
|
-
|
|
79
|
-
export type { Listener, Measure };
|
|
80
|
-
export { AdoptionStore, adoptionStore };
|