@momo-kits/foundation 0.169.7-beta.4 → 0.169.7-beta.6

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 (146) 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 +15 -14
  5. package/Application/BottomTab/BottomTabBar.tsx +49 -62
  6. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  7. package/Application/BottomTab/CurvedContainer.tsx +14 -12
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +13 -13
  9. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  10. package/Application/BottomTab/DuoRailTabBar.tsx +20 -24
  11. package/Application/BottomTab/DuoTabBar.tsx +8 -10
  12. package/Application/BottomTab/TabBarIcon.tsx +15 -29
  13. package/Application/BottomTab/TabScreen.tsx +1 -5
  14. package/Application/BottomTab/index.tsx +33 -30
  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 +4 -0
  19. package/Application/Components/DuoGlassBackground.tsx +5 -4
  20. package/Application/Components/HeaderAnimated.tsx +40 -12
  21. package/Application/Components/HeaderBackground.tsx +19 -2
  22. package/Application/Components/HeaderExtendHeader.tsx +56 -34
  23. package/Application/Components/HeaderLeft.tsx +4 -2
  24. package/Application/Components/HeaderRight.styles.ts +15 -9
  25. package/Application/Components/HeaderRight.tsx +2 -1
  26. package/Application/Components/HeaderTitle.shared.ts +19 -12
  27. package/Application/Components/HeaderTitle.tsx +4 -3
  28. package/Application/Components/HeaderToolkitAction.tsx +28 -22
  29. package/Application/Components/NavigationButton.tsx +10 -6
  30. package/Application/Components/SearchHeader.tsx +41 -15
  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 +19 -54
  43. package/Application/TooltipPortal.tsx +12 -5
  44. package/Application/WidgetContainer.tsx +10 -4
  45. package/Application/headerLayout.ts +158 -0
  46. package/Application/index.ts +11 -1
  47. package/Application/modalLayout.ts +13 -14
  48. package/Application/navigationOptions.tsx +27 -32
  49. package/Application/types.header.ts +7 -2
  50. package/Application/types.ts +7 -0
  51. package/Badge/Badge.tsx +5 -0
  52. package/Badge/BadgeDot.tsx +1 -0
  53. package/Badge/BadgeDotAnimation.tsx +15 -7
  54. package/Badge/BadgeRibbon.tsx +46 -38
  55. package/Badge/Shape.tsx +2 -0
  56. package/Badge/types.ts +2 -2
  57. package/Button/index.tsx +5 -16
  58. package/Button/utils.ts +9 -15
  59. package/CheckBox/index.tsx +4 -0
  60. package/Consts/theme.ts +10 -10
  61. package/Context/index.ts +5 -1
  62. package/Divider/DashDivider.tsx +1 -0
  63. package/Divider/index.tsx +4 -0
  64. package/FoundationList/index.tsx +4 -0
  65. package/Icon/index.tsx +6 -6
  66. package/Icon/types.ts +1 -1
  67. package/IconButton/index.tsx +4 -12
  68. package/Image/index.tsx +9 -7
  69. package/Input/Input.tsx +8 -9
  70. package/Input/InputDropDown.tsx +10 -12
  71. package/Input/InputMoney.tsx +12 -8
  72. package/Input/InputMoneyView.tsx +14 -7
  73. package/Input/InputOTP.tsx +35 -33
  74. package/Input/InputPhoneNumber.tsx +8 -9
  75. package/Input/InputSearch.tsx +22 -8
  76. package/Input/InputTextArea.tsx +4 -0
  77. package/Input/SearchTextTyping.tsx +4 -0
  78. package/Input/SystemTextInput.tsx +7 -2
  79. package/Input/TextTyping.tsx +4 -0
  80. package/Input/common.tsx +26 -12
  81. package/Input/index.tsx +24 -21
  82. package/Input/moneyUtils.ts +10 -4
  83. package/Input/styles.ts +0 -5
  84. package/Input/utils.ts +14 -12
  85. package/Layout/Card.tsx +7 -4
  86. package/Layout/FloatingButton.tsx +4 -0
  87. package/Layout/Item.tsx +6 -2
  88. package/Layout/ItemList.tsx +9 -7
  89. package/Layout/ItemSectionList.tsx +2 -1
  90. package/Layout/ReservedRegions.tsx +25 -25
  91. package/Layout/Screen.tsx +59 -58
  92. package/Layout/Section.tsx +8 -5
  93. package/Layout/TrackingScope.tsx +1 -0
  94. package/Layout/screenContent.tsx +4 -2
  95. package/Layout/screenFooter.tsx +28 -18
  96. package/Layout/screenHeader.tsx +88 -82
  97. package/Layout/screenQuickReturn.tsx +17 -20
  98. package/Layout/screenScroll.ts +4 -7
  99. package/Layout/screenTypes.ts +13 -4
  100. package/Layout/types.ts +2 -4
  101. package/Layout/utils.ts +31 -37
  102. package/Loader/DotLoader.tsx +4 -0
  103. package/Loader/Loader.tsx +4 -0
  104. package/Loader/ProgressBar.tsx +1 -0
  105. package/Loader/Spinner.tsx +4 -0
  106. package/Loader/utils.ts +11 -7
  107. package/Pagination/Dot.tsx +4 -0
  108. package/Pagination/PaginationDot.tsx +1 -0
  109. package/Pagination/PaginationNumber.tsx +5 -3
  110. package/Pagination/PaginationScroll.tsx +5 -0
  111. package/Pagination/PaginationWhiteDot.tsx +1 -0
  112. package/Pagination/index.tsx +4 -0
  113. package/Pagination/types.ts +2 -2
  114. package/Popup/PopupNotify.tsx +44 -48
  115. package/Popup/PopupPromotion.tsx +4 -7
  116. package/Popup/types.ts +4 -4
  117. package/Popup/utils.ts +4 -0
  118. package/Radio/index.tsx +4 -0
  119. package/Skeleton/index.tsx +8 -0
  120. package/Switch/index.tsx +1 -0
  121. package/Tag/index.tsx +5 -1
  122. package/Tag/types.ts +3 -3
  123. package/Text/index.tsx +14 -3
  124. package/Text/styles.ts +1 -1
  125. package/Text/types.ts +1 -6
  126. package/Text/utils.ts +20 -4
  127. package/Title/index.tsx +41 -32
  128. package/ViewBoundary/index.tsx +4 -2
  129. package/index.ts +0 -6
  130. package/package.json +1 -1
  131. package/Application/KitAdoption/AdoptionBadge.tsx +0 -119
  132. package/Application/KitAdoption/AdoptionProbe.tsx +0 -166
  133. package/Application/KitAdoption/AdoptionStore.ts +0 -80
  134. package/Application/KitAdoption/Authorship/utils.ts +0 -137
  135. package/Application/KitAdoption/Slots/utils.ts +0 -68
  136. package/Application/KitAdoption/constants.ts +0 -120
  137. package/Application/KitAdoption/index.ts +0 -12
  138. package/Application/KitAdoption/styles.ts +0 -55
  139. package/Application/KitAdoption/types.ts +0 -61
  140. package/Application/KitAdoption/utils.ts +0 -376
  141. package/Application/__tests__/KitAdoption.integration.test.tsx +0 -253
  142. package/Application/__tests__/KitAdoption.test.ts +0 -283
  143. package/Application/__tests__/KitAdoptionAuthorship.test.ts +0 -132
  144. package/Application/__tests__/KitAdoptionStore.test.ts +0 -110
  145. package/Application/__tests__/KitAdoptionStructural.test.ts +0 -193
  146. package/Application/__tests__/modalLayout.test.ts +0 -128
package/Input/index.tsx CHANGED
@@ -36,7 +36,7 @@ export interface InputProps extends TextInputProps {
36
36
  errorMessage?: string;
37
37
 
38
38
  /**
39
- * Optional. Represents the name or key of the icon to be displayed in the Input component.
39
+ * Optional. Trailing icon name or URL; any other string is shown as a text action.
40
40
  */
41
41
  trailing?: string;
42
42
 
@@ -69,19 +69,18 @@ export interface InputProps extends TextInputProps {
69
69
  required?: boolean;
70
70
 
71
71
  /**
72
- * Optional. If `true`,
73
- * include spacing when Input not have error message.
72
+ * Optional. If `true`, keeps an empty error row when there is no error or hint.
74
73
  * Defaults to `false` if not provided.
75
74
  */
76
75
  errorSpacing?: boolean;
77
76
 
78
77
  /**
79
- * If true, the icon of input will use be show indicator loading.
78
+ * Optional. If `true`, shows a loading spinner in place of the trailing icon.
80
79
  */
81
80
  loading?: boolean;
82
81
 
83
82
  /**
84
- * Optional. Represents the leading icon in the Input component.
83
+ * Optional. Represents the leading icon in the Input component.
85
84
  */
86
85
  leadingIcon?: string;
87
86
 
@@ -111,12 +110,13 @@ export interface InputProps extends TextInputProps {
111
110
  hintText?: string;
112
111
 
113
112
  /**
114
- * Optional. Represents the style of the Input component.
113
+ * Optional. Callback function to be called when the floating icon is pressed.
115
114
  */
116
115
  onPressFloatingIcon?: () => void;
117
116
 
118
117
  /**
119
- * Optional. Represents the style of the Input component.
118
+ * Optional. If `true`, shows a clear icon while focused with a value.
119
+ * Defaults to `true` if not provided.
120
120
  */
121
121
  showClearIcon?: boolean;
122
122
  }
@@ -137,12 +137,12 @@ export interface InputSearchProps extends TextInputProps {
137
137
 
138
138
  /**
139
139
  * Optional. If `true`, the text on the search button will be displayed.
140
- * Defaults to `false` if not provided.
140
+ * Defaults to `true` if not provided.
141
141
  */
142
142
  showButtonText?: boolean;
143
143
 
144
144
  /**
145
- * Optional. Represents the value for the floating title in the InputSearch component.
145
+ * Optional. Callback function to be called when the search button is pressed.
146
146
  */
147
147
  onPressButtonText?: () => void;
148
148
 
@@ -180,7 +180,7 @@ export interface InputSearchProps extends TextInputProps {
180
180
  onPressTrailing?: () => void;
181
181
 
182
182
  /**
183
- * Optional. If `true`, the user won't be able to interact with the Input component.
183
+ * Optional. Background color of the search field. Defaults to the surface color.
184
184
  */
185
185
  backgroundColor?: string;
186
186
 
@@ -195,8 +195,13 @@ export interface InputSearchProps extends TextInputProps {
195
195
  typingData?: string[];
196
196
 
197
197
  /**
198
- * Optional. Represents the callback function to be called when the search button is pressed.
199
- * @param e
198
+ * Optional. `header` is the 32pt field with a 20pt search icon that sits in the
199
+ * navigation header. Defaults to `default`, the 36pt field for content.
200
+ */
201
+ size?: 'default' | 'header';
202
+
203
+ /**
204
+ * Optional. Called when the field is pressed while showing `typingData`.
200
205
  */
201
206
  onPress?: (e: GestureResponderEvent) => void;
202
207
  }
@@ -259,7 +264,7 @@ export interface InputDropDownProps extends TouchableOpacityProps {
259
264
  placeholder?: string;
260
265
 
261
266
  /**
262
- * Optional. Represents the data for the dropdown.
267
+ * Optional. If `true`, the value wraps to multiple lines and the chevron aligns to the top.
263
268
  */
264
269
  multiline?: boolean;
265
270
 
@@ -303,19 +308,18 @@ export interface InputDropDownProps extends TouchableOpacityProps {
303
308
  required?: boolean;
304
309
 
305
310
  /**
306
- * Optional. If `true`,
307
- * include spacing when Input not have error message.
311
+ * Optional. If `true`, keeps an empty error row when there is no error or hint.
308
312
  * Defaults to `false` if not provided.
309
313
  */
310
314
  errorSpacing?: boolean;
311
315
 
312
316
  /**
313
- * If true, the icon of input will use be show indicator loading.
317
+ * Optional. If `true`, shows a loading spinner in place of the trailing icon.
314
318
  */
315
319
  loading?: boolean;
316
320
 
317
321
  /**
318
- * Optional. Represents the leading icon in the Input component.
322
+ * Optional. Represents the leading icon in the Input component.
319
323
  */
320
324
  leadingIcon?: string;
321
325
 
@@ -340,15 +344,14 @@ export interface InputDropDownProps extends TouchableOpacityProps {
340
344
  hintText?: string;
341
345
 
342
346
  /**
343
- * Optional. Represents the style of the Input component.
347
+ * Optional. Callback function to be called when the floating icon is pressed.
344
348
  */
345
349
  onPressFloatingIcon?: () => void;
346
350
  }
347
351
 
348
352
  export interface InputPhoneNumberProps extends InputProps {
349
353
  /**
350
- * Optional. Represents the country code to be displayed in the InputPhoneNumber component.
351
- * It should be in the format of a string, e.g., "+1" for the United States.
354
+ * Optional. Currently unused: the displayed prefix is always `+84`.
352
355
  */
353
356
  countryCode?: string;
354
357
  }
@@ -370,4 +373,4 @@ export {
370
373
  InputPhoneNumber,
371
374
  };
372
375
 
373
- export {checkValidPhoneNumber};
376
+ export { checkValidPhoneNumber };
@@ -1,15 +1,21 @@
1
1
  export const moneyFormatter = new Intl.NumberFormat('vi-VN');
2
2
 
3
+ /**
4
+ * Locates the single edited span between `old` and `text`: common prefix
5
+ * length (`start`), common suffix length (`suffix`) and removed + inserted
6
+ * char count (`size`).
7
+ */
3
8
  export const findEdit = (old: string, text: string) => {
4
9
  let start = 0;
5
10
  while (start < text.length && old[start] === text[start]) {
6
11
  start += 1;
7
12
  }
13
+ const maxSuffix = Math.min(text.length, old.length) - start;
8
14
  let suffix = 0;
9
- while (
10
- suffix < Math.min(text.length, old.length) - start &&
11
- old[old.length - 1 - suffix] === text[text.length - 1 - suffix]
12
- ) {
15
+ while (suffix < maxSuffix) {
16
+ if (old[old.length - 1 - suffix] !== text[text.length - 1 - suffix]) {
17
+ break;
18
+ }
13
19
  suffix += 1;
14
20
  }
15
21
  const size = old.length + text.length - 2 * (start + suffix);
package/Input/styles.ts CHANGED
@@ -2,7 +2,6 @@ import { Platform, StyleSheet } from 'react-native';
2
2
  import { Colors, Radius, Spacing } from '../Consts';
3
3
 
4
4
  export default StyleSheet.create({
5
- //input style
6
5
  input: {
7
6
  flex: 1,
8
7
  paddingLeft: Spacing.M,
@@ -65,7 +64,6 @@ export default StyleSheet.create({
65
64
  marginTop: Spacing.S,
66
65
  },
67
66
 
68
- //text area style
69
67
  textAreaView: {
70
68
  flex: 1,
71
69
  },
@@ -94,7 +92,6 @@ export default StyleSheet.create({
94
92
  marginBottom: Spacing.XS,
95
93
  },
96
94
 
97
- //searchInput
98
95
  searchInputWrapper: {
99
96
  flexDirection: 'row',
100
97
  borderRadius: Radius.XL,
@@ -136,7 +133,6 @@ export default StyleSheet.create({
136
133
  ios: 36,
137
134
  }),
138
135
  },
139
- //OTP
140
136
  otpWrapper: {
141
137
  width: '100%',
142
138
  marginTop: Spacing.M,
@@ -183,7 +179,6 @@ export default StyleSheet.create({
183
179
  opacity: 0.02,
184
180
  letterSpacing: Spacing.XXL,
185
181
  },
186
- //DropDown
187
182
  inputDropDownWrapper: {
188
183
  flexDirection: 'row',
189
184
  marginTop: Spacing.S,
package/Input/utils.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { Localize } from '../Application';
2
2
 
3
+ /**
4
+ * Formats a number Vietnamese-style (`.` thousands, `,` decimals) with
5
+ * `currency` appended; 0, NaN or a falsy value gives `0<currency>`.
6
+ */
3
7
  const formatNumberToMoney = (number: number, currency = '') => {
4
8
  if (!number || isNaN(number) || Number(number) === 0) {
5
9
  return `0${currency}`;
@@ -41,6 +45,10 @@ const formatNumberToMoney = (number: number, currency = '') => {
41
45
  return isNegative ? `-${result}${currency}` : `${result}${currency}`;
42
46
  };
43
47
 
48
+ /**
49
+ * Parses a formatted money string back to a number by stripping
50
+ * `currencyUnit`, separators, `đ` and spaces; unparsable text gives 0.
51
+ */
44
52
  const formatMoneyToNumber = (money: string, currencyUnit: string) => {
45
53
  if (money && money.length > 0) {
46
54
  const moneyString = money
@@ -59,26 +67,20 @@ const formatMoneyToNumber = (money: string, currencyUnit: string) => {
59
67
  return Number(money);
60
68
  };
61
69
 
62
- // --- PHONE VALIDATION ---
63
70
  function formatPhoneNumber(phone: string): string {
64
71
  return phone.replace(/\D/g, '');
65
72
  }
66
73
 
74
+ /**
75
+ * Keeps the digits of `phone` (prepending the `0` a 9-char local number lacks)
76
+ * and returns a localized error when empty or not 10-11 digits.
77
+ */
67
78
  function checkValidPhoneNumber(phone: string): {
68
79
  phoneFormatted: string;
69
80
  error?: string;
70
81
  } {
71
- let phoneNumber = phone;
72
-
73
- // if not starting with 0 and has 9 digits => prepend 0
74
- if (
75
- phoneNumber.length > 0 &&
76
- phoneNumber[0] !== '0' &&
77
- /^\d$/.test(phoneNumber[0]) &&
78
- phoneNumber.length === 9
79
- ) {
80
- phoneNumber = '0' + phoneNumber;
81
- }
82
+ const isMissingLeadingZero = phone.length === 9 && /^[1-9]/.test(phone);
83
+ const phoneNumber = isMissingLeadingZero ? `0${phone}` : phone;
82
84
 
83
85
  const phoneFormatted = formatPhoneNumber(phoneNumber);
84
86
  const localize = new Localize({ vi: {}, en: {} });
package/Layout/Card.tsx CHANGED
@@ -20,6 +20,10 @@ export interface CardProps extends ViewProps {
20
20
  useShadow?: boolean;
21
21
  }
22
22
 
23
+ /**
24
+ * Surface card across the content width minus 12pt margins, with its own grid for child
25
+ * Items; optional background image and light shadow.
26
+ */
23
27
  const Card: React.FC<CardProps> = ({
24
28
  children,
25
29
  style,
@@ -34,10 +38,9 @@ const Card: React.FC<CardProps> = ({
34
38
  const gutterSize = 8;
35
39
  const margin = 12;
36
40
  const widthSection = contentWidth - margin * 2;
37
- let sizePerSpan =
38
- (widthSection - margin * 2 - gutterSize * (numberOfColumns - 1)) /
39
- numberOfColumns -
40
- 1 / numberOfColumns;
41
+ const innerWidth = widthSection - margin * 2;
42
+ const columnsWidth = innerWidth - gutterSize * (numberOfColumns - 1);
43
+ const sizePerSpan = columnsWidth / numberOfColumns - 1 / numberOfColumns;
41
44
 
42
45
  const gridContext: GridContextProps = {
43
46
  numberOfColumns,
@@ -31,6 +31,10 @@ export interface FloatingButtonProps {
31
31
  renderComponent?: () => React.ReactNode;
32
32
  }
33
33
 
34
+ /**
35
+ * Floating action button above the screen bottom. With a label and a scroll value it
36
+ * shrinks to its icon while scrolling down and expands again on scroll up.
37
+ */
34
38
  export const FloatingButton: React.FC<FloatingButtonProps> = ({
35
39
  renderComponent,
36
40
  label = '',
package/Layout/Item.tsx CHANGED
@@ -7,16 +7,20 @@ import { SpanNumber } from './types';
7
7
 
8
8
  export interface ItemProps extends ViewProps {
9
9
  /**
10
- * Represents the width of the Card component in terms of span numbers (1-12).
10
+ * Width in grid columns (1-12). Defaults to the full row.
11
11
  */
12
12
  widthSpan?: SpanNumber;
13
13
 
14
14
  /**
15
- * Optional. Represents the height of the Card component in terms of span numbers (1-12).
15
+ * Optional. Height in grid spans (1-12). Unset keeps the content height.
16
16
  */
17
17
  heightSpan?: SpanNumber;
18
18
  }
19
19
 
20
+ /**
21
+ * Grid cell sized from the enclosing GridContext; outlined when the mini app turns on
22
+ * the baseline debug feature.
23
+ */
20
24
  const Item: React.FC<ItemProps> = ({
21
25
  widthSpan,
22
26
  heightSpan,
@@ -7,12 +7,16 @@ import { SpanNumber } from './types';
7
7
 
8
8
  export interface ItemListProps extends FlatListProps<any> {
9
9
  /**
10
- * Represents the width of the Card component in terms of span numbers (1-12).
10
+ * Width of each item in grid columns (1-12). Defaults to the full row.
11
11
  */
12
12
  widthSpan?: SpanNumber;
13
13
  data: any[];
14
14
  }
15
15
 
16
+ /**
17
+ * FlatList on the grid: `widthSpan` sets how many items share a row, and every item but
18
+ * the first of a row gets the gutter on its left.
19
+ */
16
20
  const ItemList = forwardRef(
17
21
  (
18
22
  {
@@ -30,6 +34,9 @@ const ItemList = forwardRef(
30
34
  const indexBegin = props?.data?.map(
31
35
  (_i: any, index: number) => index * numColumns,
32
36
  );
37
+ const defaultKeyExtractor = (item: any, index: number) => {
38
+ return (item?.id ?? item?.key ?? index).toString();
39
+ };
33
40
  const _renderItem = (item: any) => {
34
41
  if (indexBegin?.includes(item?.index)) {
35
42
  return <Item widthSpan={widthItem}>{renderItem?.(item)}</Item>;
@@ -46,12 +53,7 @@ const ItemList = forwardRef(
46
53
  ref={ref as React.RefObject<FlatList>}
47
54
  {...props}
48
55
  key={`ItemList-${numColumns}`}
49
- keyExtractor={
50
- // prefer caller's keyExtractor; fallback to common id/key/index
51
- (props as any)?.keyExtractor ??
52
- ((item: any, index: number) =>
53
- (item?.id ?? item?.key ?? index).toString())
54
- }
56
+ keyExtractor={(props as any)?.keyExtractor ?? defaultKeyExtractor}
55
57
  numColumns={numColumns}
56
58
  renderItem={_renderItem}
57
59
  horizontal={false}
@@ -7,11 +7,12 @@ import { SpanNumber } from './types';
7
7
 
8
8
  export interface ItemSectionListProps extends SectionListProps<any> {
9
9
  /**
10
- * Represents the width of the Card component in terms of span numbers (1-12).
10
+ * Width of each item in grid columns (1-12). Defaults to the full row.
11
11
  */
12
12
  widthSpan?: SpanNumber;
13
13
  }
14
14
 
15
+ /** SectionList whose items sit on the grid, `widthSpan` columns wide, a gutter apart. */
15
16
  const ItemSectionList: FC<ItemSectionListProps> = ({
16
17
  renderItem,
17
18
  widthSpan,
@@ -19,43 +19,43 @@ try {
19
19
  }
20
20
 
21
21
  /**
22
- * screen regions the system currently occludes, relative to the provider, which fills
23
- * the root view so they line up with window coordinates
22
+ * Screen regions the system currently occludes, relative to the provider, which fills
23
+ * the root view so they line up with window coordinates.
24
24
  */
25
25
  const ReservedRegionsContext = createContext<ReservedRegion[]>([]);
26
26
 
27
27
  /**
28
- * vertical seam splitting the display into left and right halves, e.g. iPhone Duo
29
- * half folded. `undefined` while the display is flat or the system reports none
28
+ * Vertical seam splitting the display into left and right halves, e.g. iPhone Duo
29
+ * half folded. `undefined` while the display is flat or the system reports none.
30
30
  */
31
31
  const HingeContext = createContext<ReservedRegion | undefined>(undefined);
32
32
 
33
+ /** Frame of the vertical hinge (see HingeContext), `undefined` while the display is flat. */
33
34
  const useHinge = () => useContext(HingeContext);
34
35
 
35
36
  /**
36
- * keeps the occlusion regions and the vertical division reported by the native provider
37
+ * Provides the occlusion regions and the vertical division (hinge) reported by the native
38
+ * provider.
37
39
  */
38
40
  const OcclusionRegions: React.FC<{
39
41
  useRegions: NativeReservedRegions['useReservedRegions'];
40
42
  children: ReactNode;
41
43
  }> = ({ useRegions, children }) => {
42
44
  const regions = useRegions();
43
- const occlusion = useMemo(
44
- () =>
45
- regions
46
- .filter(region => region.kind === 'occlusion')
47
- .map(region => region.frame),
48
- [regions],
49
- );
50
- const hinge = useMemo(
51
- () =>
52
- regions.find(
53
- region =>
54
- region.kind === 'division' &&
55
- region.frame.height > region.frame.width,
56
- )?.frame,
57
- [regions],
58
- );
45
+ const occlusion = useMemo(() => {
46
+ return regions
47
+ .filter(region => region.kind === 'occlusion')
48
+ .map(region => region.frame);
49
+ }, [regions]);
50
+ const hinge = useMemo(() => {
51
+ const division = regions.find(region => {
52
+ if (region.kind !== 'division') {
53
+ return false;
54
+ }
55
+ return region.frame.height > region.frame.width;
56
+ });
57
+ return division?.frame;
58
+ }, [regions]);
59
59
 
60
60
  return (
61
61
  <ReservedRegionsContext.Provider value={occlusion}>
@@ -65,8 +65,8 @@ const OcclusionRegions: React.FC<{
65
65
  };
66
66
 
67
67
  /**
68
- * tracks the system occlusion regions (iOS 27.1+), e.g. the iPhone Duo status bar
69
- * cluster growing for a call or a Live Activity
68
+ * Tracks the system occlusion regions (iOS 27.1+), e.g. the iPhone Duo status bar
69
+ * cluster growing for a call or a Live Activity.
70
70
  */
71
71
  const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
72
72
  children,
@@ -75,8 +75,8 @@ const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
75
75
  return <>{children}</>;
76
76
  }
77
77
 
78
- const { ReservedRegionsProvider: NativeProvider, useReservedRegions } =
79
- nativeLib;
78
+ const NativeProvider = nativeLib.ReservedRegionsProvider;
79
+ const { useReservedRegions } = nativeLib;
80
80
  return (
81
81
  <NativeProvider style={styles.container}>
82
82
  <OcclusionRegions useRegions={useReservedRegions}>
package/Layout/Screen.tsx CHANGED
@@ -37,6 +37,10 @@ import { useScreenQuickReturn } from './screenQuickReturn';
37
37
 
38
38
  export type { ScreenProps };
39
39
 
40
+ /**
41
+ * Screen container: sets the navigation header, lays children on the grid, and adds the
42
+ * scroll view, floating button and footer, with iPhone Duo side bar and tab bar insets.
43
+ */
40
44
  const Screen = forwardRef(
41
45
  (
42
46
  {
@@ -63,6 +67,7 @@ const Screen = forwardRef(
63
67
  headerBackground,
64
68
  gradientColor,
65
69
  trackingParams,
70
+ contentLayout,
66
71
  }: ScreenProps,
67
72
  ref: any,
68
73
  ) => {
@@ -108,14 +113,12 @@ const Screen = forwardRef(
108
113
  const isIOS = Platform.OS === 'ios';
109
114
  const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
110
115
  const hasSideBar = !!layout.sideBar;
111
- const useDuoGlass =
112
- hasSideBar &&
113
- (headerType === 'default' || headerType === 'extended') &&
114
- !animatedHeader &&
115
- !inputSearchProps;
116
+ const isPlainHeader = headerType === 'default' || headerType === 'extended';
117
+ const hasCustomHeader = !!animatedHeader || !!inputSearchProps;
118
+ const useDuoGlass = hasSideBar && isPlainHeader && !hasCustomHeader;
116
119
 
117
- const useQuickReturn =
118
- hasSideBar && scrollable && (useDuoGlass || !!animatedHeader);
120
+ const hasOverlayHeader = useDuoGlass || !!animatedHeader;
121
+ const useQuickReturn = hasSideBar && scrollable && hasOverlayHeader;
119
122
  const { quickReturnOptions, snapQuickReturnHeader } = useScreenQuickReturn(
120
123
  sharedValue,
121
124
  useQuickReturn,
@@ -123,18 +126,10 @@ const Screen = forwardRef(
123
126
  );
124
127
 
125
128
  let keyboardOffset = heightHeader - bottomInset;
126
- if (
127
- headerType === 'extended' ||
128
- animatedHeader ||
129
- inputSearchProps ||
130
- useDuoGlass
131
- ) {
129
+ if (headerType === 'extended' || hasCustomHeader || useDuoGlass) {
132
130
  keyboardOffset = -bottomInset;
133
131
  }
134
132
 
135
- /**
136
- * inject params for screen tracking
137
- */
138
133
  screen?.onSetParams?.(trackingParams);
139
134
 
140
135
  const { setSearchHeader, currentTint } = useScreenHeader({
@@ -150,11 +145,10 @@ const Screen = forwardRef(
150
145
  gradientColor,
151
146
  headerBackground,
152
147
  layoutOffset,
148
+ contentLayout,
153
149
  });
154
150
 
155
- /**
156
- * expose ref for screen
157
- */
151
+ // mutates the content node so callers keep its methods plus the header setters
158
152
  useImperativeHandle(ref, () => {
159
153
  return Object.assign(screenRef.current || {}, {
160
154
  setOptions: (params: NavigationOptions) => {
@@ -165,19 +159,18 @@ const Screen = forwardRef(
165
159
  });
166
160
  });
167
161
 
168
- const { handleScroll, handleScrollEnd, handleMomentumScrollEnd } =
169
- createScrollHandlers({
170
- scrollable,
171
- animatedValue: animatedValue.current,
172
- sharedValue,
173
- scrollViewProps,
174
- animatedHeader,
175
- inputSearchProps,
176
- currentTint,
177
- navigation,
178
- snapQuickReturnHeader,
179
- ref,
180
- });
162
+ const scrollHandlers = createScrollHandlers({
163
+ scrollable,
164
+ animatedValue: animatedValue.current,
165
+ sharedValue,
166
+ scrollViewProps,
167
+ animatedHeader,
168
+ inputSearchProps,
169
+ currentTint,
170
+ navigation,
171
+ snapQuickReturnHeader,
172
+ ref,
173
+ });
181
174
  const Component: any = scrollable ? Animated.ScrollView : View;
182
175
 
183
176
  const getHeight = () => {
@@ -207,6 +200,36 @@ const Screen = forwardRef(
207
200
  floatingButtonBottom = 12 + duoTabInset;
208
201
  }
209
202
 
203
+ const renderHeaderLayer = () => {
204
+ if (useDuoGlass) {
205
+ return (
206
+ <DuoScrollGradient
207
+ animatedValue={sharedValue}
208
+ gradientColor={gradientColor}
209
+ headerBackground={headerBackground}
210
+ heightHeader={heightHeader}
211
+ extended={headerType === 'extended'}
212
+ followScroll={useQuickReturn}
213
+ />
214
+ );
215
+ }
216
+ return (
217
+ <HeaderExtendHeader
218
+ headerType={headerType}
219
+ heightHeader={heightHeader}
220
+ headerRightWidth={headerRightWidth}
221
+ animatedValue={sharedValue}
222
+ inputSearchProps={inputSearchProps}
223
+ navigation={navigation}
224
+ inputSearchRef={inputSearchRef}
225
+ useShadowHeader={useShadowHeader}
226
+ gradientColor={gradientColor}
227
+ headerBackground={headerBackground}
228
+ contentLayout={contentLayout}
229
+ />
230
+ );
231
+ };
232
+
210
233
  return (
211
234
  <View
212
235
  style={[
@@ -217,29 +240,7 @@ const Screen = forwardRef(
217
240
  },
218
241
  ]}
219
242
  >
220
- {useDuoGlass ? (
221
- <DuoScrollGradient
222
- animatedValue={sharedValue}
223
- gradientColor={gradientColor}
224
- headerBackground={headerBackground}
225
- heightHeader={heightHeader}
226
- extended={headerType === 'extended'}
227
- followScroll={useQuickReturn}
228
- />
229
- ) : (
230
- <HeaderExtendHeader
231
- headerType={headerType}
232
- heightHeader={heightHeader}
233
- headerRightWidth={headerRightWidth}
234
- animatedValue={sharedValue}
235
- inputSearchProps={inputSearchProps}
236
- navigation={navigation}
237
- inputSearchRef={inputSearchRef}
238
- useShadowHeader={useShadowHeader}
239
- gradientColor={gradientColor}
240
- headerBackground={headerBackground}
241
- />
242
- )}
243
+ {renderHeaderLayer()}
243
244
 
244
245
  <KeyboardAvoidingView
245
246
  style={[
@@ -259,9 +260,9 @@ const Screen = forwardRef(
259
260
  {...scrollViewProps}
260
261
  ref={screenRef}
261
262
  showsVerticalScrollIndicator={false}
262
- onScroll={handleScroll}
263
- onScrollEndDrag={handleScrollEnd}
264
- onMomentumScrollEnd={handleMomentumScrollEnd}
263
+ onScroll={scrollHandlers.handleScroll}
264
+ onScrollEndDrag={scrollHandlers.handleScrollEnd}
265
+ onMomentumScrollEnd={scrollHandlers.handleMomentumScrollEnd}
265
266
  scrollEventThrottle={16}
266
267
  style={[
267
268
  Styles.flex,