@momo-kits/foundation 0.169.7-beta.4 → 0.169.7-beta.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 +20 -15
- package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
- package/Application/BottomTab/BottomTabBar.tsx +57 -72
- package/Application/BottomTab/BottomTabInset.tsx +8 -4
- package/Application/BottomTab/CurvedContainer.tsx +22 -14
- package/Application/BottomTab/CustomBottomTabItem.tsx +15 -15
- package/Application/BottomTab/DuoBarCompression.tsx +13 -19
- package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
- package/Application/BottomTab/DuoTabBar.tsx +23 -13
- package/Application/BottomTab/TabBarIcon.tsx +15 -29
- package/Application/BottomTab/TabScreen.tsx +1 -5
- package/Application/BottomTab/index.tsx +33 -30
- package/Application/BottomTab/metrics.ts +46 -26
- 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/ScaleSizeProvider.tsx +13 -6
- package/Application/StackScreen.tracking.ts +11 -33
- package/Application/StackScreen.tsx +10 -54
- 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/Application/types.tabs.ts +0 -1
- 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 +5 -1
- 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 +20 -4
- 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 -166
- package/Application/KitAdoption/AdoptionStore.ts +0 -80
- package/Application/KitAdoption/Authorship/utils.ts +0 -137
- package/Application/KitAdoption/Slots/utils.ts +0 -68
- package/Application/KitAdoption/constants.ts +0 -120
- package/Application/KitAdoption/index.ts +0 -12
- package/Application/KitAdoption/styles.ts +0 -55
- package/Application/KitAdoption/types.ts +0 -61
- package/Application/KitAdoption/utils.ts +0 -376
- package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
- 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__/KitAdoptionStructural.test.ts +0 -193
- package/Application/__tests__/modalLayout.test.ts +0 -128
|
@@ -8,15 +8,12 @@ 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
|
-
import type { WalkResult } from './KitAdoption/types';
|
|
13
11
|
|
|
14
12
|
const runAfterInteractions = InteractionManager.runAfterInteractions;
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* @constructor
|
|
15
|
+
* Hosts a feature screen in the stack: applies its header options, provides
|
|
16
|
+
* `ScreenContext` and reports navigation, load and interaction tracking.
|
|
20
17
|
*/
|
|
21
18
|
const StackScreen: React.FC<any> = props => {
|
|
22
19
|
const { navigator } = useContext(ApplicationContext);
|
|
@@ -43,9 +40,6 @@ const StackScreen: React.FC<any> = props => {
|
|
|
43
40
|
lastElement: undefined,
|
|
44
41
|
});
|
|
45
42
|
const widgets = useRef<any>([]);
|
|
46
|
-
const adoptionMeasure = useRef<(() => WalkResult | undefined) | undefined>(
|
|
47
|
-
undefined,
|
|
48
|
-
);
|
|
49
43
|
const context = useContext<any>(MiniAppContext);
|
|
50
44
|
|
|
51
45
|
const {
|
|
@@ -69,18 +63,12 @@ const StackScreen: React.FC<any> = props => {
|
|
|
69
63
|
|
|
70
64
|
const screenName = Component?.name || Component?.type?.name || 'Invalid';
|
|
71
65
|
|
|
72
|
-
/**
|
|
73
|
-
* set options for screen
|
|
74
|
-
*/
|
|
75
66
|
useLayoutEffect(() => {
|
|
76
67
|
if (options) {
|
|
77
68
|
navigation.setOptions(options);
|
|
78
69
|
}
|
|
79
70
|
}, [navigation, options]);
|
|
80
71
|
|
|
81
|
-
/**
|
|
82
|
-
* tracking for screen
|
|
83
|
-
*/
|
|
84
72
|
useEffect(() => {
|
|
85
73
|
let focusScreen: any;
|
|
86
74
|
let onFocusApp: any;
|
|
@@ -154,6 +142,7 @@ const StackScreen: React.FC<any> = props => {
|
|
|
154
142
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
155
143
|
}, []);
|
|
156
144
|
|
|
145
|
+
/** Reports a navigation to this screen: `back` if it was shown before. */
|
|
157
146
|
const onScreenNavigated = (pre: string, current: string) => {
|
|
158
147
|
if (!isBackgroundToForeground) {
|
|
159
148
|
const item: any = {
|
|
@@ -171,9 +160,6 @@ const StackScreen: React.FC<any> = props => {
|
|
|
171
160
|
|
|
172
161
|
tracking.current.mounted = true;
|
|
173
162
|
|
|
174
|
-
/**
|
|
175
|
-
* debug toast
|
|
176
|
-
*/
|
|
177
163
|
navigator?.maxApi?.showToastDebug?.({
|
|
178
164
|
appId: context.appId,
|
|
179
165
|
message: `${screenName} screen_navigated`,
|
|
@@ -187,13 +173,7 @@ const StackScreen: React.FC<any> = props => {
|
|
|
187
173
|
onScreenInteraction,
|
|
188
174
|
onFirstInteraction,
|
|
189
175
|
onScreenLoading,
|
|
190
|
-
} = createScreenTracking({
|
|
191
|
-
tracking,
|
|
192
|
-
context,
|
|
193
|
-
navigator,
|
|
194
|
-
screenName,
|
|
195
|
-
adoptionMeasure,
|
|
196
|
-
});
|
|
176
|
+
} = createScreenTracking({ tracking, context, navigator, screenName });
|
|
197
177
|
|
|
198
178
|
return (
|
|
199
179
|
<ScreenContext.Provider
|
|
@@ -201,9 +181,7 @@ const StackScreen: React.FC<any> = props => {
|
|
|
201
181
|
screenName,
|
|
202
182
|
bottomTab: !!bottomTab,
|
|
203
183
|
onElementLoad: (item: any) => {
|
|
204
|
-
|
|
205
|
-
* widget handle
|
|
206
|
-
*/
|
|
184
|
+
// Widgets record their own load span and don't end the screen load.
|
|
207
185
|
if (item?.componentName === 'Widget') {
|
|
208
186
|
const index = widgets.current.findIndex(
|
|
209
187
|
(element: any) => element.componentId === item.componentId,
|
|
@@ -227,35 +205,25 @@ const StackScreen: React.FC<any> = props => {
|
|
|
227
205
|
return;
|
|
228
206
|
}
|
|
229
207
|
|
|
230
|
-
/**
|
|
231
|
-
* tracking for element screen load
|
|
232
|
-
*/
|
|
233
208
|
clearTimeout(tracking.current.timeoutLoad);
|
|
234
209
|
tracking.current.endTime = Date.now();
|
|
235
210
|
tracking.current.timeoutInteraction?.cancel?.();
|
|
236
211
|
tracking.current.timeoutInteraction = runAfterInteractions(() => {
|
|
237
|
-
tracking.current.
|
|
238
|
-
|
|
212
|
+
const elapsed = Date.now() - tracking.current.startTime;
|
|
213
|
+
tracking.current.timeInteraction = elapsed;
|
|
239
214
|
});
|
|
240
215
|
|
|
241
|
-
/**
|
|
242
|
-
* support for debug last element
|
|
243
|
-
*/
|
|
244
216
|
if (item?.componentName) {
|
|
245
217
|
tracking.current.lastElement = item;
|
|
246
218
|
}
|
|
247
219
|
|
|
248
|
-
|
|
249
|
-
* for stop tracking when user interaction
|
|
250
|
-
*/
|
|
220
|
+
// A user interaction settles load and interaction tracking at once.
|
|
251
221
|
if (item?.interaction) {
|
|
252
222
|
onScreenLoad();
|
|
253
223
|
onScreenInteraction();
|
|
254
224
|
onFirstInteraction(item?.action);
|
|
255
225
|
}
|
|
256
|
-
|
|
257
|
-
* timeout for handle tracking screen
|
|
258
|
-
*/
|
|
226
|
+
// Reports once no element has loaded for 2s.
|
|
259
227
|
tracking.current.timeoutLoad = setTimeout(() => {
|
|
260
228
|
const time = tracking.current.endTime - tracking.current.startTime;
|
|
261
229
|
tracking.current.widgets = widgets.current;
|
|
@@ -273,16 +241,10 @@ const StackScreen: React.FC<any> = props => {
|
|
|
273
241
|
tracking.current.timeStartLoading = Date.now();
|
|
274
242
|
}
|
|
275
243
|
|
|
276
|
-
/**
|
|
277
|
-
* tracking for loading screen
|
|
278
|
-
*/
|
|
279
244
|
if (!loading) {
|
|
280
245
|
tracking.current.timeEndLoading = Date.now();
|
|
281
246
|
}
|
|
282
247
|
|
|
283
|
-
/**
|
|
284
|
-
* timeout for handle tracking screen
|
|
285
|
-
*/
|
|
286
248
|
clearTimeout(tracking.current.timeoutLoading);
|
|
287
249
|
tracking.current.timeoutLoading = setTimeout(() => {
|
|
288
250
|
onScreenLoading();
|
|
@@ -294,13 +256,7 @@ const StackScreen: React.FC<any> = props => {
|
|
|
294
256
|
}}
|
|
295
257
|
>
|
|
296
258
|
<TooltipPortalProvider>
|
|
297
|
-
<
|
|
298
|
-
screenName={screenName}
|
|
299
|
-
navigation={props.navigation}
|
|
300
|
-
measureRef={adoptionMeasure}
|
|
301
|
-
>
|
|
302
|
-
<Component heightHeader={heightHeader} {...data} />
|
|
303
|
-
</AdoptionProbe>
|
|
259
|
+
<Component heightHeader={heightHeader} {...data} />
|
|
304
260
|
<TooltipPortalHost />
|
|
305
261
|
<SwipeBackHaptic />
|
|
306
262
|
</TooltipPortalProvider>
|
|
@@ -21,10 +21,14 @@ const TooltipPortalContext = createContext<TooltipPortalContextValue | null>(
|
|
|
21
21
|
null,
|
|
22
22
|
);
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Registry of tooltip contents that `TooltipPortalHost` renders in one layer
|
|
26
|
+
* above the screen, so tooltips aren't clipped by their anchor's parents.
|
|
27
|
+
*/
|
|
24
28
|
export const TooltipPortalProvider: React.FC<{ children: ReactNode }> = ({
|
|
25
29
|
children,
|
|
26
30
|
}) => {
|
|
27
|
-
//
|
|
31
|
+
// The ref map updates synchronously; the reducer only triggers re-renders.
|
|
28
32
|
const portalsRef = useRef<Map<string, ReactNode>>(new Map());
|
|
29
33
|
const hostRef = useRef<View>(null);
|
|
30
34
|
const [updateCounter, forceUpdate] = useReducer((x: number) => x + 1, 0);
|
|
@@ -32,16 +36,15 @@ export const TooltipPortalProvider: React.FC<{ children: ReactNode }> = ({
|
|
|
32
36
|
|
|
33
37
|
const register = useCallback((id: string, content: ReactNode) => {
|
|
34
38
|
portalsRef.current.set(id, content);
|
|
35
|
-
forceUpdate();
|
|
39
|
+
forceUpdate();
|
|
36
40
|
}, []);
|
|
37
41
|
|
|
38
42
|
const unregister = useCallback((id: string, immediate = false) => {
|
|
39
43
|
const hasItem = portalsRef.current.has(id);
|
|
40
|
-
if (!hasItem) return;
|
|
44
|
+
if (!hasItem) return;
|
|
41
45
|
|
|
42
46
|
portalsRef.current.delete(id);
|
|
43
47
|
|
|
44
|
-
// Clear any pending updates
|
|
45
48
|
if (pendingUpdateRef.current) {
|
|
46
49
|
clearTimeout(pendingUpdateRef.current);
|
|
47
50
|
pendingUpdateRef.current = null;
|
|
@@ -72,6 +75,7 @@ export const TooltipPortalProvider: React.FC<{ children: ReactNode }> = ({
|
|
|
72
75
|
);
|
|
73
76
|
};
|
|
74
77
|
|
|
78
|
+
/** Full-screen, touch-through layer that renders every registered tooltip. */
|
|
75
79
|
export const TooltipPortalHost: React.FC = () => {
|
|
76
80
|
const context = useContext(TooltipPortalContext);
|
|
77
81
|
|
|
@@ -95,6 +99,10 @@ export const TooltipPortalHost: React.FC = () => {
|
|
|
95
99
|
);
|
|
96
100
|
};
|
|
97
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Tooltip portal API. Outside a provider it returns no-ops (and warns in dev)
|
|
104
|
+
* so a tooltip degrades instead of crashing.
|
|
105
|
+
*/
|
|
98
106
|
export const useTooltipPortal = () => {
|
|
99
107
|
const context = useContext(TooltipPortalContext);
|
|
100
108
|
|
|
@@ -104,7 +112,6 @@ export const useTooltipPortal = () => {
|
|
|
104
112
|
'useTooltipPortal must be used within TooltipPortalProvider. Tooltips may not appear correctly.',
|
|
105
113
|
);
|
|
106
114
|
}
|
|
107
|
-
// Return no-op functions if context is missing
|
|
108
115
|
return {
|
|
109
116
|
register: () => { },
|
|
110
117
|
unregister: () => { },
|
|
@@ -18,6 +18,7 @@ import { StyleSheet, View } from 'react-native';
|
|
|
18
18
|
|
|
19
19
|
const Stack = createStackNavigator();
|
|
20
20
|
|
|
21
|
+
/** Widget screen that reports its laid-out height to size the container. */
|
|
21
22
|
const WidgetContent: React.FC = ({ route }: any) => {
|
|
22
23
|
const Component = route?.params?.screen;
|
|
23
24
|
return (
|
|
@@ -31,6 +32,10 @@ const WidgetContent: React.FC = ({ route }: any) => {
|
|
|
31
32
|
);
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Root of an embedded widget: its own navigator with the widget screen and the
|
|
37
|
+
* `Modal` route, sized to the widget's content height.
|
|
38
|
+
*/
|
|
34
39
|
const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
35
40
|
widget,
|
|
36
41
|
theme = defaultTheme,
|
|
@@ -61,11 +66,9 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
61
66
|
headerGradient = context?.designConfig?.headerGradient;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
/**
|
|
65
|
-
* inject data for navigator
|
|
66
|
-
*/
|
|
67
69
|
navigator.current.maxApi = maxApi;
|
|
68
70
|
|
|
71
|
+
/** Text for a localization key, or the current-language side of `{ vi, en }`. */
|
|
69
72
|
const translate = (data?: string | { vi: string; en: string }) => {
|
|
70
73
|
if (data && typeof data !== 'string') {
|
|
71
74
|
return localize?.translateData(data);
|
|
@@ -81,7 +84,10 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
81
84
|
};
|
|
82
85
|
const isWhiteList = isWhiteListDesignSystem(mergedContext);
|
|
83
86
|
const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
|
|
84
|
-
|
|
87
|
+
let navigationFontFamily = 'SFProText';
|
|
88
|
+
if (isWhiteList) {
|
|
89
|
+
navigationFontFamily = 'MomoTrustSans-Regular';
|
|
90
|
+
}
|
|
85
91
|
|
|
86
92
|
return (
|
|
87
93
|
<View style={[styles.container, { height: height ?? '100%' }]}>
|
|
@@ -73,11 +73,12 @@ type ModalLayoutInput = {
|
|
|
73
73
|
hinge?: ReservedRegion;
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
const clamp = (value: number, min: number, max: number) =>
|
|
77
|
-
Math.min(Math.max(value, min), max);
|
|
76
|
+
const clamp = (value: number, min: number, max: number) => {
|
|
77
|
+
return Math.min(Math.max(value, min), max);
|
|
78
|
+
};
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
|
-
* Modal geometry for a window, pure so it can be tested without a device
|
|
81
|
+
* Modal geometry for a window, pure so it can be tested without a device.
|
|
81
82
|
*/
|
|
82
83
|
const getModalLayout = ({
|
|
83
84
|
width,
|
|
@@ -91,8 +92,9 @@ const getModalLayout = ({
|
|
|
91
92
|
const isFoldable = isDuo || shortSide >= FOLDABLE_MIN_WIDTH;
|
|
92
93
|
const isDuoOuter = isDuo && shortSide < FOLDABLE_MIN_WIDTH;
|
|
93
94
|
const hingeRight = hinge ? hinge.x + hinge.width : 0;
|
|
94
|
-
const
|
|
95
|
-
|
|
95
|
+
const hasHinge = !!hinge && hinge.x > 0;
|
|
96
|
+
const isHingeInWindow = hingeRight < width - insets.right;
|
|
97
|
+
const isHalfFolded = isDuo && hasHinge && isHingeInWindow;
|
|
96
98
|
|
|
97
99
|
const safeLeft = isHalfFolded ? hingeRight : insets.left;
|
|
98
100
|
const placement = {
|
|
@@ -112,9 +114,9 @@ const getModalLayout = ({
|
|
|
112
114
|
minWidth: popupWidth,
|
|
113
115
|
maxWidth: popupWidth,
|
|
114
116
|
margin: Spacing.XL,
|
|
115
|
-
radius: Radius.
|
|
117
|
+
radius: Radius.XL,
|
|
116
118
|
},
|
|
117
|
-
sheet: { margin: 0, radius: Radius.
|
|
119
|
+
sheet: { margin: 0, radius: Radius.XL },
|
|
118
120
|
scrimOpacity: SCRIM_OPACITY,
|
|
119
121
|
};
|
|
120
122
|
}
|
|
@@ -125,6 +127,7 @@ const getModalLayout = ({
|
|
|
125
127
|
|
|
126
128
|
const sheetMargin = Spacing.M;
|
|
127
129
|
const sheetSpace = placement.width - sheetMargin * 2;
|
|
130
|
+
const sheetMinWidth = Math.min(SHEET_MIN_WIDTH, sheetSpace);
|
|
128
131
|
const sheetMaxHeight = height - insets.top - Spacing.M;
|
|
129
132
|
|
|
130
133
|
return {
|
|
@@ -139,11 +142,7 @@ const getModalLayout = ({
|
|
|
139
142
|
radius: Radius.XL,
|
|
140
143
|
},
|
|
141
144
|
sheet: {
|
|
142
|
-
width: clamp(
|
|
143
|
-
sheetSpace,
|
|
144
|
-
Math.min(SHEET_MIN_WIDTH, sheetSpace),
|
|
145
|
-
SHEET_MAX_WIDTH,
|
|
146
|
-
),
|
|
145
|
+
width: clamp(sheetSpace, sheetMinWidth, SHEET_MAX_WIDTH),
|
|
147
146
|
maxHeight: sheetMaxHeight,
|
|
148
147
|
minHeight: Math.min(SHEET_MIN_HEIGHT, sheetMaxHeight),
|
|
149
148
|
margin: sheetMargin,
|
|
@@ -154,8 +153,8 @@ const getModalLayout = ({
|
|
|
154
153
|
};
|
|
155
154
|
|
|
156
155
|
/**
|
|
157
|
-
* Modal geometry for the current window
|
|
158
|
-
* iPhone Duo hinge
|
|
156
|
+
* Modal geometry for the current window; follows fold, unfold, rotation and
|
|
157
|
+
* the iPhone Duo hinge.
|
|
159
158
|
*/
|
|
160
159
|
const useModalLayout = (): ModalLayout => {
|
|
161
160
|
const { width, height } = useWindowDimensions();
|
|
@@ -133,13 +133,15 @@ const exportHeaderTitle = (
|
|
|
133
133
|
};
|
|
134
134
|
} else if (typeof params.headerTitle === 'string') {
|
|
135
135
|
return {
|
|
136
|
-
headerTitle: (props: any) =>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
headerTitle: (props: any) => {
|
|
137
|
+
return (
|
|
138
|
+
<HeaderTitle
|
|
139
|
+
{...props}
|
|
140
|
+
children={params.headerTitle}
|
|
141
|
+
animatedValue={animatedValue}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
},
|
|
143
145
|
};
|
|
144
146
|
}
|
|
145
147
|
return {};
|
|
@@ -156,15 +158,17 @@ const getOptions = (
|
|
|
156
158
|
let options: StackNavigationOptions = {};
|
|
157
159
|
|
|
158
160
|
if (hasCustomHeaderLeft(params)) {
|
|
159
|
-
let headerLeft: any = (props: any) =>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
let headerLeft: any = (props: any) => {
|
|
162
|
+
return (
|
|
163
|
+
<HeaderLeft
|
|
164
|
+
{...props}
|
|
165
|
+
preventBack={params.preventBack}
|
|
166
|
+
onPressLeftHeader={params.onPressLeftHeader}
|
|
167
|
+
onBackHandler={params.onBackHandler}
|
|
168
|
+
floating
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
168
172
|
if (params.hiddenBack) {
|
|
169
173
|
headerLeft = null;
|
|
170
174
|
options.headerTitleContainerStyle = {
|
|
@@ -203,7 +207,7 @@ const getOptions = (
|
|
|
203
207
|
};
|
|
204
208
|
}
|
|
205
209
|
|
|
206
|
-
//
|
|
210
|
+
// Only ever turns swipe back off; screens opt in via gestureEnabled.
|
|
207
211
|
if (params.gestureEnabled === undefined && hasCustomBackHandling(params)) {
|
|
208
212
|
options.gestureEnabled = false;
|
|
209
213
|
}
|
package/Badge/Badge.tsx
CHANGED
|
@@ -7,6 +7,11 @@ import { BadgeProps } from './types';
|
|
|
7
7
|
import { Colors, Radius, Spacing } from '../Consts';
|
|
8
8
|
import { Text, useScaleSize } from '../Text';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Label or count badge; counts over 99 show "99+". Counts use the error color,
|
|
12
|
+
* labels the warning color, and `backgroundColor` applies only if it's a
|
|
13
|
+
* `Colors.*_03` token.
|
|
14
|
+
*/
|
|
10
15
|
const Badge: FC<BadgeProps> = ({
|
|
11
16
|
label = '',
|
|
12
17
|
style,
|
package/Badge/BadgeDot.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { BadgeDotProps } from './types';
|
|
|
4
4
|
import styles from './styles';
|
|
5
5
|
import { MiniAppContext } from '../Context';
|
|
6
6
|
|
|
7
|
+
/** Static red notification dot with a white ring, `large` or `small`. */
|
|
7
8
|
const BadgeDot: FC<BadgeDotProps> = ({ size = 'large', style }) => {
|
|
8
9
|
const context = useContext<any>(MiniAppContext);
|
|
9
10
|
let dotStyle;
|
|
@@ -14,6 +14,10 @@ import styles from './styles';
|
|
|
14
14
|
|
|
15
15
|
const DURATION = 500;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Red notification dot that pulses with a spring and emits an expanding,
|
|
19
|
+
* fading wave ring, looping until unmounted.
|
|
20
|
+
*/
|
|
17
21
|
const BadgeDotAnimation = ({ size, style }: BadgeDotProps) => {
|
|
18
22
|
const scaleAnim = useSharedValue(1);
|
|
19
23
|
const waveScaleAnim = useSharedValue(1);
|
|
@@ -57,14 +61,18 @@ const BadgeDotAnimation = ({ size, style }: BadgeDotProps) => {
|
|
|
57
61
|
};
|
|
58
62
|
}, [scaleAnim, waveOpacityAnim, waveScaleAnim]);
|
|
59
63
|
|
|
60
|
-
const waveAnimatedStyle = useAnimatedStyle(() =>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
const waveAnimatedStyle = useAnimatedStyle(() => {
|
|
65
|
+
return {
|
|
66
|
+
transform: [{ scale: waveScaleAnim.value }],
|
|
67
|
+
opacity: waveOpacityAnim.value,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
64
70
|
|
|
65
|
-
const dotAnimatedStyle = useAnimatedStyle(() =>
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
const dotAnimatedStyle = useAnimatedStyle(() => {
|
|
72
|
+
return {
|
|
73
|
+
transform: [{ scale: scaleAnim.value }],
|
|
74
|
+
};
|
|
75
|
+
});
|
|
68
76
|
|
|
69
77
|
return (
|
|
70
78
|
<View style={[styles.dotAnimationContainer, style]}>
|
package/Badge/BadgeRibbon.tsx
CHANGED
|
@@ -8,6 +8,10 @@ import { Text } from '../Text';
|
|
|
8
8
|
import { Colors } from '../Consts';
|
|
9
9
|
import { Image } from '../Image';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Corner ribbon label in the warning color, skewed or `isRound`. Right-corner
|
|
13
|
+
* ribbons are rotated 180deg with the text counter-rotated to stay upright.
|
|
14
|
+
*/
|
|
11
15
|
const BadgeRibbon: FC<BadgeRibbonProps> = ({
|
|
12
16
|
position = 'top_right',
|
|
13
17
|
label = 'Label',
|
|
@@ -33,39 +37,16 @@ const BadgeRibbon: FC<BadgeRibbonProps> = ({
|
|
|
33
37
|
const textTransform = useMemo(() => [{ rotateZ: textRotate }], [textRotate]);
|
|
34
38
|
|
|
35
39
|
const ribbonTail = isUpTail ? <UpTail /> : <DownTail />;
|
|
36
|
-
const renderRoundContent = () =>
|
|
37
|
-
|
|
38
|
-
collapsable={false}
|
|
39
|
-
style={[
|
|
40
|
-
styles.ribbonContent,
|
|
41
|
-
{
|
|
42
|
-
backgroundColor: theme.colors.warning.primary,
|
|
43
|
-
marginTop: textMargin,
|
|
44
|
-
},
|
|
45
|
-
]}
|
|
46
|
-
>
|
|
47
|
-
<View
|
|
48
|
-
collapsable={false}
|
|
49
|
-
style={[styles.ribbonLabelWrapper, { transform: textTransform }]}
|
|
50
|
-
>
|
|
51
|
-
<Text
|
|
52
|
-
style={styles.ribbonLabel}
|
|
53
|
-
color={Colors.black_01}
|
|
54
|
-
typography="label_xs_medium"
|
|
55
|
-
>
|
|
56
|
-
{label}
|
|
57
|
-
</Text>
|
|
58
|
-
</View>
|
|
59
|
-
</View>
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
const renderSkewContent = () => (
|
|
63
|
-
<View style={[styles.ribbonSkewRow, { marginTop: skewMargin }]}>
|
|
40
|
+
const renderRoundContent = () => {
|
|
41
|
+
return (
|
|
64
42
|
<View
|
|
65
43
|
collapsable={false}
|
|
66
44
|
style={[
|
|
67
|
-
styles.
|
|
68
|
-
{
|
|
45
|
+
styles.ribbonContent,
|
|
46
|
+
{
|
|
47
|
+
backgroundColor: theme.colors.warning.primary,
|
|
48
|
+
marginTop: textMargin,
|
|
49
|
+
},
|
|
69
50
|
]}
|
|
70
51
|
>
|
|
71
52
|
<View
|
|
@@ -81,14 +62,41 @@ const BadgeRibbon: FC<BadgeRibbonProps> = ({
|
|
|
81
62
|
</Text>
|
|
82
63
|
</View>
|
|
83
64
|
</View>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const renderSkewContent = () => {
|
|
69
|
+
return (
|
|
70
|
+
<View style={[styles.ribbonSkewRow, { marginTop: skewMargin }]}>
|
|
71
|
+
<View
|
|
72
|
+
collapsable={false}
|
|
73
|
+
style={[
|
|
74
|
+
styles.ribbonSkewContent,
|
|
75
|
+
{ backgroundColor: theme.colors.warning.primary },
|
|
76
|
+
]}
|
|
77
|
+
>
|
|
78
|
+
<View
|
|
79
|
+
collapsable={false}
|
|
80
|
+
style={[styles.ribbonLabelWrapper, { transform: textTransform }]}
|
|
81
|
+
>
|
|
82
|
+
<Text
|
|
83
|
+
style={styles.ribbonLabel}
|
|
84
|
+
color={Colors.black_01}
|
|
85
|
+
typography="label_xs_medium"
|
|
86
|
+
>
|
|
87
|
+
{label}
|
|
88
|
+
</Text>
|
|
89
|
+
</View>
|
|
90
|
+
</View>
|
|
91
|
+
<Image
|
|
92
|
+
source={{
|
|
93
|
+
uri: 'https://static.momocdn.net/app/img/kits/utils/Tail_4x.png',
|
|
94
|
+
}}
|
|
95
|
+
style={styles.skew}
|
|
96
|
+
/>
|
|
97
|
+
</View>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
92
100
|
|
|
93
101
|
const content = isRound ? renderRoundContent() : renderSkewContent();
|
|
94
102
|
|
package/Badge/Shape.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import styles from './styles';
|
|
3
3
|
import {Image} from '../Image';
|
|
4
4
|
|
|
5
|
+
/** Ribbon tail image pointing up (the down tail rotated 180deg). */
|
|
5
6
|
const UpTail = () => {
|
|
6
7
|
return (
|
|
7
8
|
<Image
|
|
@@ -14,6 +15,7 @@ const UpTail = () => {
|
|
|
14
15
|
);
|
|
15
16
|
};
|
|
16
17
|
|
|
18
|
+
/** Ribbon tail image pointing down. */
|
|
17
19
|
const DownTail = () => {
|
|
18
20
|
return (
|
|
19
21
|
<Image
|
package/Badge/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {ViewStyle} from 'react-native';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Props for a
|
|
4
|
+
* Props for a Badge component. It displays a short label or a number count.
|
|
5
5
|
*/
|
|
6
6
|
export type BadgeProps = {
|
|
7
7
|
/**
|
|
@@ -20,7 +20,7 @@ export type BadgeProps = {
|
|
|
20
20
|
backgroundColor?: string;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Optional. Overrides the generated component ID used for accessibility and QA.
|
|
24
24
|
*/
|
|
25
25
|
accessibilityLabel?: string;
|
|
26
26
|
};
|
package/Button/index.tsx
CHANGED
|
@@ -63,6 +63,11 @@ export interface ButtonProps extends TouchableOpacityProps {
|
|
|
63
63
|
params?: any;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Design-system button: `type` sets the colors, `size` the height and text,
|
|
68
|
+
* and `loading` swaps an icon slot for a spinner. Primary buttons use the mini
|
|
69
|
+
* app gradient from `config.navigationBar.buttonColors` when set.
|
|
70
|
+
*/
|
|
66
71
|
const Button: FC<ButtonProps> = ({
|
|
67
72
|
type = 'primary',
|
|
68
73
|
size = 'large',
|
|
@@ -90,33 +95,21 @@ const Button: FC<ButtonProps> = ({
|
|
|
90
95
|
rest.accessibilityLabel,
|
|
91
96
|
);
|
|
92
97
|
|
|
93
|
-
/**
|
|
94
|
-
* animate press in (Reanimated)
|
|
95
|
-
*/
|
|
96
98
|
const animateIn = (event: GestureResponderEvent) => {
|
|
97
99
|
rest?.onPressIn?.(event);
|
|
98
100
|
pressAnim.value = withTiming(1, { duration: 100 });
|
|
99
101
|
};
|
|
100
102
|
|
|
101
|
-
/**
|
|
102
|
-
* animate press out (Reanimated)
|
|
103
|
-
*/
|
|
104
103
|
const animateOut = (event: GestureResponderEvent) => {
|
|
105
104
|
rest?.onPressOut?.(event);
|
|
106
105
|
pressAnim.value = withTiming(0, { duration: 100 });
|
|
107
106
|
};
|
|
108
107
|
|
|
109
|
-
/**
|
|
110
|
-
* export size style
|
|
111
|
-
*/
|
|
112
108
|
const getSizeStyle = () => {
|
|
113
109
|
const styleSheet: { [key: string]: any } = styles;
|
|
114
110
|
return styleSheet[size ?? 'small'];
|
|
115
111
|
};
|
|
116
112
|
|
|
117
|
-
/**
|
|
118
|
-
* render title
|
|
119
|
-
*/
|
|
120
113
|
const renderTitle = () => {
|
|
121
114
|
const typography = getTypography(size);
|
|
122
115
|
const textColor = getTextColor(type, theme, color);
|
|
@@ -127,10 +120,6 @@ const Button: FC<ButtonProps> = ({
|
|
|
127
120
|
);
|
|
128
121
|
};
|
|
129
122
|
|
|
130
|
-
/**
|
|
131
|
-
* render icon
|
|
132
|
-
* @param position
|
|
133
|
-
*/
|
|
134
123
|
const renderIcon = (position: 'left' | 'right') => {
|
|
135
124
|
const iconSize = getIconSize(size);
|
|
136
125
|
const space = getIconSpace(size);
|