@momo-kits/foundation 0.167.2-beta.1 → 0.168.1-beta.2

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 (53) hide show
  1. package/Application/BottomSheet.tsx +113 -14
  2. package/Application/BottomTab/BottomTabBar.tsx +6 -3
  3. package/Application/BottomTab/CustomBottomTabItem.tsx +3 -5
  4. package/Application/BottomTab/DuoBarCompression.tsx +135 -0
  5. package/Application/BottomTab/DuoTabBar.tsx +537 -0
  6. package/Application/BottomTab/index.tsx +145 -134
  7. package/Application/Components/DuoGlassBackground.tsx +158 -0
  8. package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
  9. package/Application/Components/HeaderAnimated.tsx +9 -3
  10. package/Application/Components/HeaderExtendHeader.tsx +70 -28
  11. package/Application/Components/HeaderLeft.tsx +38 -0
  12. package/Application/Components/HeaderRight.tsx +144 -9
  13. package/Application/Components/HeaderTitle.tsx +43 -7
  14. package/Application/Components/SearchHeader.tsx +20 -15
  15. package/Application/Components/SideBarButton.tsx +134 -0
  16. package/Application/ModalScreen.tsx +16 -2
  17. package/Application/NavigationContainer.tsx +22 -38
  18. package/Application/Navigator.ts +1 -3
  19. package/Application/StackScreen.tsx +2 -5
  20. package/Application/device.ts +29 -0
  21. package/Application/types.ts +10 -1
  22. package/Application/utils.tsx +10 -7
  23. package/Badge/Badge.tsx +2 -3
  24. package/Badge/BadgeDotAnimation.tsx +3 -3
  25. package/Button/index.tsx +11 -11
  26. package/Consts/theme.ts +3 -4
  27. package/Input/Input.tsx +3 -4
  28. package/Input/InputMoney.tsx +22 -45
  29. package/Input/InputOTP.tsx +9 -12
  30. package/Input/InputPhoneNumber.tsx +3 -4
  31. package/Input/InputSearch.tsx +17 -14
  32. package/Input/InputTextArea.tsx +3 -4
  33. package/Input/SystemTextInput.tsx +2 -3
  34. package/Input/TextTyping.tsx +11 -6
  35. package/Input/utils.ts +2 -3
  36. package/Layout/Card.tsx +8 -26
  37. package/Layout/FloatingButton.tsx +3 -3
  38. package/Layout/Item.tsx +1 -10
  39. package/Layout/ReservedRegions.tsx +78 -0
  40. package/Layout/Screen.tsx +242 -48
  41. package/Layout/Section.tsx +5 -18
  42. package/Layout/index.ts +4 -4
  43. package/Layout/styles.ts +5 -0
  44. package/Layout/utils.ts +477 -5
  45. package/Pagination/Dot.tsx +3 -6
  46. package/Popup/PopupNotify.tsx +33 -22
  47. package/Popup/PopupPromotion.tsx +21 -8
  48. package/Popup/utils.ts +32 -0
  49. package/Radio/index.tsx +2 -3
  50. package/Text/utils.ts +1 -2
  51. package/Title/index.tsx +8 -11
  52. package/package.json +5 -2
  53. package/Layout/GridSystem.tsx +0 -137
@@ -1,4 +1,3 @@
1
- /* eslint-disable react-hooks/exhaustive-deps */
2
1
  import React, {
3
2
  forwardRef,
4
3
  useContext,
@@ -9,10 +8,9 @@ import React, {
9
8
  useState,
10
9
  } from 'react';
11
10
  import {
12
- NativeSyntheticEvent,
13
11
  TextInput,
14
- TextInputFocusEventData,
15
- TextInputSelectionChangeEventData,
12
+ TextInputFocusEvent,
13
+ TextInputSelectionChangeEvent,
16
14
  TouchableOpacity,
17
15
  View,
18
16
  } from 'react-native';
@@ -44,7 +42,6 @@ const countDigits = (text: string) => text.replace(/\D/g, '').length;
44
42
  const groupDigits = (digits: string) =>
45
43
  digits.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
46
44
 
47
- // Caret position right after the `digitIndex`-th digit of `display`.
48
45
  const caretFromDigitIndex = (display: string, digitIndex: number) => {
49
46
  let count = 0;
50
47
  for (let i = 0; i < display.length && count < digitIndex; i++) {
@@ -98,9 +95,6 @@ const detectEdit = (old: string, next: string, selection: Selection): Edit => {
98
95
  };
99
96
  };
100
97
 
101
- // Applies `edit` to the digits of `display`. Separators and currency are never
102
- // deleted: removing a separator deletes the digit before it instead, removing
103
- // only the currency leaves the digits untouched.
104
98
  const applyEdit = (display: string, edit: Edit, digitLimit: number) => {
105
99
  const digits = display.replace(/\D/g, '');
106
100
  let left = countDigits(display.slice(0, edit.start));
@@ -166,11 +160,10 @@ const InputMoney = forwardRef(
166
160
  const inputFontFamily = exportFontFamily('bold');
167
161
  const [focused, setFocused] = useState(false);
168
162
  const [displayValue, setDisplayValue] = useState('');
169
- const [numericValue, setNumericValue] = useState('');
170
163
  const [commitCount, setCommitCount] = useState(0);
171
164
  const displayRef = useRef('');
165
+ const digitsRef = useRef('');
172
166
  const selectionRef = useRef<Selection>({ start: 0, end: 0 });
173
- // Caret to push to native once the committed text is rendered.
174
167
  const pendingCaretRef = useRef<number | null>(null);
175
168
  const pendingSelectionRef = useRef<Selection | null>(null);
176
169
  const editingRef = useRef(false);
@@ -184,55 +177,39 @@ const InputMoney = forwardRef(
184
177
  props.accessibilityLabel,
185
178
  );
186
179
 
187
- const digitLimit =
188
- typeof (props as any)?.maxLength === 'number'
189
- ? (props as any).maxLength
190
- : Number.POSITIVE_INFINITY;
180
+ const digitLimit = props.maxLength ?? Number.POSITIVE_INFINITY;
191
181
 
192
182
  useEffect(() => {
193
183
  if (value !== undefined && typeof value === 'string') {
194
184
  const cleanValue = value.replace(/\D/g, '').replace(/^0+(?=\d)/, '');
195
- if (cleanValue !== numericValue) {
185
+ if (cleanValue !== digitsRef.current) {
196
186
  commit(cleanValue, cleanValue.length);
197
187
  }
198
188
  }
189
+ // eslint-disable-next-line react-hooks/exhaustive-deps
199
190
  }, [value]);
200
191
 
201
- // Always set the caret imperatively: a controlled `selection` prop is only
202
- // sent to native when it differs from the last native selection, so it is
203
- // skipped whenever a stale selection event already reported the same value.
204
192
  useLayoutEffect(() => {
205
193
  const caret = pendingCaretRef.current;
206
- if (caret === null) return;
194
+ if (caret === null || !inputRef.current?.isFocused()) return;
207
195
  pendingCaretRef.current = null;
208
- if (inputRef.current?.isFocused()) {
209
- inputRef.current.setSelection(caret, caret);
210
- }
196
+ inputRef.current.setSelection(caret, caret);
211
197
  }, [commitCount]);
212
198
 
213
199
  const commit = (digits: string, caretDigit: number) => {
214
200
  const display = digits ? `${groupDigits(digits)}${currency}` : '';
215
201
  const caret = caretFromDigitIndex(display, caretDigit);
216
- const changed = digits !== numericValue;
202
+ const changed = digits !== digitsRef.current;
217
203
  displayRef.current = display;
204
+ digitsRef.current = digits;
218
205
  selectionRef.current = { start: caret, end: caret };
219
206
  pendingCaretRef.current = caret;
220
207
  setCommitCount(count => count + 1);
221
208
  setDisplayValue(display);
222
- setNumericValue(digits);
223
209
  return changed;
224
210
  };
225
211
 
226
- const parseInputText = (text: string): string => {
227
- if (!text) return '';
228
- const currencyRegex = new RegExp(` ?${currency}$`, 'g');
229
- const textWithoutCurrency = text.replace(currencyRegex, '').trim();
230
- return textWithoutCurrency.replace(/[^\d]/g, '');
231
- };
232
-
233
212
  const onClearText = () => {
234
- inputRef?.current?.clear();
235
- inputRef.current?.setNativeProps({ text: '' });
236
213
  commit('', 0);
237
214
  onChangeText?.('');
238
215
  };
@@ -258,10 +235,7 @@ const InputMoney = forwardRef(
258
235
  }
259
236
  };
260
237
 
261
- // Keeps the caret out of the currency suffix.
262
- const _onSelectionChange = (
263
- e: NativeSyntheticEvent<TextInputSelectionChangeEventData>,
264
- ) => {
238
+ const _onSelectionChange = (e: TextInputSelectionChangeEvent) => {
265
239
  props.onSelectionChange?.(e);
266
240
  if (editingRef.current) return;
267
241
  const selection = e.nativeEvent.selection;
@@ -283,23 +257,25 @@ const InputMoney = forwardRef(
283
257
  });
284
258
  };
285
259
 
286
- const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
260
+ const _onFocus = (e: TextInputFocusEvent) => {
287
261
  setFocused(true);
262
+ const caret = pendingCaretRef.current;
263
+ pendingCaretRef.current = null;
264
+ if (caret !== null && !props.selectTextOnFocus) {
265
+ selectionRef.current = { start: caret, end: caret };
266
+ inputRef.current?.setSelection(caret, caret);
267
+ }
288
268
  onFocus?.(e);
289
269
  };
290
270
 
291
- const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
271
+ const _onBlur = (e: TextInputFocusEvent) => {
292
272
  setFocused(false);
293
273
  onBlur?.(e);
294
274
  };
295
275
 
296
276
  const _setText = (text: string) => {
297
- if (!text) {
298
- commit('', 0);
299
- onChangeText?.('');
300
- return;
301
- }
302
- const digits = parseInputText(text)
277
+ const digits = text
278
+ .replace(/\D/g, '')
303
279
  .replace(/^0+(?=\d)/, '')
304
280
  .slice(0, digitLimit);
305
281
  if (commit(digits, digits.length)) {
@@ -350,6 +326,7 @@ const InputMoney = forwardRef(
350
326
  accessibilityLabel={`${componentId}`}
351
327
  textAlignVertical="center"
352
328
  ref={inputRef}
329
+ editable={props.editable !== false && !disabled}
353
330
  accessibilityState={{
354
331
  disabled: disabled,
355
332
  ...props.accessibilityState,
@@ -64,8 +64,8 @@ const OTPCaret: FC<CaretProps> = ({ index, length }) => {
64
64
  opacity: opacity.value,
65
65
  }));
66
66
 
67
- const spacingStyle = !isNaN(Number(length)) &&
68
- index !== Number(length) - 1 && { marginRight: Spacing.L };
67
+ const hasNextInput = !isNaN(Number(length)) && index !== Number(length) - 1;
68
+ const spacingStyle = hasNextInput && { marginRight: Spacing.L };
69
69
 
70
70
  return (
71
71
  <View style={[Styles.rowCenter, spacingStyle]}>
@@ -162,6 +162,7 @@ const InputOTP = forwardRef(
162
162
 
163
163
  const renderInputs = (inputLength: number) => {
164
164
  const TextInputs: React.ReactNode[] = [];
165
+ const textColors = theme.colors.text;
165
166
  for (let i = 0; i < inputLength; i++) {
166
167
  TextInputs.push(
167
168
  <View key={`otp-input-${i}`}>
@@ -170,9 +171,7 @@ const InputOTP = forwardRef(
170
171
  ) : (
171
172
  <Text
172
173
  style={i !== inputLength - 1 && { marginRight: Spacing.L }}
173
- color={
174
- value[i] ? theme.colors.text.default : theme.colors.text.hint
175
- }
174
+ color={value[i] ? textColors.default : textColors.hint}
176
175
  typography={value[i] ? 'header_m_bold' : 'body_default_regular'}
177
176
  >
178
177
  {value[i] || '-'}
@@ -201,16 +200,14 @@ const InputOTP = forwardRef(
201
200
  return (
202
201
  <View style={Styles.row}>
203
202
  {splitValue.map((letter, index) => {
203
+ const isLast = index === value.length - 1;
204
+ const isTyping = focused && value.length !== MAX_LENGTH;
205
+ const isCompact = fontScale > 1 && value.length > 6;
206
+ const marginRight = isCompact ? Spacing.M : Spacing.L;
204
207
  return (
205
208
  <Text
206
209
  key={`otp-letter-${index}`}
207
- style={
208
- (index !== value.length - 1 ||
209
- (focused && value.length !== MAX_LENGTH)) && {
210
- marginRight:
211
- fontScale > 1 && value.length > 6 ? Spacing.M : Spacing.L,
212
- }
213
- }
210
+ style={(!isLast || isTyping) && { marginRight }}
214
211
  typography={'header_m_bold'}
215
212
  >
216
213
  {letter}
@@ -6,10 +6,9 @@ import React, {
6
6
  useState,
7
7
  } from 'react';
8
8
  import {
9
- NativeSyntheticEvent,
10
9
  StyleSheet,
11
10
  TextInput,
12
- TextInputFocusEventData,
11
+ TextInputFocusEvent,
13
12
  TouchableOpacity,
14
13
  View,
15
14
  } from 'react-native';
@@ -93,12 +92,12 @@ const InputPhoneNumber = forwardRef(
93
92
  onChangeText?.(text);
94
93
  };
95
94
 
96
- const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
95
+ const _onFocus = (e: TextInputFocusEvent) => {
97
96
  setFocused(true);
98
97
  onFocus?.(e);
99
98
  };
100
99
 
101
- const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
100
+ const _onBlur = (e: TextInputFocusEvent) => {
102
101
  setFocused(false);
103
102
  onBlur?.(e);
104
103
  };
@@ -10,9 +10,8 @@ import React, {
10
10
  useState,
11
11
  } from 'react';
12
12
  import {
13
- NativeSyntheticEvent,
14
13
  TextInput,
15
- TextInputFocusEventData,
14
+ TextInputFocusEvent,
16
15
  TouchableOpacity,
17
16
  View,
18
17
  } from 'react-native';
@@ -59,9 +58,11 @@ const TextTyping: FC<any> = ({
59
58
  listChar.length !== 0 && showChar(listChar?.[currentCharIndex.current]);
60
59
  };
61
60
 
62
- currentIndex.current >= data?.length
63
- ? handleResetList()
64
- : handleAnimation();
61
+ if (currentIndex.current >= data?.length) {
62
+ handleResetList();
63
+ } else {
64
+ handleAnimation();
65
+ }
65
66
  };
66
67
 
67
68
  const showChar = (char?: string) => {
@@ -82,9 +83,12 @@ const TextTyping: FC<any> = ({
82
83
  }, timeDelayNextChar);
83
84
  };
84
85
 
85
- currentCharIndex.current >= (data?.[currentIndex.current]?.length || 0)
86
- ? handleResetString()
87
- : handleShowChar();
86
+ const textLength = data?.[currentIndex.current]?.length || 0;
87
+ if (currentCharIndex.current >= textLength) {
88
+ handleResetString();
89
+ } else {
90
+ handleShowChar();
91
+ }
88
92
  };
89
93
 
90
94
  const reset = () => {
@@ -166,12 +170,12 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
166
170
  props.accessibilityLabel,
167
171
  );
168
172
 
169
- const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
173
+ const _onFocus = (e: TextInputFocusEvent) => {
170
174
  setFocused(true);
171
175
  onFocus?.(e);
172
176
  };
173
177
 
174
- const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
178
+ const _onBlur = (e: TextInputFocusEvent) => {
175
179
  setFocused(false);
176
180
  onBlur?.(e);
177
181
  };
@@ -303,6 +307,8 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
303
307
  inputState = 'filled';
304
308
  }
305
309
 
310
+ const inputBackground = backgroundColor ?? theme.colors.background.surface;
311
+
306
312
  return (
307
313
  <ComponentContext.Provider
308
314
  value={{
@@ -322,10 +328,7 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
322
328
  <View
323
329
  style={[
324
330
  styles.searchInputWrapper,
325
- {
326
- backgroundColor:
327
- backgroundColor ?? theme.colors.background.surface,
328
- },
331
+ { backgroundColor: inputBackground },
329
332
  ]}
330
333
  >
331
334
  <Icon
@@ -7,9 +7,8 @@ import React, {
7
7
  useState,
8
8
  } from 'react';
9
9
  import {
10
- NativeSyntheticEvent,
11
10
  TextInput,
12
- TextInputFocusEventData,
11
+ TextInputFocusEvent,
13
12
  TouchableOpacity,
14
13
  View,
15
14
  } from 'react-native';
@@ -79,12 +78,12 @@ const InputTextArea = forwardRef(
79
78
  setInputValue(text);
80
79
  };
81
80
 
82
- const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
81
+ const _onFocus = (e: TextInputFocusEvent) => {
83
82
  setFocused(true);
84
83
  onFocus?.(e);
85
84
  };
86
85
 
87
- const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
86
+ const _onBlur = (e: TextInputFocusEvent) => {
88
87
  setFocused(false);
89
88
  onBlur?.(e);
90
89
  };
@@ -17,11 +17,10 @@ const SystemTextInput: FC<SystemTextInputProps> = props => {
17
17
  const needsAndroidWrapper =
18
18
  Platform.OS === 'android' && !editable && (onPressIn || onPressOut);
19
19
 
20
+ const noPressHandlers = { onPressIn: undefined, onPressOut: undefined };
20
21
  const inputProps: TextInputProps = {
21
22
  ...props,
22
- ...(needsAndroidWrapper
23
- ? { onPressIn: undefined, onPressOut: undefined }
24
- : null),
23
+ ...(needsAndroidWrapper ? noPressHandlers : null),
25
24
  ...(editable === false ? { accessibilityState: undefined } : null),
26
25
  };
27
26
 
@@ -36,9 +36,11 @@ const TextTyping: FC<TextTypingProps> = ({
36
36
  listChar.length !== 0 && showChar(listChar?.[currentCharIndex.current]);
37
37
  };
38
38
 
39
- currentIndex.current >= data?.length
40
- ? handleResetList()
41
- : handleAnimation();
39
+ if (currentIndex.current >= data?.length) {
40
+ handleResetList();
41
+ } else {
42
+ handleAnimation();
43
+ }
42
44
  };
43
45
 
44
46
  const showChar = (char?: string) => {
@@ -59,9 +61,12 @@ const TextTyping: FC<TextTypingProps> = ({
59
61
  }, timeDelayNextChar);
60
62
  };
61
63
 
62
- currentCharIndex.current >= (data?.[currentIndex.current]?.length || 0)
63
- ? handleResetString()
64
- : handleShowChar();
64
+ const textLength = data?.[currentIndex.current]?.length || 0;
65
+ if (currentCharIndex.current >= textLength) {
66
+ handleResetString();
67
+ } else {
68
+ handleShowChar();
69
+ }
65
70
  };
66
71
 
67
72
  const reset = () => {
package/Input/utils.ts CHANGED
@@ -15,9 +15,8 @@ const formatNumberToMoney = (number: number, currency = '') => {
15
15
 
16
16
  const numberString = Math.abs(number).toString();
17
17
  if (numberString.length < 3) {
18
- return isNegative
19
- ? `-${numberString}${currency}`
20
- : `${numberString}${currency}`;
18
+ const sign = isNegative ? '-' : '';
19
+ return `${sign}${numberString}${currency}`;
21
20
  }
22
21
 
23
22
  let count = 0;
package/Layout/Card.tsx CHANGED
@@ -1,12 +1,12 @@
1
1
  import React, { useContext } from 'react';
2
- import { Dimensions, View, ViewProps } from 'react-native';
3
- import { useGridSystem } from './utils';
2
+ import { View, ViewProps } from 'react-native';
3
+ import { useGridSystem, useScreenLayout } from './utils';
4
4
  import { GridContextProps } from './types';
5
- import { GridContext, GridSystem } from './index';
5
+ import { GridContext } from './index';
6
6
  import { Image } from '../Image';
7
- import { ApplicationContext, MiniAppContext } from '../Context';
7
+ import { ApplicationContext } from '../Context';
8
8
  import styles from './styles';
9
- import { Colors, Radius, Shadow, Spacing } from '../Consts';
9
+ import { Radius, Shadow, Spacing } from '../Consts';
10
10
 
11
11
  export interface CardProps extends ViewProps {
12
12
  /**
@@ -27,12 +27,13 @@ const Card: React.FC<CardProps> = ({
27
27
  useShadow = false,
28
28
  ...props
29
29
  }) => {
30
- const { showGrid, theme } = useContext(ApplicationContext);
30
+ const { theme } = useContext(ApplicationContext);
31
31
  const { numberOfColumns } = useGridSystem();
32
+ const { contentWidth } = useScreenLayout();
32
33
 
33
34
  const gutterSize = 8;
34
35
  const margin = 12;
35
- const widthSection = Dimensions.get('window').width - margin * 2;
36
+ const widthSection = contentWidth - margin * 2;
36
37
  let sizePerSpan =
37
38
  (widthSection - margin * 2 - gutterSize * (numberOfColumns - 1)) /
38
39
  numberOfColumns -
@@ -47,23 +48,6 @@ const Card: React.FC<CardProps> = ({
47
48
  },
48
49
  };
49
50
 
50
- /**
51
- * render overlay only dev mode
52
- */
53
- const renderOverlay = () => {
54
- return (
55
- <>
56
- <GridSystem
57
- width={widthSection}
58
- gutterSize={Spacing.S}
59
- screenGrid={false}
60
- gridColor={Colors.red_01}
61
- />
62
- <View pointerEvents={'none'} style={styles.boxGridCard} />
63
- </>
64
- );
65
- };
66
-
67
51
  return (
68
52
  <GridContext.Provider value={gridContext}>
69
53
  <View
@@ -95,9 +79,7 @@ const Card: React.FC<CardProps> = ({
95
79
  ]}
96
80
  />
97
81
  )}
98
- {/* Render only allowed children as valid React elements */}
99
82
  {children}
100
- {showGrid && renderOverlay()}
101
83
  </View>
102
84
  </GridContext.Provider>
103
85
  );
@@ -10,8 +10,8 @@ import Animated, {
10
10
  useAnimatedStyle,
11
11
  useSharedValue,
12
12
  withTiming,
13
- runOnJS,
14
13
  } from 'react-native-reanimated';
14
+ import { scheduleOnRN } from 'react-native-worklets';
15
15
  import { ApplicationContext } from '../Context';
16
16
  import { Icon } from '../Icon';
17
17
  import { useScaleSize, exportFontFamily } from '../Text';
@@ -78,7 +78,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
78
78
  minWidth,
79
79
  { duration: 100 },
80
80
  finished => {
81
- if (finished) runOnJS(setShowText)(false);
81
+ if (finished) scheduleOnRN(setShowText, false);
82
82
  },
83
83
  );
84
84
  } else {
@@ -87,7 +87,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
87
87
  maxWidth.value,
88
88
  { duration: 100 },
89
89
  finished => {
90
- if (finished) runOnJS(setShowText)(true);
90
+ if (finished) scheduleOnRN(setShowText, true);
91
91
  },
92
92
  );
93
93
  }
package/Layout/Item.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { useContext } from 'react';
2
2
  import { View, ViewProps } from 'react-native';
3
3
  import { GridContext } from './index';
4
- import { ApplicationContext, MiniAppContext } from '../Context';
4
+ import { MiniAppContext } from '../Context';
5
5
  import styles from './styles';
6
6
  import { SpanNumber } from './types';
7
7
 
@@ -23,19 +23,11 @@ const Item: React.FC<ItemProps> = ({
23
23
  children,
24
24
  style,
25
25
  }) => {
26
- const { showGrid } = useContext(ApplicationContext);
27
26
  const context = useContext<any>(MiniAppContext);
28
27
  const grid = useContext(GridContext);
29
28
 
30
29
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
31
30
 
32
- /**
33
- * render overlay view only dev mode
34
- */
35
- const renderOverlay = () => {
36
- return <View pointerEvents={'none'} style={styles.boxGrid} />;
37
- };
38
-
39
31
  return (
40
32
  <View
41
33
  style={[
@@ -49,7 +41,6 @@ const Item: React.FC<ItemProps> = ({
49
41
  ]}
50
42
  >
51
43
  {children}
52
- {showGrid && renderOverlay()}
53
44
  </View>
54
45
  );
55
46
  };
@@ -0,0 +1,78 @@
1
+ import React, { createContext, ReactNode, useMemo } from 'react';
2
+ import { Platform, StyleSheet } from 'react-native';
3
+
4
+ export type ReservedRegion = {
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ };
10
+
11
+ type NativeReservedRegions = typeof import('react-native-reserved-regions');
12
+
13
+ // native lib is optional so apps without react-native-reserved-regions still bundle
14
+ let nativeLib: NativeReservedRegions | undefined;
15
+ try {
16
+ nativeLib = require('react-native-reserved-regions');
17
+ } catch {
18
+ nativeLib = undefined;
19
+ }
20
+
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
24
+ */
25
+ const ReservedRegionsContext = createContext<ReservedRegion[]>([]);
26
+
27
+ /**
28
+ * keeps the occlusion regions reported by the native provider, the only kind layout reads
29
+ */
30
+ const OcclusionRegions: React.FC<{
31
+ useRegions: NativeReservedRegions['useReservedRegions'];
32
+ children: ReactNode;
33
+ }> = ({ useRegions, children }) => {
34
+ const regions = useRegions();
35
+ const occlusion = useMemo(
36
+ () =>
37
+ regions
38
+ .filter(region => region.kind === 'occlusion')
39
+ .map(region => region.frame),
40
+ [regions],
41
+ );
42
+
43
+ return (
44
+ <ReservedRegionsContext.Provider value={occlusion}>
45
+ {children}
46
+ </ReservedRegionsContext.Provider>
47
+ );
48
+ };
49
+
50
+ /**
51
+ * tracks the system occlusion regions (iOS 27.1+), e.g. the iPhone Duo status bar
52
+ * cluster growing for a call or a Live Activity
53
+ */
54
+ const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
55
+ children,
56
+ }) => {
57
+ if (Platform.OS !== 'ios' || !nativeLib) {
58
+ return <>{children}</>;
59
+ }
60
+
61
+ const { ReservedRegionsProvider: NativeProvider, useReservedRegions } =
62
+ nativeLib;
63
+ return (
64
+ <NativeProvider style={styles.container}>
65
+ <OcclusionRegions useRegions={useReservedRegions}>
66
+ {children}
67
+ </OcclusionRegions>
68
+ </NativeProvider>
69
+ );
70
+ };
71
+
72
+ const styles = StyleSheet.create({
73
+ container: {
74
+ flex: 1,
75
+ },
76
+ });
77
+
78
+ export { ReservedRegionsContext, ReservedRegionsProvider };