@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.
Files changed (143) hide show
  1. package/Application/BottomSheet.hooks.ts +8 -8
  2. package/Application/BottomSheet.parts.tsx +44 -34
  3. package/Application/BottomSheet.tsx +40 -37
  4. package/Application/BottomTab/ActiveIndicator.tsx +20 -15
  5. package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
  6. package/Application/BottomTab/BottomTabBar.tsx +57 -72
  7. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  8. package/Application/BottomTab/CurvedContainer.tsx +22 -14
  9. package/Application/BottomTab/CustomBottomTabItem.tsx +15 -15
  10. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  11. package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
  12. package/Application/BottomTab/DuoTabBar.tsx +23 -13
  13. package/Application/BottomTab/TabBarIcon.tsx +15 -29
  14. package/Application/BottomTab/TabScreen.tsx +1 -5
  15. package/Application/BottomTab/index.tsx +33 -30
  16. package/Application/BottomTab/metrics.ts +46 -26
  17. package/Application/BottomTab/tabItems.ts +1 -1
  18. package/Application/BottomTab/usePressLift.ts +2 -2
  19. package/Application/Components/BackgroundImageView.tsx +4 -0
  20. package/Application/Components/DuoGlassBackground.tsx +5 -4
  21. package/Application/Components/HeaderAnimated.tsx +4 -0
  22. package/Application/Components/HeaderBackground.tsx +4 -0
  23. package/Application/Components/HeaderExtendHeader.tsx +11 -10
  24. package/Application/Components/HeaderLeft.tsx +2 -1
  25. package/Application/Components/HeaderRight.tsx +2 -1
  26. package/Application/Components/HeaderTitle.shared.ts +11 -9
  27. package/Application/Components/HeaderTitle.tsx +4 -3
  28. package/Application/Components/HeaderToolkitAction.tsx +17 -20
  29. package/Application/Components/NavigationButton.tsx +2 -1
  30. package/Application/Components/SearchHeader.tsx +17 -10
  31. package/Application/Components/TitleJourney.tsx +2 -2
  32. package/Application/Components/TitleLocation.tsx +2 -2
  33. package/Application/Components/TitleUser.tsx +2 -2
  34. package/Application/Components/useToolkitActions.tsx +17 -32
  35. package/Application/Localize.ts +7 -0
  36. package/Application/ModalScreen.tsx +10 -10
  37. package/Application/Navigation.ts +14 -16
  38. package/Application/NavigationContainer.tsx +19 -18
  39. package/Application/Navigator.ts +20 -48
  40. package/Application/ScaleSizeProvider.tsx +13 -6
  41. package/Application/StackScreen.tracking.ts +11 -33
  42. package/Application/StackScreen.tsx +10 -54
  43. package/Application/TooltipPortal.tsx +12 -5
  44. package/Application/WidgetContainer.tsx +10 -4
  45. package/Application/modalLayout.ts +13 -14
  46. package/Application/navigationOptions.tsx +21 -17
  47. package/Application/types.tabs.ts +0 -1
  48. package/Badge/Badge.tsx +5 -0
  49. package/Badge/BadgeDot.tsx +1 -0
  50. package/Badge/BadgeDotAnimation.tsx +15 -7
  51. package/Badge/BadgeRibbon.tsx +46 -38
  52. package/Badge/Shape.tsx +2 -0
  53. package/Badge/types.ts +2 -2
  54. package/Button/index.tsx +5 -16
  55. package/Button/utils.ts +9 -15
  56. package/CheckBox/index.tsx +4 -0
  57. package/Consts/theme.ts +10 -10
  58. package/Context/index.ts +5 -1
  59. package/Divider/DashDivider.tsx +1 -0
  60. package/Divider/index.tsx +4 -0
  61. package/FoundationList/index.tsx +4 -0
  62. package/Icon/index.tsx +6 -6
  63. package/Icon/types.ts +1 -1
  64. package/IconButton/index.tsx +4 -12
  65. package/Image/index.tsx +9 -7
  66. package/Input/Input.tsx +8 -9
  67. package/Input/InputDropDown.tsx +10 -12
  68. package/Input/InputMoney.tsx +12 -8
  69. package/Input/InputMoneyView.tsx +14 -7
  70. package/Input/InputOTP.tsx +35 -33
  71. package/Input/InputPhoneNumber.tsx +8 -9
  72. package/Input/InputSearch.tsx +4 -6
  73. package/Input/InputTextArea.tsx +4 -0
  74. package/Input/SearchTextTyping.tsx +4 -0
  75. package/Input/SystemTextInput.tsx +7 -2
  76. package/Input/TextTyping.tsx +4 -0
  77. package/Input/common.tsx +26 -12
  78. package/Input/index.tsx +17 -20
  79. package/Input/moneyUtils.ts +10 -4
  80. package/Input/styles.ts +0 -5
  81. package/Input/utils.ts +14 -12
  82. package/Layout/Card.tsx +7 -4
  83. package/Layout/FloatingButton.tsx +4 -0
  84. package/Layout/Item.tsx +6 -2
  85. package/Layout/ItemList.tsx +9 -7
  86. package/Layout/ItemSectionList.tsx +2 -1
  87. package/Layout/ReservedRegions.tsx +25 -25
  88. package/Layout/Screen.tsx +56 -58
  89. package/Layout/Section.tsx +8 -5
  90. package/Layout/TrackingScope.tsx +1 -0
  91. package/Layout/screenContent.tsx +4 -2
  92. package/Layout/screenFooter.tsx +28 -18
  93. package/Layout/screenHeader.tsx +34 -31
  94. package/Layout/screenQuickReturn.tsx +17 -20
  95. package/Layout/screenScroll.ts +4 -7
  96. package/Layout/screenTypes.ts +3 -2
  97. package/Layout/types.ts +2 -4
  98. package/Layout/utils.ts +31 -37
  99. package/Loader/DotLoader.tsx +4 -0
  100. package/Loader/Loader.tsx +4 -0
  101. package/Loader/ProgressBar.tsx +1 -0
  102. package/Loader/Spinner.tsx +4 -0
  103. package/Loader/utils.ts +11 -7
  104. package/Pagination/Dot.tsx +4 -0
  105. package/Pagination/PaginationDot.tsx +1 -0
  106. package/Pagination/PaginationNumber.tsx +5 -3
  107. package/Pagination/PaginationScroll.tsx +5 -0
  108. package/Pagination/PaginationWhiteDot.tsx +1 -0
  109. package/Pagination/index.tsx +4 -0
  110. package/Pagination/types.ts +2 -2
  111. package/Popup/PopupNotify.tsx +44 -48
  112. package/Popup/PopupPromotion.tsx +4 -7
  113. package/Popup/types.ts +4 -4
  114. package/Popup/utils.ts +4 -0
  115. package/Radio/index.tsx +4 -0
  116. package/Skeleton/index.tsx +8 -0
  117. package/Switch/index.tsx +1 -0
  118. package/Tag/index.tsx +5 -1
  119. package/Tag/types.ts +3 -3
  120. package/Text/index.tsx +14 -3
  121. package/Text/styles.ts +1 -1
  122. package/Text/types.ts +1 -6
  123. package/Text/utils.ts +20 -4
  124. package/Title/index.tsx +41 -32
  125. package/ViewBoundary/index.tsx +4 -2
  126. package/index.ts +0 -6
  127. package/package.json +1 -1
  128. package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
  129. package/Application/KitAdoption/AdoptionProbe.tsx +0 -166
  130. package/Application/KitAdoption/AdoptionStore.ts +0 -80
  131. package/Application/KitAdoption/Authorship/utils.ts +0 -137
  132. package/Application/KitAdoption/Slots/utils.ts +0 -68
  133. package/Application/KitAdoption/constants.ts +0 -120
  134. package/Application/KitAdoption/index.ts +0 -12
  135. package/Application/KitAdoption/styles.ts +0 -55
  136. package/Application/KitAdoption/types.ts +0 -61
  137. package/Application/KitAdoption/utils.ts +0 -376
  138. package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
  139. package/Application/__tests__/KitAdoption.test.ts +0 -283
  140. package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
  141. package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
  142. package/Application/__tests__/KitAdoptionStructural.test.ts +0 -193
  143. 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
- * container for stack screen
18
- * @param props
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.timeInteraction =
238
- Date.now() - tracking.current.startTime;
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
- <AdoptionProbe
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
- // Use ref for synchronous updates + reducer for manual re-renders
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(); // Trigger re-render to show new content
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; // Already removed
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
- const navigationFontFamily = isWhiteList ? 'MomoTrustSans-Regular' : 'SFProText';
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 isHalfFolded =
95
- isDuo && !!hinge && hinge.x > 0 && hingeRight < width - insets.right;
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.L,
117
+ radius: Radius.XL,
116
118
  },
117
- sheet: { margin: 0, radius: Radius.M },
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, follows fold, unfold, rotation and the
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
- <HeaderTitle
138
- {...props}
139
- children={params.headerTitle}
140
- animatedValue={animatedValue}
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
- <HeaderLeft
161
- {...props}
162
- preventBack={params.preventBack}
163
- onPressLeftHeader={params.onPressLeftHeader}
164
- onBackHandler={params.onBackHandler}
165
- floating
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
- // only ever turns swipe back off, screens opt in via gestureEnabled
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
  }
@@ -36,7 +36,6 @@ export type BottomTabProps = {
36
36
  };
37
37
 
38
38
  export type FloatingButtonProps = {
39
- container?: React.ReactNode;
40
39
  icon: string | React.ReactNode;
41
40
  label: string;
42
41
  onPress: () => void;
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,
@@ -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
- transform: [{ scale: waveScaleAnim.value }],
62
- opacity: waveOpacityAnim.value,
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
- transform: [{ scale: scaleAnim.value }],
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]}>
@@ -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
- <View
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.ribbonSkewContent,
68
- { backgroundColor: theme.colors.warning.primary },
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
- <Image
85
- source={{
86
- uri: 'https://static.momocdn.net/app/img/kits/utils/Tail_4x.png',
87
- }}
88
- style={styles.skew}
89
- />
90
- </View>
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 general Badge component. It can display a wide range of badges including simple labels, numbers, or dots.
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);