@momo-kits/foundation 0.169.4-beta.6 → 0.169.4-beta.7
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 +5 -11
- package/Application/BottomTab/BottomTabBar.styles.ts +3 -0
- package/Application/BottomTab/BottomTabBar.tsx +11 -12
- package/Application/BottomTab/CurvedContainer.tsx +8 -2
- package/Application/BottomTab/CustomBottomTabItem.tsx +9 -4
- package/Application/BottomTab/DuoRailTabBar.tsx +3 -3
- package/Application/BottomTab/DuoTabBar.tsx +15 -3
- package/Application/BottomTab/TabBarIcon.tsx +24 -0
- package/Application/BottomTab/metrics.ts +26 -3
- package/Application/ModalScreen.tsx +1 -7
- package/Application/NavigationContainer.tsx +0 -2
- package/Application/StackScreen.tsx +1 -4
- package/Application/types.tabs.ts +0 -1
- package/Text/utils.ts +8 -3
- package/package.json +1 -1
- package/Application/KitAdoption/AdoptionBadge.tsx +0 -123
- package/Application/KitAdoption/AdoptionProbe.tsx +0 -147
- package/Application/KitAdoption/AdoptionStore.ts +0 -80
- package/Application/KitAdoption/constants.ts +0 -104
- 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 -400
- package/Application/__tests__/KitAdoption.integration.test.tsx +0 -235
- package/Application/__tests__/KitAdoption.test.ts +0 -260
- package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
|
@@ -18,7 +18,6 @@ import Animated, {
|
|
|
18
18
|
} from 'react-native-reanimated';
|
|
19
19
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
20
20
|
import { DUO_ORIENTATIONS, styles } from './BottomSheet.styles';
|
|
21
|
-
import { AdoptionProbe } from './KitAdoption';
|
|
22
21
|
import {
|
|
23
22
|
useBottomSheetLayout,
|
|
24
23
|
useDuoKeyboardLift,
|
|
@@ -174,16 +173,11 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
174
173
|
);
|
|
175
174
|
|
|
176
175
|
const screen = (
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{...props}
|
|
183
|
-
{...props.route.params}
|
|
184
|
-
onRequestClose={onRequestClose}
|
|
185
|
-
/>
|
|
186
|
-
</AdoptionProbe>
|
|
176
|
+
<Screen
|
|
177
|
+
{...props}
|
|
178
|
+
{...props.route.params}
|
|
179
|
+
onRequestClose={onRequestClose}
|
|
180
|
+
/>
|
|
187
181
|
);
|
|
188
182
|
|
|
189
183
|
return (
|
|
@@ -46,6 +46,9 @@ export const styles = StyleSheet.create({
|
|
|
46
46
|
width: TabBarMetrics.ctaSize,
|
|
47
47
|
height: TabBarMetrics.ctaSize,
|
|
48
48
|
borderRadius: TabBarMetrics.ctaSize / 2,
|
|
49
|
+
// Figma Icon Button border: white 60% hairline
|
|
50
|
+
borderWidth: 1,
|
|
51
|
+
borderColor: `rgba(255, 255, 255, ${TabBarMetrics.ctaBorderOpacity})`,
|
|
49
52
|
alignItems: 'center',
|
|
50
53
|
justifyContent: 'center',
|
|
51
54
|
},
|
|
@@ -17,7 +17,7 @@ import { CurvedContainer } from './CurvedContainer';
|
|
|
17
17
|
import { ActiveIndicator } from './ActiveIndicator';
|
|
18
18
|
import { getHorizontalBarWidth, TabBarMetrics, TabBarMotion } from './metrics';
|
|
19
19
|
import { getTabItems, TabItem } from './tabItems';
|
|
20
|
-
import {
|
|
20
|
+
import { CappedScaleText } from './TabBarIcon';
|
|
21
21
|
import { styles } from './BottomTabBar.styles';
|
|
22
22
|
|
|
23
23
|
type Props = BottomTabBarProps & {
|
|
@@ -114,7 +114,7 @@ const CenterAction: React.FC<{
|
|
|
114
114
|
</Animated.View>
|
|
115
115
|
<View style={styles.ctaLabel}>
|
|
116
116
|
{fixedFontScale ? (
|
|
117
|
-
<
|
|
117
|
+
<CappedScaleText>{labelText}</CappedScaleText>
|
|
118
118
|
) : (
|
|
119
119
|
labelText
|
|
120
120
|
)}
|
|
@@ -194,21 +194,17 @@ const HorizontalTabBar: React.FC<{
|
|
|
194
194
|
return renderItems(items);
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
-
const customContainer = withCta && floatingButton?.container;
|
|
198
|
-
|
|
199
197
|
return (
|
|
200
198
|
<View
|
|
201
199
|
style={[styles.container, { width }]}
|
|
202
200
|
accessibilityLabel={'bottom_tab_bar'}
|
|
203
201
|
>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
/>
|
|
211
|
-
)}
|
|
202
|
+
<CurvedContainer
|
|
203
|
+
width={width}
|
|
204
|
+
height={TabBarMetrics.height}
|
|
205
|
+
radius={TabBarMetrics.height / 2}
|
|
206
|
+
notchCenterX={withCta ? width / 2 : undefined}
|
|
207
|
+
/>
|
|
212
208
|
<View style={styles.content}>
|
|
213
209
|
<ActiveIndicator
|
|
214
210
|
axis={'x'}
|
|
@@ -241,6 +237,8 @@ export default function BottomTabBar(props: Props) {
|
|
|
241
237
|
window.height,
|
|
242
238
|
insets.left,
|
|
243
239
|
insets.right,
|
|
240
|
+
state.routes.length,
|
|
241
|
+
withCta,
|
|
244
242
|
);
|
|
245
243
|
|
|
246
244
|
return (
|
|
@@ -258,6 +256,7 @@ export default function BottomTabBar(props: Props) {
|
|
|
258
256
|
]}
|
|
259
257
|
>
|
|
260
258
|
<HorizontalTabBar
|
|
259
|
+
fixedFontScale
|
|
261
260
|
items={getTabItems(props)}
|
|
262
261
|
width={width}
|
|
263
262
|
floatingButton={floatingButton}
|
|
@@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native';
|
|
|
3
3
|
import Svg, { Defs, FeGaussianBlur, Filter, Path } from 'react-native-svg';
|
|
4
4
|
import { Colors } from '../../Consts';
|
|
5
5
|
import { ApplicationContext } from '../../Context';
|
|
6
|
-
import { NOTCH_CURVES, NOTCH_HALF_WIDTH } from './metrics';
|
|
6
|
+
import { NOTCH_CURVES, NOTCH_HALF_WIDTH, TabBarMetrics } from './metrics';
|
|
7
7
|
|
|
8
8
|
type Props = {
|
|
9
9
|
width: number;
|
|
@@ -137,7 +137,13 @@ const CurvedContainer: React.FC<Props> = ({
|
|
|
137
137
|
filter={'url(#keyShadow)'}
|
|
138
138
|
/>
|
|
139
139
|
)}
|
|
140
|
-
<Path
|
|
140
|
+
<Path
|
|
141
|
+
d={path}
|
|
142
|
+
fill={theme.colors.background.surface}
|
|
143
|
+
fillOpacity={TabBarMetrics.backgroundOpacity}
|
|
144
|
+
stroke={theme.colors.border.default}
|
|
145
|
+
strokeWidth={1}
|
|
146
|
+
/>
|
|
141
147
|
</Svg>
|
|
142
148
|
);
|
|
143
149
|
};
|
|
@@ -10,10 +10,10 @@ import Animated, {
|
|
|
10
10
|
useSharedValue,
|
|
11
11
|
withTiming,
|
|
12
12
|
} from 'react-native-reanimated';
|
|
13
|
-
import {
|
|
13
|
+
import { CappedScaleText, TabBarIcon } from './TabBarIcon';
|
|
14
14
|
import { Text } from '../../Text';
|
|
15
15
|
import { MiniAppContext } from '../../Context';
|
|
16
|
-
import { Colors
|
|
16
|
+
import { Colors } from '../../Consts';
|
|
17
17
|
import { TabBarMetrics, TabBarMotion } from './metrics';
|
|
18
18
|
|
|
19
19
|
type IconRenderer = (props: {
|
|
@@ -98,7 +98,7 @@ const TabItemContent: React.FC<TabItemContentProps> = ({
|
|
|
98
98
|
/>
|
|
99
99
|
</View>
|
|
100
100
|
{!hideLabel &&
|
|
101
|
-
(fixedFontScale ? <
|
|
101
|
+
(fixedFontScale ? <CappedScaleText>{text}</CappedScaleText> : text)}
|
|
102
102
|
</>
|
|
103
103
|
);
|
|
104
104
|
};
|
|
@@ -147,7 +147,7 @@ const styles = StyleSheet.create({
|
|
|
147
147
|
minWidth: 1,
|
|
148
148
|
alignItems: 'center',
|
|
149
149
|
justifyContent: 'center',
|
|
150
|
-
gap:
|
|
150
|
+
gap: TabBarMetrics.itemIconLabelGap,
|
|
151
151
|
},
|
|
152
152
|
slot: {
|
|
153
153
|
height: TabBarMetrics.indicatorHeight,
|
|
@@ -166,6 +166,11 @@ const styles = StyleSheet.create({
|
|
|
166
166
|
},
|
|
167
167
|
labelBox: {
|
|
168
168
|
alignSelf: 'stretch',
|
|
169
|
+
// fixed to the design line height so a capped-scale label overflows downward and the
|
|
170
|
+
// icon above it stays put
|
|
171
|
+
height: TabBarMetrics.labelHeight,
|
|
172
|
+
justifyContent: 'flex-start',
|
|
173
|
+
overflow: 'visible',
|
|
169
174
|
},
|
|
170
175
|
label: {
|
|
171
176
|
textAlign: 'center',
|
|
@@ -13,7 +13,7 @@ import { TabItemContent } from './CustomBottomTabItem';
|
|
|
13
13
|
import { CurvedContainer } from './CurvedContainer';
|
|
14
14
|
import { TabBarMetrics } from './metrics';
|
|
15
15
|
import { getTabItems, TabItem } from './tabItems';
|
|
16
|
-
import {
|
|
16
|
+
import { CappedScaleText } from './TabBarIcon';
|
|
17
17
|
import { usePressLift } from './usePressLift';
|
|
18
18
|
import { ActiveIndicator } from './ActiveIndicator';
|
|
19
19
|
import {
|
|
@@ -116,7 +116,7 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
116
116
|
<CtaIcon icon={floatingButton.icon} />
|
|
117
117
|
</Animated.View>
|
|
118
118
|
<View style={styles.railCtaLabel}>
|
|
119
|
-
<
|
|
119
|
+
<CappedScaleText>
|
|
120
120
|
<Text
|
|
121
121
|
numberOfLines={1}
|
|
122
122
|
typography={'label_xs_medium'}
|
|
@@ -124,7 +124,7 @@ const RailCenterAction: React.FC<{ floatingButton: FloatingButtonProps }> = ({
|
|
|
124
124
|
>
|
|
125
125
|
{floatingButton.label}
|
|
126
126
|
</Text>
|
|
127
|
-
</
|
|
127
|
+
</CappedScaleText>
|
|
128
128
|
{floatingButton.labelContent && (
|
|
129
129
|
<View pointerEvents={'none'} style={styles.railCtaLabelOverlay}>
|
|
130
130
|
{floatingButton.labelContent}
|
|
@@ -3,7 +3,11 @@ import React from 'react';
|
|
|
3
3
|
import { useWindowDimensions, View } from 'react-native';
|
|
4
4
|
import { useScreenLayout } from '../../Layout/utils';
|
|
5
5
|
import { FloatingButtonProps } from '../types';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getSystemIndicatorHeight,
|
|
8
|
+
hasCenterAction,
|
|
9
|
+
HorizontalTabBar,
|
|
10
|
+
} from './BottomTabBar';
|
|
7
11
|
import { getHorizontalBarWidth, TabBarMetrics } from './metrics';
|
|
8
12
|
import { getTabItems } from './tabItems';
|
|
9
13
|
import { DuoRailTabBar } from './DuoRailTabBar';
|
|
@@ -34,6 +38,7 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
34
38
|
const { insets } = useScreenLayout();
|
|
35
39
|
const { width, height } = useWindowDimensions();
|
|
36
40
|
const { floatingButton, activeTintColor, inactiveTintColor } = props;
|
|
41
|
+
const tabItems = getTabItems(props);
|
|
37
42
|
|
|
38
43
|
return (
|
|
39
44
|
<View
|
|
@@ -45,8 +50,15 @@ const DuoFloatingTabBar: React.FC<DuoTabBarProps> = props => {
|
|
|
45
50
|
>
|
|
46
51
|
<HorizontalTabBar
|
|
47
52
|
fixedFontScale
|
|
48
|
-
items={
|
|
49
|
-
width={getHorizontalBarWidth(
|
|
53
|
+
items={tabItems}
|
|
54
|
+
width={getHorizontalBarWidth(
|
|
55
|
+
width,
|
|
56
|
+
height,
|
|
57
|
+
insets.left,
|
|
58
|
+
insets.right,
|
|
59
|
+
tabItems.length,
|
|
60
|
+
hasCenterAction(floatingButton, tabItems.length),
|
|
61
|
+
)}
|
|
50
62
|
floatingButton={floatingButton}
|
|
51
63
|
activeTintColor={activeTintColor}
|
|
52
64
|
inactiveTintColor={inactiveTintColor}
|
|
@@ -71,6 +71,30 @@ export const FixedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
|
71
71
|
);
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
+
// the tab labels follow the OS/host font scale but never grow past this, so the fixed-height
|
|
75
|
+
// container and the notch stay aligned
|
|
76
|
+
const TAB_LABEL_MAX_SCALE = 1.2;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* caps the label scale at `TAB_LABEL_MAX_SCALE`: it keeps following the host/OS font scale but
|
|
80
|
+
* stops growing past 1.2× so it fits the fixed bar geometry
|
|
81
|
+
*/
|
|
82
|
+
export const CappedScaleText: React.FC<{ children: React.ReactNode }> = ({
|
|
83
|
+
children,
|
|
84
|
+
}) => {
|
|
85
|
+
const context = useContext<any>(MiniAppContext);
|
|
86
|
+
return (
|
|
87
|
+
<MiniAppContext.Provider
|
|
88
|
+
value={{
|
|
89
|
+
...context,
|
|
90
|
+
fontScale: { ...context?.fontScale, maxScaleRate: TAB_LABEL_MAX_SCALE },
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
</MiniAppContext.Provider>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
74
98
|
/**
|
|
75
99
|
* numbers use the error color, text the warning color (Figma Badge round numeric / text)
|
|
76
100
|
*/
|
|
@@ -9,23 +9,36 @@ import { Colors, Radius, Spacing } from '../../Consts';
|
|
|
9
9
|
* - vertical: iPhone Duo rail, center action under the container
|
|
10
10
|
*/
|
|
11
11
|
const TabBarMetrics = {
|
|
12
|
-
height:
|
|
12
|
+
height: 60,
|
|
13
13
|
margin: Spacing.S,
|
|
14
|
-
paddingVertical: Spacing.
|
|
14
|
+
paddingVertical: Spacing.XS,
|
|
15
15
|
paddingHorizontal: Spacing.M,
|
|
16
|
-
itemGap:
|
|
16
|
+
itemGap: 1,
|
|
17
|
+
// gap between the icon slot and the label inside a tab (Figma: none)
|
|
18
|
+
itemIconLabelGap: 0,
|
|
19
|
+
// .Nav/Background: no backdrop blur, so use the Figma Blur=Off spec (opacity 95%, no blur) plus the 1px
|
|
20
|
+
// border — nearly solid, matching the frosted sample; surface keeps dark-mode support
|
|
21
|
+
backgroundOpacity: 0.95,
|
|
22
|
+
// white 60% hairline around the center action button (Figma Icon Button border)
|
|
23
|
+
ctaBorderOpacity: 0.6,
|
|
17
24
|
iconSize: 24,
|
|
18
25
|
// active indicator behind the icon: 24 icon + XS·M padding
|
|
19
26
|
indicatorPaddingVertical: Spacing.XS,
|
|
20
27
|
indicatorPaddingHorizontal: Spacing.M,
|
|
21
28
|
indicatorRadius: Radius.XL,
|
|
22
29
|
indicatorHeight: 32,
|
|
30
|
+
// label_s_medium line height: the label box keeps this height so a scaled label grows
|
|
31
|
+
// downward instead of pushing the icon up
|
|
32
|
+
labelHeight: 18,
|
|
23
33
|
// home indicator / gesture bar area under the container
|
|
24
34
|
systemIndicatorHeight: 24,
|
|
25
35
|
// unfolded foldables (short side from 600, the smallest in the spec) use 65% of the short
|
|
26
36
|
// side, centered; phones and folded outer displays fill the window
|
|
27
37
|
foldableMinShortSide: 600,
|
|
28
38
|
foldableWidthRatio: 0.65,
|
|
39
|
+
// a 2-tab bar doesn't stretch to the full width: each item is this fixed width and the bar is
|
|
40
|
+
// sized to its content, centered (Figma Navigation · 2 Tab: item 100, bar ~225)
|
|
41
|
+
twoTabItemWidth: 100,
|
|
29
42
|
ctaSlotWidth: 92,
|
|
30
43
|
ctaSize: 48,
|
|
31
44
|
// the icon sits 10 inside the 48 button (prototype .cta: 28 icon), 12 inside the 52 rail one
|
|
@@ -136,7 +149,17 @@ const getHorizontalBarWidth = (
|
|
|
136
149
|
height: number,
|
|
137
150
|
insetLeft: number,
|
|
138
151
|
insetRight: number,
|
|
152
|
+
itemCount: number,
|
|
153
|
+
hasCta: boolean,
|
|
139
154
|
) => {
|
|
155
|
+
// 2 tabs (no center action) stay content-sized and centered instead of filling the window
|
|
156
|
+
if (itemCount === 2 && !hasCta) {
|
|
157
|
+
return (
|
|
158
|
+
TabBarMetrics.twoTabItemWidth * 2 +
|
|
159
|
+
TabBarMetrics.paddingHorizontal * 2 +
|
|
160
|
+
TabBarMetrics.itemGap
|
|
161
|
+
);
|
|
162
|
+
}
|
|
140
163
|
const shortSide = Math.min(width, height);
|
|
141
164
|
if (shortSide >= TabBarMetrics.foldableMinShortSide) {
|
|
142
165
|
return shortSide * TabBarMetrics.foldableWidthRatio;
|
|
@@ -24,7 +24,6 @@ import Navigation from './Navigation';
|
|
|
24
24
|
import { ModalParams } from './types';
|
|
25
25
|
import BottomSheet from './BottomSheet';
|
|
26
26
|
import { isIPhoneDuo } from './utils';
|
|
27
|
-
import { AdoptionProbe } from './KitAdoption';
|
|
28
27
|
import { useModalLayout } from './modalLayout';
|
|
29
28
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
30
29
|
|
|
@@ -183,12 +182,7 @@ const Modal: React.FC<ModalParams> = props => {
|
|
|
183
182
|
modalStyle,
|
|
184
183
|
]}
|
|
185
184
|
>
|
|
186
|
-
<
|
|
187
|
-
screenName={Component?.name || Component?.type?.name || 'Invalid'}
|
|
188
|
-
navigation={props.navigation}
|
|
189
|
-
>
|
|
190
|
-
<Component {...params} onRequestClose={onDismiss} />
|
|
191
|
-
</AdoptionProbe>
|
|
185
|
+
<Component {...params} onRequestClose={onDismiss} />
|
|
192
186
|
</Animated.View>
|
|
193
187
|
</KeyboardAvoidingView>
|
|
194
188
|
</Container>
|
|
@@ -30,7 +30,6 @@ import { HeaderBackground } from './Components/HeaderBackground';
|
|
|
30
30
|
import { useScreenLayout } from '../Layout';
|
|
31
31
|
import { getHeaderRowOptions } from '../Layout/utils';
|
|
32
32
|
import { ReservedRegionsProvider } from '../Layout/ReservedRegions';
|
|
33
|
-
import { AdoptionBadge } from './KitAdoption';
|
|
34
33
|
|
|
35
34
|
const Stack = createStackNavigator();
|
|
36
35
|
|
|
@@ -87,7 +86,6 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
87
86
|
initialParams={initialParams}
|
|
88
87
|
localize={localize}
|
|
89
88
|
/>
|
|
90
|
-
<AdoptionBadge />
|
|
91
89
|
</MiniAppContext.Provider>
|
|
92
90
|
</ReservedRegionsProvider>
|
|
93
91
|
</SafeAreaProvider>
|
|
@@ -8,7 +8,6 @@ import { SwipeBackHaptic } from './Components/SwipeBackHaptic';
|
|
|
8
8
|
import { useAppState } from './hooks';
|
|
9
9
|
import { createScreenTracking } from './StackScreen.tracking';
|
|
10
10
|
import { TooltipPortalHost, TooltipPortalProvider } from './TooltipPortal';
|
|
11
|
-
import { AdoptionProbe } from './KitAdoption';
|
|
12
11
|
|
|
13
12
|
const runAfterInteractions = InteractionManager.runAfterInteractions;
|
|
14
13
|
|
|
@@ -284,9 +283,7 @@ const StackScreen: React.FC<any> = props => {
|
|
|
284
283
|
}}
|
|
285
284
|
>
|
|
286
285
|
<TooltipPortalProvider>
|
|
287
|
-
<
|
|
288
|
-
<Component heightHeader={heightHeader} {...data} />
|
|
289
|
-
</AdoptionProbe>
|
|
286
|
+
<Component heightHeader={heightHeader} {...data} />
|
|
290
287
|
<TooltipPortalHost />
|
|
291
288
|
<SwipeBackHaptic />
|
|
292
289
|
</TooltipPortalProvider>
|
package/Text/utils.ts
CHANGED
|
@@ -9,13 +9,18 @@ const MAX_DEVICE_SCALE = 5;
|
|
|
9
9
|
const useScaleSize = (size: number, scaleRate?: number) => {
|
|
10
10
|
const { width } = useWindowDimensions();
|
|
11
11
|
const context = useContext<any>(MiniAppContext);
|
|
12
|
+
// opt-in ceiling: when set (arg or context), the final size never exceeds size × capRate,
|
|
13
|
+
// in both the mini-app userScaleRate path and the OS path. Undefined keeps the old behavior.
|
|
14
|
+
const capRate = scaleRate ?? context?.fontScale?.maxScaleRate;
|
|
15
|
+
const cap = (scaled: number) =>
|
|
16
|
+
capRate != null ? Math.min(scaled, size * capRate) : scaled;
|
|
12
17
|
if (context?.fontScale?.useOSScaleRate === false) {
|
|
13
|
-
return size * (context.fontScale.userScaleRate ?? 1);
|
|
18
|
+
return cap(size * (context.fontScale.userScaleRate ?? 1));
|
|
14
19
|
}
|
|
15
20
|
const fontScale = PixelRatio.getFontScale();
|
|
16
21
|
const deviceScale = width / DEFAULT_SCREEN_SIZE;
|
|
17
22
|
|
|
18
|
-
const maxScaleRate =
|
|
23
|
+
const maxScaleRate = capRate ?? MAX_FONT_SCALE;
|
|
19
24
|
|
|
20
25
|
let fontSizeDeviceScale = size;
|
|
21
26
|
let fontSizeOSScale = size;
|
|
@@ -34,7 +39,7 @@ const useScaleSize = (size: number, scaleRate?: number) => {
|
|
|
34
39
|
);
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
return Math.max(fontSizeDeviceScale, fontSizeOSScale);
|
|
42
|
+
return cap(Math.max(fontSizeDeviceScale, fontSizeOSScale));
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
const scaleSize = (size: number) => {
|
package/package.json
CHANGED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import React, { useContext, 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 { MiniAppContext } from '../../Context';
|
|
7
|
-
import { Text } from '../../Text';
|
|
8
|
-
import { adoptionStore } from './AdoptionStore';
|
|
9
|
-
import { styles } from './styles';
|
|
10
|
-
import type { ScreenSnapshot } from './types';
|
|
11
|
-
import { isAdoptionEnabled } from './utils';
|
|
12
|
-
|
|
13
|
-
/** Subscribes a React listener to the adoption store. Stable identity for useSyncExternalStore. */
|
|
14
|
-
const subscribeToStore = (listener: () => void): (() => void) =>
|
|
15
|
-
adoptionStore.subscribe(listener);
|
|
16
|
-
|
|
17
|
-
const getLatestSnapshot = (): ScreenSnapshot | undefined =>
|
|
18
|
-
adoptionStore.getLatest();
|
|
19
|
-
|
|
20
|
-
const getScreenCount = (): number => adoptionStore.getAll().length;
|
|
21
|
-
|
|
22
|
-
/** Share of classified nodes that are app-authored kit: K / (K + R + O). */
|
|
23
|
-
const getAdoptionRate = (snapshot: ScreenSnapshot): number => {
|
|
24
|
-
const { K, R, O } = snapshot.counts;
|
|
25
|
-
const total = K + R + O;
|
|
26
|
-
return total === 0 ? 0 : K / total;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const formatSummary = (snapshot: ScreenSnapshot | undefined): string => {
|
|
30
|
-
if (!snapshot) return 'Kit adoption: chưa có dữ liệu';
|
|
31
|
-
const { K, R, O } = snapshot.counts;
|
|
32
|
-
const percent = Math.round(getAdoptionRate(snapshot) * 100);
|
|
33
|
-
return `Kit ${percent}% · K${K} R${R} O${O}`;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const handleRemeasure = (): void => {
|
|
37
|
-
adoptionStore.remeasureActive();
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const handleShare = (): void => {
|
|
41
|
-
Share.share({ message: JSON.stringify(adoptionStore.getAll()) }).catch(
|
|
42
|
-
() => undefined,
|
|
43
|
-
);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const handleClear = (): void => {
|
|
47
|
-
adoptionStore.clear();
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Dev-only floating badge showing the latest screen's kit adoption. Tap to
|
|
52
|
-
* expand actions (re-measure, share JSON, clear). Renders nothing in release
|
|
53
|
-
* builds or when kit adoption is off.
|
|
54
|
-
*/
|
|
55
|
-
const AdoptionBadge: React.FC = () => {
|
|
56
|
-
const context = useContext<unknown>(MiniAppContext);
|
|
57
|
-
const insets = useSafeAreaInsets();
|
|
58
|
-
const [isExpanded, setExpanded] = useState(false);
|
|
59
|
-
const latest = useSyncExternalStore(subscribeToStore, getLatestSnapshot);
|
|
60
|
-
const screenCount = useSyncExternalStore(subscribeToStore, getScreenCount);
|
|
61
|
-
|
|
62
|
-
if (!__DEV__ || !isAdoptionEnabled(context)) return null;
|
|
63
|
-
|
|
64
|
-
const handleToggle = (): void => setExpanded(prev => !prev);
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<View
|
|
68
|
-
pointerEvents="box-none"
|
|
69
|
-
style={[styles.wrapper, { top: insets.top + Spacing.S }]}
|
|
70
|
-
>
|
|
71
|
-
<TouchableOpacity
|
|
72
|
-
accessibilityRole="button"
|
|
73
|
-
onPress={handleToggle}
|
|
74
|
-
style={styles.badge}
|
|
75
|
-
>
|
|
76
|
-
<Text typography="label_xs_medium" color={Colors.black_01}>
|
|
77
|
-
{formatSummary(latest)}
|
|
78
|
-
</Text>
|
|
79
|
-
{latest ? (
|
|
80
|
-
<Text typography="description_xs_regular" color={Colors.black_01}>
|
|
81
|
-
{latest.screen_name}
|
|
82
|
-
</Text>
|
|
83
|
-
) : null}
|
|
84
|
-
</TouchableOpacity>
|
|
85
|
-
{isExpanded ? (
|
|
86
|
-
<View style={styles.panel}>
|
|
87
|
-
<Text typography="label_xs_medium" color={Colors.black_17}>
|
|
88
|
-
{`Đã đo ${screenCount} màn hình`}
|
|
89
|
-
</Text>
|
|
90
|
-
<TouchableOpacity
|
|
91
|
-
accessibilityRole="button"
|
|
92
|
-
onPress={handleRemeasure}
|
|
93
|
-
style={styles.action}
|
|
94
|
-
>
|
|
95
|
-
<Text typography="label_xs_medium" color={Colors.blue_03}>
|
|
96
|
-
Đo lại
|
|
97
|
-
</Text>
|
|
98
|
-
</TouchableOpacity>
|
|
99
|
-
<TouchableOpacity
|
|
100
|
-
accessibilityRole="button"
|
|
101
|
-
onPress={handleShare}
|
|
102
|
-
style={styles.action}
|
|
103
|
-
>
|
|
104
|
-
<Text typography="label_xs_medium" color={Colors.blue_03}>
|
|
105
|
-
Share JSON
|
|
106
|
-
</Text>
|
|
107
|
-
</TouchableOpacity>
|
|
108
|
-
<TouchableOpacity
|
|
109
|
-
accessibilityRole="button"
|
|
110
|
-
onPress={handleClear}
|
|
111
|
-
style={styles.actionOutline}
|
|
112
|
-
>
|
|
113
|
-
<Text typography="label_xs_medium" color={Colors.black_17}>
|
|
114
|
-
Xoá
|
|
115
|
-
</Text>
|
|
116
|
-
</TouchableOpacity>
|
|
117
|
-
</View>
|
|
118
|
-
) : null}
|
|
119
|
-
</View>
|
|
120
|
-
);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
export { AdoptionBadge };
|