@momo-kits/foundation 0.169.6 → 0.169.7-beta.10

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 +34 -44
  3. package/Application/BottomSheet.tsx +37 -40
  4. package/Application/BottomTab/ActiveIndicator.tsx +14 -15
  5. package/Application/BottomTab/BottomTabBar.tsx +62 -49
  6. package/Application/BottomTab/BottomTabInset.tsx +4 -8
  7. package/Application/BottomTab/CurvedContainer.tsx +12 -14
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
  9. package/Application/BottomTab/DuoBarCompression.tsx +19 -13
  10. package/Application/BottomTab/DuoRailTabBar.tsx +24 -20
  11. package/Application/BottomTab/DuoTabBar.tsx +10 -8
  12. package/Application/BottomTab/TabBarIcon.tsx +29 -15
  13. package/Application/BottomTab/TabScreen.tsx +5 -1
  14. package/Application/BottomTab/index.tsx +30 -33
  15. package/Application/BottomTab/metrics.ts +23 -23
  16. package/Application/BottomTab/tabItems.ts +1 -1
  17. package/Application/BottomTab/usePressLift.ts +2 -2
  18. package/Application/Components/BackgroundImageView.tsx +0 -4
  19. package/Application/Components/DuoGlassBackground.tsx +4 -5
  20. package/Application/Components/HeaderAnimated.tsx +0 -4
  21. package/Application/Components/HeaderBackground.tsx +0 -4
  22. package/Application/Components/HeaderExtendHeader.tsx +10 -11
  23. package/Application/Components/HeaderLeft.tsx +1 -2
  24. package/Application/Components/HeaderRight.tsx +1 -2
  25. package/Application/Components/HeaderTitle.shared.ts +9 -11
  26. package/Application/Components/HeaderTitle.tsx +3 -4
  27. package/Application/Components/HeaderToolkitAction.tsx +20 -17
  28. package/Application/Components/NavigationButton.tsx +1 -2
  29. package/Application/Components/SearchHeader.tsx +10 -17
  30. package/Application/Components/TitleJourney.tsx +2 -2
  31. package/Application/Components/TitleLocation.tsx +2 -2
  32. package/Application/Components/TitleUser.tsx +2 -2
  33. package/Application/Components/useToolkitActions.tsx +32 -17
  34. package/Application/KitAdoption/AdoptionBadge.tsx +119 -0
  35. package/Application/KitAdoption/AdoptionProbe.tsx +166 -0
  36. package/Application/KitAdoption/AdoptionStore.ts +80 -0
  37. package/Application/KitAdoption/Authorship/utils.ts +137 -0
  38. package/Application/KitAdoption/Slots/utils.ts +68 -0
  39. package/Application/KitAdoption/constants.ts +120 -0
  40. package/Application/KitAdoption/index.ts +12 -0
  41. package/Application/KitAdoption/styles.ts +55 -0
  42. package/Application/KitAdoption/types.ts +61 -0
  43. package/Application/KitAdoption/utils.ts +376 -0
  44. package/Application/Localize.ts +0 -7
  45. package/Application/ModalScreen.tsx +10 -10
  46. package/Application/Navigation.ts +16 -14
  47. package/Application/NavigationContainer.tsx +18 -19
  48. package/Application/Navigator.ts +48 -20
  49. package/Application/ScaleSizeProvider.tsx +6 -13
  50. package/Application/StackScreen.tracking.ts +33 -11
  51. package/Application/StackScreen.tsx +54 -10
  52. package/Application/TooltipPortal.tsx +5 -12
  53. package/Application/WidgetContainer.tsx +4 -10
  54. package/Application/__tests__/KitAdoption.integration.test.tsx +253 -0
  55. package/Application/__tests__/KitAdoption.test.ts +283 -0
  56. package/Application/__tests__/KitAdoptionAuthorship.test.ts +132 -0
  57. package/Application/__tests__/KitAdoptionStore.test.ts +110 -0
  58. package/Application/__tests__/KitAdoptionStructural.test.ts +193 -0
  59. package/Application/__tests__/modalLayout.test.ts +128 -0
  60. package/Application/modalLayout.ts +14 -13
  61. package/Application/navigationOptions.tsx +17 -21
  62. package/Badge/Badge.tsx +0 -5
  63. package/Badge/BadgeDot.tsx +0 -1
  64. package/Badge/BadgeDotAnimation.tsx +7 -15
  65. package/Badge/BadgeRibbon.tsx +38 -46
  66. package/Badge/Shape.tsx +0 -2
  67. package/Badge/types.ts +2 -2
  68. package/Button/index.tsx +16 -5
  69. package/Button/utils.ts +15 -9
  70. package/CheckBox/index.tsx +0 -4
  71. package/Consts/theme.ts +10 -10
  72. package/Context/index.ts +1 -5
  73. package/Divider/DashDivider.tsx +0 -1
  74. package/Divider/index.tsx +0 -4
  75. package/FoundationList/index.tsx +0 -4
  76. package/Icon/index.tsx +6 -6
  77. package/Icon/types.ts +1 -1
  78. package/IconButton/index.tsx +12 -4
  79. package/Image/index.tsx +7 -9
  80. package/Input/Input.tsx +9 -8
  81. package/Input/InputDropDown.tsx +12 -10
  82. package/Input/InputMoney.tsx +8 -12
  83. package/Input/InputMoneyView.tsx +7 -14
  84. package/Input/InputOTP.tsx +33 -35
  85. package/Input/InputPhoneNumber.tsx +9 -8
  86. package/Input/InputSearch.tsx +6 -4
  87. package/Input/InputTextArea.tsx +0 -4
  88. package/Input/SearchTextTyping.tsx +0 -4
  89. package/Input/SystemTextInput.tsx +2 -7
  90. package/Input/TextTyping.tsx +0 -4
  91. package/Input/common.tsx +12 -26
  92. package/Input/index.tsx +20 -17
  93. package/Input/moneyUtils.ts +4 -10
  94. package/Input/styles.ts +5 -0
  95. package/Input/utils.ts +12 -14
  96. package/Layout/Card.tsx +13 -16
  97. package/Layout/FloatingButton.tsx +0 -4
  98. package/Layout/Item.tsx +2 -6
  99. package/Layout/ItemList.tsx +7 -9
  100. package/Layout/ItemSectionList.tsx +1 -2
  101. package/Layout/ReservedRegions.tsx +25 -25
  102. package/Layout/Screen.tsx +58 -56
  103. package/Layout/Section.tsx +13 -17
  104. package/Layout/TrackingScope.tsx +0 -1
  105. package/Layout/constants.ts +19 -0
  106. package/Layout/screenContent.tsx +2 -4
  107. package/Layout/screenFooter.tsx +18 -28
  108. package/Layout/screenHeader.tsx +31 -34
  109. package/Layout/screenQuickReturn.tsx +20 -17
  110. package/Layout/screenScroll.ts +7 -4
  111. package/Layout/screenTypes.ts +2 -3
  112. package/Layout/types.ts +41 -3
  113. package/Layout/useGridSystem.ts +49 -0
  114. package/Layout/utils.ts +37 -38
  115. package/Loader/DotLoader.tsx +0 -4
  116. package/Loader/Loader.tsx +0 -4
  117. package/Loader/ProgressBar.tsx +0 -1
  118. package/Loader/Spinner.tsx +0 -4
  119. package/Loader/utils.ts +7 -11
  120. package/Pagination/Dot.tsx +0 -4
  121. package/Pagination/PaginationDot.tsx +0 -1
  122. package/Pagination/PaginationNumber.tsx +3 -5
  123. package/Pagination/PaginationScroll.tsx +0 -5
  124. package/Pagination/PaginationWhiteDot.tsx +0 -1
  125. package/Pagination/index.tsx +0 -4
  126. package/Pagination/types.ts +2 -2
  127. package/Popup/PopupNotify.tsx +48 -44
  128. package/Popup/PopupPromotion.tsx +7 -4
  129. package/Popup/types.ts +4 -4
  130. package/Popup/utils.ts +0 -4
  131. package/Radio/index.tsx +0 -4
  132. package/Skeleton/index.tsx +0 -8
  133. package/Switch/index.tsx +0 -1
  134. package/Tag/index.tsx +1 -5
  135. package/Tag/types.ts +3 -3
  136. package/Text/index.tsx +3 -14
  137. package/Text/styles.ts +1 -1
  138. package/Text/types.ts +6 -1
  139. package/Text/utils.ts +4 -20
  140. package/Title/index.tsx +32 -41
  141. package/ViewBoundary/index.tsx +2 -4
  142. package/index.ts +6 -0
  143. package/package.json +1 -1
@@ -0,0 +1,376 @@
1
+ import {
2
+ COUNTED_FIBER_TAGS,
3
+ FIBER_TAG,
4
+ FOUNDATION_REEXPORT_NAMES,
5
+ HOST_RN_PREFIXES,
6
+ LIB_UNIT_NAMES,
7
+ MAX_FIBERS,
8
+ REACT_FORWARD_REF_TYPE,
9
+ REACT_MEMO_TYPE,
10
+ RN_PRIMITIVE_NAMES,
11
+ TOP_TRANSPARENT_LIMIT,
12
+ } from './constants';
13
+ import {
14
+ getAuthorFiber,
15
+ isAppAuthored,
16
+ isHostDefined,
17
+ } from './Authorship/utils';
18
+ import { collectCellSlots, collectSlotProps, isListCell } from './Slots/utils';
19
+ import type {
20
+ AdoptionBucket,
21
+ AdoptionTrackingParams,
22
+ FiberLike,
23
+ KitRegistries,
24
+ SnapshotStatus,
25
+ WalkResult,
26
+ } from './types';
27
+
28
+ const ANONYMOUS_NAME = 'Anonymous';
29
+ const UNKNOWN_APP_ID = 'unknown';
30
+
31
+ type LooseObject = Record<string, unknown>;
32
+ type Classification = AdoptionBucket | 'unit' | 'transparent';
33
+ type WalkEntry = { fiber: FiberLike; unitSlots: Set<unknown> | null };
34
+ type Tally = {
35
+ counts: Record<AdoptionBucket, number>;
36
+ names: Record<AdoptionBucket, Record<string, number>>;
37
+ transparent: Record<string, number>;
38
+ appAuthoredSeen: boolean;
39
+ };
40
+
41
+ /**
42
+ * True for non-null objects and functions, the only values React fibers and
43
+ * component types can hold.
44
+ */
45
+ const isObjectLike = (value: unknown): value is LooseObject =>
46
+ (typeof value === 'object' && value !== null) || typeof value === 'function';
47
+
48
+ /** Non-empty string name, or undefined. */
49
+ const readName = (value: unknown): string | undefined =>
50
+ typeof value === 'string' && value !== '' ? value : undefined;
51
+
52
+ const readNameOf = (value: unknown): string | undefined =>
53
+ isObjectLike(value) ? readName(value.name) : undefined;
54
+
55
+ /** Runs a read that may throw (RN exports use throwing getters); returns undefined on failure. */
56
+ const readSafely = (read: () => unknown): unknown => {
57
+ try {
58
+ return read();
59
+ } catch {
60
+ return undefined;
61
+ }
62
+ };
63
+
64
+ /** Functions and React exotic objects ($$typeof) are component candidates. */
65
+ const isComponentLike = (value: unknown): value is object =>
66
+ typeof value === 'function' ||
67
+ (isObjectLike(value) && value.$$typeof !== undefined);
68
+
69
+ const isInSet = (set: WeakSet<object>, value: unknown): boolean =>
70
+ isObjectLike(value) && set.has(value);
71
+
72
+ /** Adds a non-null object-typed count, keeping prototype keys (e.g. "constructor") out of the lookup. */
73
+ const incrementName = (record: Record<string, number>, name: string): void => {
74
+ const current = Object.prototype.hasOwnProperty.call(record, name)
75
+ ? record[name]
76
+ : 0;
77
+ record[name] = current + 1;
78
+ };
79
+
80
+ /**
81
+ * Returns the live copy of a fiber: the one under the committed HostRoot,
82
+ * otherwise its alternate. Walking a stale copy would count the old tree.
83
+ */
84
+ const getCurrentFiber = (fiber: FiberLike): FiberLike => {
85
+ let hostRoot: FiberLike | null | undefined = fiber;
86
+ while (hostRoot && hostRoot.tag !== FIBER_TAG.HostRoot) {
87
+ hostRoot = hostRoot.return;
88
+ }
89
+ if (!hostRoot) return fiber;
90
+ const root = hostRoot.stateNode as { current?: unknown } | null | undefined;
91
+ return root?.current === hostRoot ? fiber : fiber.alternate ?? fiber;
92
+ };
93
+
94
+ /** Inner component of a React.memo or forwardRef wrapper, or undefined for other types. */
95
+ const getWrappedType = (type: LooseObject): unknown => {
96
+ if (type.$$typeof === REACT_MEMO_TYPE) return type.type;
97
+ if (type.$$typeof === REACT_FORWARD_REF_TYPE) return type.render;
98
+ return undefined;
99
+ };
100
+
101
+ /**
102
+ * Strips memo and forwardRef wrappers (recursively) to reach the underlying
103
+ * component, since a fiber's type is often the wrapper.
104
+ */
105
+ const unwrapType = (type: unknown): unknown => {
106
+ if (!isObjectLike(type)) return type;
107
+ const inner = getWrappedType(type);
108
+ return inner === undefined ? type : unwrapType(inner);
109
+ };
110
+
111
+ /**
112
+ * Display name for a fiber type, following wrappers when the type has no name
113
+ * of its own. Used for lib-unit matching and the report.
114
+ */
115
+ const getTypeName = (type: unknown): string => {
116
+ if (typeof type === 'string') return type;
117
+ if (!isObjectLike(type)) return ANONYMOUS_NAME;
118
+ return (
119
+ readName(type.displayName) ??
120
+ readName(type.name) ??
121
+ readNameOf(type.render) ??
122
+ readNameOf(type.type) ??
123
+ ANONYMOUS_NAME
124
+ );
125
+ };
126
+
127
+ /** Adds a component and its unwrapped form to the set. */
128
+ const addRegistryEntry = (set: WeakSet<object>, value: unknown): void => {
129
+ if (!isComponentLike(value)) return;
130
+ set.add(value);
131
+ const inner = unwrapType(value);
132
+ if (isObjectLike(inner)) set.add(inner);
133
+ };
134
+
135
+ /**
136
+ * Builds the identity sets used to classify fibers. RN is read by name only,
137
+ * because enumerating react-native exports hits throwing getters.
138
+ */
139
+ const createRegistries = (
140
+ kitModules: Record<string, unknown>[],
141
+ rnModule: Record<string, unknown>,
142
+ ): KitRegistries => {
143
+ const kit = new WeakSet<object>();
144
+ const rn = new WeakSet<object>();
145
+ kitModules.forEach(mod => {
146
+ Object.keys(mod).forEach(name => {
147
+ if (FOUNDATION_REEXPORT_NAMES.includes(name)) return;
148
+ addRegistryEntry(
149
+ kit,
150
+ readSafely(() => mod[name]),
151
+ );
152
+ });
153
+ });
154
+ RN_PRIMITIVE_NAMES.forEach(name => {
155
+ addRegistryEntry(
156
+ rn,
157
+ readSafely(() => rnModule[name]),
158
+ );
159
+ });
160
+ return { kit, rn };
161
+ };
162
+
163
+ /** App id from the mini-app context, or a placeholder when it is missing. */
164
+ const getAppId = (context: unknown): string => {
165
+ if (!isObjectLike(context) || context.appId == null) return UNKNOWN_APP_ID;
166
+ return String(context.appId);
167
+ };
168
+
169
+ /** True for host string types that come from react-native's native components. */
170
+ const isRnHostType = (type: unknown): boolean =>
171
+ typeof type === 'string' &&
172
+ HOST_RN_PREFIXES.some(prefix => type.startsWith(prefix));
173
+
174
+ /**
175
+ * Buckets a fiber: K/R/O for kit, RN and other components, 'unit' for lib
176
+ * units (counted once), or 'transparent' for app wrappers that are not counted.
177
+ */
178
+ const classifyFiber = (
179
+ fiber: FiberLike,
180
+ registries: KitRegistries,
181
+ ): Classification => {
182
+ if (fiber.tag === FIBER_TAG.HostComponent) {
183
+ return isRnHostType(fiber.type) ? 'R' : 'O';
184
+ }
185
+ const inner = unwrapType(fiber.type);
186
+ if (isInSet(registries.kit, fiber.type) || isInSet(registries.kit, inner)) {
187
+ return 'K';
188
+ }
189
+ if (isInSet(registries.rn, fiber.type) || isInSet(registries.rn, inner)) {
190
+ return 'R';
191
+ }
192
+ if (LIB_UNIT_NAMES.includes(getTypeName(fiber.type))) return 'unit';
193
+ return isHostDefined(fiber) ? 'O' : 'transparent';
194
+ };
195
+
196
+ const createTally = (): Tally => ({
197
+ counts: { K: 0, R: 0, O: 0 },
198
+ names: { K: {}, R: {}, O: {} },
199
+ transparent: {},
200
+ appAuthoredSeen: false,
201
+ });
202
+
203
+ const countComponent = (
204
+ tally: Tally,
205
+ bucket: AdoptionBucket,
206
+ name: string,
207
+ ): void => {
208
+ tally.counts[bucket] += 1;
209
+ incrementName(tally.names[bucket], name);
210
+ };
211
+
212
+ /** Top transparent wrapper names by count, most frequent first. */
213
+ const topEntries = (
214
+ record: Record<string, number>,
215
+ ): { name: string; count: number }[] =>
216
+ Object.keys(record)
217
+ .map(name => ({ name, count: record[name] }))
218
+ .sort((a, b) => b.count - a.count)
219
+ .slice(0, TOP_TRANSPARENT_LIMIT);
220
+
221
+ /** Status 'dev_bundle' means no fiber was authored by a bundle other than the host's. */
222
+ const getStatus = (
223
+ truncated: boolean,
224
+ fibers: number,
225
+ appAuthoredSeen: boolean,
226
+ ): SnapshotStatus => {
227
+ if (truncated) return 'truncated';
228
+ if (fibers > 0 && !appAuthoredSeen) return 'dev_bundle';
229
+ return 'ok';
230
+ };
231
+
232
+ const buildResult = (
233
+ tally: Tally,
234
+ fibers: number,
235
+ truncated: boolean,
236
+ ): WalkResult => ({
237
+ counts: { ...tally.counts },
238
+ kit: tally.names.K,
239
+ rn: tally.names.R,
240
+ other: tally.names.O,
241
+ top_transparent: topEntries(tally.transparent),
242
+ fibers,
243
+ truncated,
244
+ status: getStatus(truncated, fibers, tally.appAuthoredSeen),
245
+ });
246
+
247
+ /**
248
+ * Queues the children of a fiber (and their siblings) with the given slot set.
249
+ * Slot sets only pass through lib-unit internals and walk-through fibers.
250
+ */
251
+ const pushChildren = (
252
+ fiber: FiberLike,
253
+ unitSlots: Set<unknown> | null,
254
+ stack: WalkEntry[],
255
+ ): void => {
256
+ for (let child = fiber.child; child; child = child.sibling) {
257
+ stack.push({ fiber: child, unitSlots });
258
+ }
259
+ };
260
+
261
+ /**
262
+ * Counts one app-scope fiber. A lib unit counts as K once and, in structural
263
+ * mode, so does every K/R/O fiber; only the app-passed slot descendants are walked on.
264
+ */
265
+ const countFiber = (
266
+ fiber: FiberLike,
267
+ registries: KitRegistries,
268
+ tally: Tally,
269
+ stack: WalkEntry[],
270
+ isStructural: boolean,
271
+ ): void => {
272
+ const name = getTypeName(fiber.type);
273
+ const kind = classifyFiber(fiber, registries);
274
+ if (kind === 'transparent') {
275
+ incrementName(tally.transparent, name);
276
+ pushChildren(fiber, null, stack);
277
+ return;
278
+ }
279
+ countComponent(tally, kind === 'unit' ? 'K' : kind, name);
280
+ if (kind !== 'unit' && !isStructural) {
281
+ pushChildren(fiber, null, stack);
282
+ return;
283
+ }
284
+ const slots = new Set<unknown>();
285
+ collectSlotProps(fiber.memoizedProps, slots, 0);
286
+ pushChildren(fiber, slots, stack);
287
+ };
288
+
289
+ /**
290
+ * Handles one popped fiber: walk-through tags and lib-unit internals are
291
+ * only traversed, host-defined fibers are skipped, and app-scope fibers are counted.
292
+ */
293
+ const visitEntry = (
294
+ entry: WalkEntry,
295
+ registries: KitRegistries,
296
+ tally: Tally,
297
+ stack: WalkEntry[],
298
+ isStructural: boolean,
299
+ ): void => {
300
+ const { fiber, unitSlots } = entry;
301
+ if (!COUNTED_FIBER_TAGS.includes(fiber.tag)) {
302
+ pushChildren(fiber, unitSlots, stack);
303
+ return;
304
+ }
305
+ if (unitSlots && !unitSlots.has(fiber.memoizedProps)) {
306
+ if (isStructural && isListCell(fiber)) collectCellSlots(fiber, unitSlots);
307
+ pushChildren(fiber, unitSlots, stack);
308
+ return;
309
+ }
310
+ if (!isAppAuthored(getAuthorFiber(fiber))) {
311
+ pushChildren(fiber, null, stack);
312
+ return;
313
+ }
314
+ tally.appAuthoredSeen = true;
315
+ countFiber(fiber, registries, tally, stack, isStructural);
316
+ };
317
+
318
+ /**
319
+ * Walks a screen's fiber tree from rootFiber (not its siblings) and tallies
320
+ * app-authored kit, RN and other components. Stops at maxFibers and marks truncated.
321
+ * Structural mode (release) treats every counted fiber as a unit and walks only app-passed slots.
322
+ */
323
+ const walkScreen = (
324
+ rootFiber: FiberLike,
325
+ registries: KitRegistries,
326
+ stopAt?: (fiber: FiberLike) => boolean,
327
+ maxFibers = MAX_FIBERS,
328
+ isStructural = !__DEV__,
329
+ ): WalkResult => {
330
+ const tally = createTally();
331
+ const stack: WalkEntry[] = [{ fiber: rootFiber, unitSlots: null }];
332
+ let fibers = 0;
333
+ let truncated = false;
334
+ for (let entry = stack.pop(); entry; entry = stack.pop()) {
335
+ if (fibers === maxFibers) {
336
+ truncated = true;
337
+ break;
338
+ }
339
+ fibers += 1;
340
+ if (entry.fiber !== rootFiber && stopAt?.(entry.fiber)) continue;
341
+ visitEntry(entry, registries, tally, stack, isStructural);
342
+ }
343
+ return buildResult(tally, fibers, truncated);
344
+ };
345
+
346
+ /** True only when the host marks this app as whitelisted for adoption tracking. */
347
+ const isAdoptionTrackingEnabled = (context: unknown): boolean => {
348
+ if (!isObjectLike(context)) return false;
349
+ const config = context.designSystemConfig;
350
+ return isObjectLike(config) && config.isWhiteList === true;
351
+ };
352
+
353
+ /** Counts as the analytics params of a screen-load event, or undefined unless the walk is ok. */
354
+ const getAdoptionTrackingParams = (
355
+ result: WalkResult | undefined,
356
+ ): AdoptionTrackingParams | undefined => {
357
+ if (result?.status !== 'ok') return undefined;
358
+ const { K, R, O } = result.counts;
359
+ return { num_design_system: K, num_rn: R, num_other: O };
360
+ };
361
+
362
+ export {
363
+ classifyFiber,
364
+ collectSlotProps,
365
+ createRegistries,
366
+ getAdoptionTrackingParams,
367
+ getAppId,
368
+ getAuthorFiber,
369
+ getCurrentFiber,
370
+ getTypeName,
371
+ isAdoptionTrackingEnabled,
372
+ isAppAuthored,
373
+ isHostDefined,
374
+ unwrapType,
375
+ walkScreen,
376
+ };
@@ -2,10 +2,6 @@ import { NativeModules } from 'react-native';
2
2
  import { LocalizationObject } from './types';
3
3
  import defaultLanguage from '../Assets/language.json';
4
4
 
5
- /**
6
- * String table for kit and feature copy. The language is read once from the
7
- * native `RNResource` module at construction; Vietnamese is the default.
8
- */
9
5
  class Localize {
10
6
  private assets: LocalizationObject;
11
7
  private readonly currentLanguage: 'en' | 'vi';
@@ -29,17 +25,14 @@ class Localize {
29
25
  return this.assets;
30
26
  }
31
27
 
32
- /** Text for `key` in the current language, or the key itself if missing. */
33
28
  translate(key: string) {
34
29
  return this.assets[this.currentLanguage]?.[key] || key;
35
30
  }
36
31
 
37
- /** Picks the current-language text of a `{ vi, en }` pair, else its JSON. */
38
32
  translateData(data: { vi: string; en: string }) {
39
33
  return data[this.currentLanguage] ?? JSON.stringify(data);
40
34
  }
41
35
 
42
- /** Merges extra translations; keys that already exist keep their text. */
43
36
  addTranslations(translations: LocalizationObject) {
44
37
  this.assets = {
45
38
  vi: { ...translations?.vi, ...this.assets.vi },
@@ -24,13 +24,10 @@ 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';
27
28
  import { useModalLayout } from './modalLayout';
28
29
  import { scheduleOnRN } from 'react-native-worklets';
29
30
 
30
- /**
31
- * Screen of the `Modal` route: a bottom sheet when pushed by
32
- * `showBottomSheet`, otherwise a centered popup.
33
- */
34
31
  const ModalScreen: React.FC<any> = props => {
35
32
  if (props.route?.params?.isBottomSheet) {
36
33
  return <BottomSheet {...props} />;
@@ -38,10 +35,6 @@ const ModalScreen: React.FC<any> = props => {
38
35
  return <Modal {...props} />;
39
36
  };
40
37
 
41
- /**
42
- * Centered popup that fades and scales in over a scrim. On foldables it is
43
- * centered in the modal placement area, at most a popup wide.
44
- */
45
38
  const Modal: React.FC<ModalParams> = props => {
46
39
  const { navigator } = useContext(ApplicationContext);
47
40
  const context = useContext<any>(MiniAppContext);
@@ -58,7 +51,9 @@ const Modal: React.FC<ModalParams> = props => {
58
51
  const isDuo = isIPhoneDuo();
59
52
  const { isFoldable, placement, popup, scrimOpacity } = useModalLayout();
60
53
 
61
- // Auto-tracks the popup as displayed on the current screen.
54
+ /**
55
+ * Fire auto_popup_displayed tracking event on mount
56
+ */
62
57
  useEffect(() => {
63
58
  navigator?.maxApi?.getDataObserver?.('current_screen', (curScreen: any) => {
64
59
  context.autoTracking({
@@ -188,7 +183,12 @@ const Modal: React.FC<ModalParams> = props => {
188
183
  modalStyle,
189
184
  ]}
190
185
  >
191
- <Component {...params} onRequestClose={onDismiss} />
186
+ <AdoptionProbe
187
+ screenName={Component?.name || Component?.type?.name || 'Invalid'}
188
+ navigation={props.navigation}
189
+ >
190
+ <Component {...params} onRequestClose={onDismiss} />
191
+ </AdoptionProbe>
192
192
  </Animated.View>
193
193
  </KeyboardAvoidingView>
194
194
  </Container>
@@ -11,10 +11,6 @@ export const WHITE_LIST = [
11
11
  'vn.momo.ekyc',
12
12
  ];
13
13
 
14
- /**
15
- * Screen-level navigation handle given to features; `setOptions` enforces the
16
- * design system's header rules before applying React Navigation options.
17
- */
18
14
  class Navigation {
19
15
  instance: NavigationProp<any>;
20
16
  context: any;
@@ -25,33 +21,39 @@ class Navigation {
25
21
  }
26
22
 
27
23
  /**
28
- * Drops header overrides the design system forbids: any `headerLeft`, and
29
- * render-function `headerRight`/`headerTitle` outside `WHITE_LIST` apps.
24
+ * navigation options filter by design system rule
25
+ * @param params
30
26
  */
31
27
  private filterParams = (params: NavigationOptions) => {
32
- const isWhiteListed = WHITE_LIST.includes(this.context?.appId);
33
- const rightType = typeof params.headerRight;
34
- const titleType = typeof params.headerTitle;
35
- const isTitleRenderer = titleType !== 'string' && titleType !== 'object';
36
-
37
28
  if (params.headerLeft) {
38
29
  console.warn('headerLeft not allow override render by design system!');
39
30
  delete params.headerLeft;
40
31
  }
41
32
 
42
- if (params.headerRight && rightType !== 'object' && !isWhiteListed) {
33
+ if (
34
+ params.headerRight &&
35
+ typeof params.headerRight !== 'object' &&
36
+ !WHITE_LIST.includes(this.context?.appId)
37
+ ) {
43
38
  console.warn('headerRight not allow type func render by design system!');
44
39
  delete params.headerRight;
45
40
  }
46
41
 
47
- if (params.headerTitle && isTitleRenderer && !isWhiteListed) {
42
+ if (
43
+ params.headerTitle &&
44
+ !['string', 'object'].includes(typeof params.headerTitle) &&
45
+ !WHITE_LIST.includes(this.context?.appId)
46
+ ) {
48
47
  console.warn('headerTitle not allow type func render by design system!');
49
48
  delete params.headerTitle;
50
49
  }
51
50
  return params;
52
51
  };
53
52
 
54
- /** Applies kit `NavigationOptions` to the screen after `filterParams`. */
53
+ /**
54
+ * navigation setOptions support props NavigationOptions base on react-navigation options
55
+ * @param params
56
+ */
55
57
  setOptions = (params: NavigationOptions) => {
56
58
  const options = getOptions(this.filterParams(params));
57
59
  this.instance.setOptions(options as StackNavigationOptions);
@@ -30,13 +30,10 @@ 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';
33
34
 
34
35
  const Stack = createStackNavigator();
35
36
 
36
- /**
37
- * Root of a mini app: safe area, reserved regions and the merged mini app
38
- * context around the kit's navigator.
39
- */
40
37
  const NavigationContainer: React.FC<NavigationContainerProps> = ({
41
38
  screen,
42
39
  theme = defaultTheme,
@@ -90,16 +87,13 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
90
87
  initialParams={initialParams}
91
88
  localize={localize}
92
89
  />
90
+ <AdoptionBadge />
93
91
  </MiniAppContext.Provider>
94
92
  </ReservedRegionsProvider>
95
93
  </SafeAreaProvider>
96
94
  );
97
95
  };
98
96
 
99
- /**
100
- * Stack navigator with its `Stack`, `Dialog` and `Modal` routes, under an
101
- * `ApplicationContext` carrying the navigator, themed fonts and `translate`.
102
- */
103
97
  const Navigation: React.FC<any> = ({
104
98
  screen,
105
99
  theme = defaultTheme,
@@ -117,10 +111,9 @@ const Navigation: React.FC<any> = ({
117
111
  const syncCanGoBack = useNativeCanGoBack();
118
112
  const isWhiteList = isWhiteListDesignSystem(context);
119
113
  const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
120
- let navigationFontFamily = 'SFProText';
121
- if (isWhiteList) {
122
- navigationFontFamily = 'MomoTrustSans-Regular';
123
- }
114
+ const navigationFontFamily = isWhiteList
115
+ ? 'MomoTrustSans-Regular'
116
+ : 'SFProText';
124
117
  let headerBackground = context?.designSystemConfig?.config?.headerBar;
125
118
  let headerGradient = theme.colors?.gradient;
126
119
 
@@ -131,9 +124,15 @@ const Navigation: React.FC<any> = ({
131
124
  headerGradient = context?.designSystemConfig?.config?.headerGradient;
132
125
  }
133
126
 
127
+ /**
128
+ * inject data for navigator
129
+ */
134
130
  navigator.current.maxApi = maxApi;
135
131
 
136
- /** Text for a localization key, or the current-language side of `{ vi, en }`. */
132
+ /**
133
+ * translate context method
134
+ * @param data
135
+ */
137
136
  const translate = (data?: string | { vi: string; en: string }) => {
138
137
  if (data && typeof data !== 'string') {
139
138
  return localize?.translateData(data);
@@ -231,12 +230,12 @@ const Navigation: React.FC<any> = ({
231
230
  backgroundColor: theme.colors.background.default,
232
231
  },
233
232
  headerLeft: (props: any) => <HeaderLeft {...props} floating />,
234
- headerBackground: (props: any) => {
235
- return <HeaderBackground {...props} />;
236
- },
237
- headerRight: (props: any) => {
238
- return <HeaderRight {...props} floating />;
239
- },
233
+ headerBackground: (props: any) => (
234
+ <HeaderBackground {...props} />
235
+ ),
236
+ headerRight: (props: any) => (
237
+ <HeaderRight {...props} floating />
238
+ ),
240
239
  headerTintColor: Colors.black_17,
241
240
  ...TransitionPresets.ModalTransition,
242
241
  }}