@momo-kits/foundation 0.167.1-duo.4 → 0.167.1-duo.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.
@@ -18,7 +18,6 @@ import { Icon } from '../../Icon';
18
18
  import { DuoMetrics, useScreenLayout } from '../../Layout/utils';
19
19
  import { Text } from '../../Text';
20
20
  import { GlassView } from '../Components/OptionalGlassView';
21
- import { useDuoBarCompression } from './DuoBarCompression';
22
21
  import { FloatingButtonProps } from '../types';
23
22
  import { TabBarIcon } from './TabBarIcon';
24
23
 
@@ -147,121 +146,76 @@ const QrIcon: React.FC<{ icon: FloatingButtonProps['icon']; size: number }> = ({
147
146
  return <>{icon}</>;
148
147
  };
149
148
 
150
- type TabItem = ReturnType<typeof getTabItems>[number];
151
-
152
149
  /**
153
- * glass column of tab items with the sliding selection bubble
154
- */
155
- const Rail: React.FC<{
156
- items: TabItem[];
157
- index: number;
158
- onItemPress?: () => void;
159
- }> = ({ items, index, onItemPress }) => {
160
- const { theme } = useContext(ApplicationContext);
161
- const bubbleOffset = useSharedValue(index * (ITEM_SIZE + RAIL_GAP));
162
-
163
- useEffect(() => {
164
- bubbleOffset.value = withSpring(index * (ITEM_SIZE + RAIL_GAP), SPRING);
165
- }, [index, bubbleOffset]);
166
-
167
- const bubbleStyle = useAnimatedStyle(() => ({
168
- transform: [{ translateY: bubbleOffset.value }],
169
- }));
170
-
171
- return (
172
- <View style={styles.rail} accessibilityLabel={'bottom_tab_bar'}>
173
- <BarMaterial radius={Radius.XL} />
174
- <Animated.View
175
- pointerEvents={'none'}
176
- style={[
177
- styles.railBubble,
178
- { backgroundColor: theme.colors.background.selected },
179
- bubbleStyle,
180
- ]}
181
- />
182
- {items.map(item => (
183
- <TouchableOpacity
184
- key={item.route.key}
185
- accessibilityRole={'tab'}
186
- accessibilityLabel={item.label}
187
- accessibilityState={{ selected: item.focused }}
188
- onPress={() => {
189
- item.onPress();
190
- onItemPress?.();
191
- }}
192
- onLongPress={item.onLongPress}
193
- style={styles.railItem}
194
- >
195
- {item.icon}
196
- </TouchableOpacity>
197
- ))}
198
- </View>
199
- );
200
- };
201
-
202
- /**
203
- * vertical bar anchored to the bottom of the Duo side bar, QR button below it (Figma MT1-C/E).
204
- * When the side bar is short, it can shrink to one button showing the current tab
205
- * (HIG "Tab bar compressed"); tapping it expands the rail and compresses the toolbar
150
+ * vertical bar anchored to the bottom of the Duo side bar, QR button below it (Figma MT1-C/E)
206
151
  */
207
152
  const DuoRailTabBar: React.FC<Props> = props => {
208
153
  const { theme } = useContext(ApplicationContext);
209
154
  const { sideBar } = useScreenLayout();
210
- const { tabBarCompressed, expandTabBar, onTabSelected } =
211
- useDuoBarCompression();
212
155
  const { state, floatingButton } = props;
213
156
  const items = getTabItems(props);
214
- const current = items[state.index];
157
+ const bubbleOffset = useSharedValue(state.index * (ITEM_SIZE + RAIL_GAP));
215
158
 
216
- const columnStyle = sideBar && [
217
- styles.railColumn,
218
- {
219
- [sideBar.edge]: 0,
220
- width: sideBar.width,
221
- bottom: sideBar.bottom,
222
- },
223
- sideBar.edge === 'right'
224
- ? { alignItems: 'flex-start' as const, paddingLeft: Spacing.M }
225
- : { alignItems: 'flex-end' as const, paddingRight: Spacing.M },
226
- ];
159
+ useEffect(() => {
160
+ bubbleOffset.value = withSpring(
161
+ state.index * (ITEM_SIZE + RAIL_GAP),
162
+ SPRING,
163
+ );
164
+ }, [state.index, bubbleOffset]);
227
165
 
228
- const qr = floatingButton && (
229
- <TouchableOpacity
230
- accessibilityRole={'button'}
231
- accessibilityLabel={floatingButton.label}
232
- onPress={floatingButton.onPress}
233
- style={[styles.railQr, { backgroundColor: theme.colors.primary }]}
234
- >
235
- <QrIcon icon={floatingButton.icon} size={QR_ICON_SIZE} />
236
- </TouchableOpacity>
237
- );
166
+ const bubbleStyle = useAnimatedStyle(() => ({
167
+ transform: [{ translateY: bubbleOffset.value }],
168
+ }));
238
169
 
239
170
  if (!sideBar) {
240
171
  return null;
241
172
  }
242
173
 
243
- if (!tabBarCompressed) {
244
- return (
245
- <View pointerEvents={'box-none'} style={columnStyle}>
246
- <Rail items={items} index={state.index} onItemPress={onTabSelected} />
247
- {qr}
248
- </View>
249
- );
250
- }
251
-
252
174
  return (
253
- <View pointerEvents={'box-none'} style={columnStyle}>
254
- <TouchableOpacity
255
- accessibilityRole={'button'}
256
- accessibilityLabel={current?.label}
257
- accessibilityHint={'bottom_tab_bar'}
258
- onPress={expandTabBar}
259
- style={styles.railCompact}
260
- >
175
+ <View
176
+ pointerEvents={'box-none'}
177
+ style={[
178
+ styles.railColumn,
179
+ { [sideBar.edge]: 0, width: sideBar.width },
180
+ sideBar.edge === 'right'
181
+ ? { alignItems: 'flex-start', paddingLeft: Spacing.M }
182
+ : { alignItems: 'flex-end', paddingRight: Spacing.M },
183
+ ]}
184
+ >
185
+ <View style={styles.rail} accessibilityLabel={'bottom_tab_bar'}>
261
186
  <BarMaterial radius={Radius.XL} />
262
- {current?.icon}
263
- </TouchableOpacity>
264
- {qr}
187
+ <Animated.View
188
+ pointerEvents={'none'}
189
+ style={[
190
+ styles.railBubble,
191
+ { backgroundColor: theme.colors.background.selected },
192
+ bubbleStyle,
193
+ ]}
194
+ />
195
+ {items.map(item => (
196
+ <TouchableOpacity
197
+ key={item.route.key}
198
+ accessibilityRole={'tab'}
199
+ accessibilityLabel={item.label}
200
+ accessibilityState={{ selected: item.focused }}
201
+ onPress={item.onPress}
202
+ onLongPress={item.onLongPress}
203
+ style={styles.railItem}
204
+ >
205
+ {item.icon}
206
+ </TouchableOpacity>
207
+ ))}
208
+ </View>
209
+ {floatingButton && (
210
+ <TouchableOpacity
211
+ accessibilityRole={'button'}
212
+ accessibilityLabel={floatingButton.label}
213
+ onPress={floatingButton.onPress}
214
+ style={[styles.railQr, { backgroundColor: theme.colors.primary }]}
215
+ >
216
+ <QrIcon icon={floatingButton.icon} size={QR_ICON_SIZE} />
217
+ </TouchableOpacity>
218
+ )}
265
219
  </View>
266
220
  );
267
221
  };
@@ -393,15 +347,9 @@ const styles = StyleSheet.create({
393
347
  },
394
348
  railColumn: {
395
349
  position: 'absolute',
350
+ bottom: Spacing.XL,
396
351
  gap: Spacing.M,
397
352
  },
398
- railCompact: {
399
- width: ITEM_SIZE,
400
- height: ITEM_SIZE,
401
- borderRadius: ITEM_SIZE / 2,
402
- alignItems: 'center',
403
- justifyContent: 'center',
404
- },
405
353
  rail: {
406
354
  width: ITEM_SIZE,
407
355
  paddingVertical: RAIL_PADDING,
@@ -28,7 +28,6 @@ import { BottomTabProps } from '../types';
28
28
  import { getOptions, getStackOptions, useAppState } from '../utils';
29
29
  import BottomTabBar from './BottomTabBar';
30
30
  import { DuoTabBar } from './DuoTabBar';
31
- import { DuoBarCompressionProvider } from './DuoBarCompression';
32
31
  import { ApplicationContext, MiniAppContext } from '../../Context';
33
32
  import { useScreenLayout } from '../../Layout';
34
33
 
@@ -206,7 +205,6 @@ const BottomTab: React.FC<BottomTabProps> = ({
206
205
  navigation,
207
206
  initialRouteName,
208
207
  floatingButton,
209
- compressionBehavior,
210
208
  }) => {
211
209
  const { theme, navigator } = useContext(ApplicationContext);
212
210
  const { isIPhoneDuo } = useScreenLayout();
@@ -283,156 +281,147 @@ const BottomTab: React.FC<BottomTabProps> = ({
283
281
  };
284
282
 
285
283
  return (
286
- <DuoBarCompressionProvider
287
- behavior={compressionBehavior}
288
- tabCount={tabs.length}
289
- hasQr={!!floatingButton}
284
+ <Tab.Navigator
285
+ screenOptions={{
286
+ headerShown: false,
287
+ headerTransparent: true,
288
+ headerBackground: undefined,
289
+ tabBarLabelPosition: 'below-icon',
290
+ tabBarBackground: () => (
291
+ <View
292
+ style={[
293
+ styles.container,
294
+ {
295
+ backgroundColor: theme.colors.background.surface,
296
+ },
297
+ ]}
298
+ >
299
+ <Animated.View style={[styles.indicatorContainer, indicatorStyle]}>
300
+ <View
301
+ style={[
302
+ styles.indicator,
303
+ { backgroundColor: theme.colors.primary },
304
+ ]}
305
+ />
306
+ </Animated.View>
307
+ </View>
308
+ ),
309
+ }}
310
+ tabBar={props =>
311
+ isIPhoneDuo ? (
312
+ <DuoTabBar
313
+ {...props}
314
+ floatingButton={floatingButton}
315
+ inactiveTintColor={theme.colors.text.default}
316
+ activeTintColor={theme.colors.primary}
317
+ />
318
+ ) : (
319
+ <BottomTabBar
320
+ {...props}
321
+ floatingButton={floatingButton}
322
+ inactiveTintColor={theme.colors.text.hint}
323
+ activeTintColor={theme.colors.primary}
324
+ style={[
325
+ {
326
+ height:
327
+ 64 +
328
+ (Platform.OS === 'ios'
329
+ ? Math.min(insets.bottom, 21)
330
+ : insets.bottom),
331
+ },
332
+ ]}
333
+ />
334
+ )
335
+ }
336
+ initialRouteName={initialRouteName}
290
337
  >
291
- <Tab.Navigator
292
- screenOptions={{
293
- headerShown: false,
294
- headerTransparent: true,
295
- headerBackground: undefined,
296
- tabBarLabelPosition: 'below-icon',
297
- tabBarBackground: () => (
298
- <View
299
- style={[
300
- styles.container,
301
- {
302
- backgroundColor: theme.colors.background.surface,
303
- },
304
- ]}
305
- >
306
- <Animated.View
307
- style={[styles.indicatorContainer, indicatorStyle]}
308
- >
309
- <View
310
- style={[
311
- styles.indicator,
312
- { backgroundColor: theme.colors.primary },
313
- ]}
314
- />
315
- </Animated.View>
316
- </View>
317
- ),
318
- }}
319
- tabBar={props =>
320
- isIPhoneDuo ? (
321
- <DuoTabBar
322
- {...props}
323
- floatingButton={floatingButton}
324
- inactiveTintColor={theme.colors.text.default}
325
- activeTintColor={theme.colors.primary}
326
- />
327
- ) : (
328
- <BottomTabBar
329
- {...props}
330
- floatingButton={floatingButton}
331
- inactiveTintColor={theme.colors.text.hint}
332
- activeTintColor={theme.colors.primary}
333
- style={[
334
- {
335
- height:
336
- 64 +
337
- (Platform.OS === 'ios'
338
- ? Math.min(insets.bottom, 21)
339
- : insets.bottom),
340
- },
341
- ]}
342
- />
343
- )
338
+ {tabs.map((item, index) => {
339
+ const isNum = !isNaN(parseInt(`${item?.badgeLabel}`, 10));
340
+ const isDot = typeof item?.badgeLabel === 'string' && !item?.badgeLabel;
341
+ const isNumZero = isNum && Number(item.badgeLabel) === 0;
342
+
343
+ let badgeLabel = item?.badgeLabel;
344
+ if (isNumZero) {
345
+ badgeLabel = undefined;
344
346
  }
345
- initialRouteName={initialRouteName}
346
- >
347
- {tabs.map((item, index) => {
348
- const isNum = !isNaN(parseInt(`${item?.badgeLabel}`, 10));
349
- const isDot =
350
- typeof item?.badgeLabel === 'string' && !item?.badgeLabel;
351
- const isNumZero = isNum && Number(item.badgeLabel) === 0;
352
347
 
353
- let badgeLabel = item?.badgeLabel;
354
- if (isNumZero) {
355
- badgeLabel = undefined;
356
- }
348
+ let badgeStyle: {
349
+ marginRight: number;
350
+ top: number;
351
+ backgroundColor: string;
352
+ width?: number;
353
+ } = {
354
+ marginRight: -8,
355
+ top: -4,
356
+ backgroundColor: Colors.orange_03,
357
+ };
357
358
 
358
- let badgeStyle: {
359
- marginRight: number;
360
- top: number;
361
- backgroundColor: string;
362
- width?: number;
363
- } = {
364
- marginRight: -8,
359
+ if (isNum) {
360
+ badgeStyle = {
361
+ marginRight: -4,
365
362
  top: -4,
363
+ backgroundColor: Colors.red_03,
364
+ };
365
+ }
366
+ if (isDot) {
367
+ badgeStyle = {
368
+ marginRight: -2,
369
+ top: -2,
366
370
  backgroundColor: Colors.orange_03,
371
+ width: 12,
367
372
  };
373
+ }
368
374
 
369
- if (isNum) {
370
- badgeStyle = {
371
- marginRight: -4,
372
- top: -4,
373
- backgroundColor: Colors.red_03,
374
- };
375
- }
376
- if (isDot) {
377
- badgeStyle = {
378
- marginRight: -2,
379
- top: -2,
380
- backgroundColor: Colors.orange_03,
381
- width: 12,
382
- };
383
- }
384
-
385
- return (
386
- <Tab.Screen
387
- key={`BottomTab-${item.name}`}
388
- name={item.name}
389
- component={TabScreen}
390
- initialParams={{ ...item, nested, index }}
391
- listeners={handler}
392
- options={{
393
- tabBarLabel: ({ focused, color }) => {
394
- return (
395
- <Text
396
- numberOfLines={1}
397
- typography={
398
- focused ? 'label_xs_medium' : 'description_xs_regular'
399
- }
400
- color={color}
401
- >
402
- {item.label}
403
- </Text>
404
- );
375
+ return (
376
+ <Tab.Screen
377
+ key={`BottomTab-${item.name}`}
378
+ name={item.name}
379
+ component={TabScreen}
380
+ initialParams={{ ...item, nested, index }}
381
+ listeners={handler}
382
+ options={{
383
+ tabBarLabel: ({ focused, color }) => {
384
+ return (
385
+ <Text
386
+ numberOfLines={1}
387
+ typography={
388
+ focused ? 'label_xs_medium' : 'description_xs_regular'
389
+ }
390
+ color={color}
391
+ >
392
+ {item.label}
393
+ </Text>
394
+ );
395
+ },
396
+ title: item.label,
397
+ tabBarStyle: [
398
+ {
399
+ height: 64,
405
400
  },
406
- title: item.label,
407
- tabBarStyle: [
408
- {
409
- height: 64,
410
- },
411
- styles.tabStyle,
412
- ],
413
- tabBarBadge: badgeLabel,
414
- tabBarBadgeStyle: [
415
- {
416
- borderColor: 'white',
417
- borderWidth: 2,
418
- fontSize: 10,
419
- lineHeight: 14,
420
- fontWeight: 'bold',
421
- alignSelf: 'center',
422
- fontFamily: exportFontFamily('bold'),
423
- },
424
- badgeStyle,
425
- ],
426
- tabBarIcon: ({ color }) => (
427
- <Icon source={item.icon} color={color} size={28} />
428
- ),
429
- ...item,
430
- }}
431
- />
432
- );
433
- })}
434
- </Tab.Navigator>
435
- </DuoBarCompressionProvider>
401
+ styles.tabStyle,
402
+ ],
403
+ tabBarBadge: badgeLabel,
404
+ tabBarBadgeStyle: [
405
+ {
406
+ borderColor: 'white',
407
+ borderWidth: 2,
408
+ fontSize: 10,
409
+ lineHeight: 14,
410
+ fontWeight: 'bold',
411
+ alignSelf: 'center',
412
+ fontFamily: exportFontFamily('bold'),
413
+ },
414
+ badgeStyle,
415
+ ],
416
+ tabBarIcon: ({ color }) => (
417
+ <Icon source={item.icon} color={color} size={28} />
418
+ ),
419
+ ...item,
420
+ }}
421
+ />
422
+ );
423
+ })}
424
+ </Tab.Navigator>
436
425
  );
437
426
  };
438
427
 
@@ -82,8 +82,8 @@ const DuoGlassBackground: React.FC<{
82
82
  };
83
83
 
84
84
  /**
85
- * pink header gradient drawn under the content, fading out on scroll as main's extended
86
- * header does: header-high for default headers, 52 taller (collapsing) for extended
85
+ * pink header gradient drawn under the content: header-high for default headers
86
+ * (scrolls away), 52 taller for extended ones (collapses and fades, as off Duo)
87
87
  */
88
88
  const DuoScrollGradient: React.FC<{
89
89
  animatedValue?: AnimatedCompatValue;
@@ -91,15 +91,12 @@ const DuoScrollGradient: React.FC<{
91
91
  headerBackground?: string;
92
92
  heightHeader: number;
93
93
  extended?: boolean;
94
- // quick return header: the gradient leaves with the content instead of fading
95
- followScroll?: boolean;
96
94
  }> = ({
97
95
  animatedValue,
98
96
  gradientColor,
99
97
  headerBackground,
100
98
  heightHeader,
101
99
  extended,
102
- followScroll,
103
100
  }) => {
104
101
  const { theme } = useContext(ApplicationContext);
105
102
  const sharedValue = useNormalizedSharedValue(animatedValue);
@@ -107,21 +104,19 @@ const DuoScrollGradient: React.FC<{
107
104
  const image = headerBackground ?? theme.assets?.headerBackground;
108
105
 
109
106
  const scrollStyle = useAnimatedStyle(() => {
110
- if (followScroll) {
107
+ if (!extended) {
111
108
  return {
112
- height: heightHeader + (extended ? EXTENDED_GRADIENT_EXTRA : 0),
109
+ height: heightHeader,
113
110
  transform: [{ translateY: -Math.max(sharedValue.value, 0) }],
114
111
  };
115
112
  }
116
113
  return {
117
- height: extended
118
- ? interpolate(
119
- sharedValue.value,
120
- [0, EXTENDED_COLLAPSE_DISTANCE],
121
- [heightHeader + EXTENDED_GRADIENT_EXTRA, heightHeader],
122
- Extrapolation.CLAMP,
123
- )
124
- : heightHeader,
114
+ height: interpolate(
115
+ sharedValue.value,
116
+ [0, EXTENDED_COLLAPSE_DISTANCE],
117
+ [heightHeader + EXTENDED_GRADIENT_EXTRA, heightHeader],
118
+ Extrapolation.CLAMP,
119
+ ),
125
120
  opacity: interpolate(
126
121
  sharedValue.value,
127
122
  [0, EXTENDED_GRADIENT_EXTRA],
@@ -140,18 +135,21 @@ const DuoScrollGradient: React.FC<{
140
135
  pointerEvents={'none'}
141
136
  style={[styles.gradient, scrollStyle]}
142
137
  >
143
- <LinearGradient
144
- colors={[color, color + '00']}
145
- style={StyleSheet.absoluteFill}
146
- >
147
- {!!image && (
148
- <Image
149
- style={styles.gradientFill}
150
- source={{ uri: image }}
151
- loading={false}
152
- />
153
- )}
154
- </LinearGradient>
138
+ {/* extended squeezes the whole fade into its box like main; default clips it */}
139
+ <View style={extended ? StyleSheet.absoluteFill : styles.gradientFill}>
140
+ <LinearGradient
141
+ colors={[color, color + '00']}
142
+ style={StyleSheet.absoluteFill}
143
+ >
144
+ {!!image && (
145
+ <Image
146
+ style={styles.gradientFill}
147
+ source={{ uri: image }}
148
+ loading={false}
149
+ />
150
+ )}
151
+ </LinearGradient>
152
+ </View>
155
153
  </Animated.View>
156
154
  );
157
155
  };
@@ -168,7 +166,7 @@ const styles = StyleSheet.create({
168
166
  right: 0,
169
167
  overflow: 'hidden',
170
168
  },
171
- // banner image keeps its 375:154 shape, the gradient box clips it
169
+ // default header gradient keeps the off-Duo 375:154 shape, the header clips it
172
170
  gradientFill: {
173
171
  width: '100%',
174
172
  position: 'absolute',
@@ -5,9 +5,7 @@ import { ApplicationContext, MiniAppContext } from '../../Context';
5
5
  import { PopupNotify } from '../../Popup';
6
6
  import { NavigationButton } from './NavigationButton';
7
7
  import { SideBarButton } from './SideBarButton';
8
- import Animated from 'react-native-reanimated';
9
8
  import { useSideBarItemStyle } from '../../Layout/utils';
10
- import { useSideBarCounterStyle } from './DuoQuickReturnHeader';
11
9
 
12
10
  /**
13
11
  * default header left used for nav
@@ -22,7 +20,6 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
22
20
  const context = useContext<any>(MiniAppContext);
23
21
  const { navigator } = useContext(ApplicationContext);
24
22
  const sideBarStyle = useSideBarItemStyle('left', 0);
25
- const counterStyle = useSideBarCounterStyle();
26
23
 
27
24
  const goBack = useCallback(() => {
28
25
  const canGoBack = navigator?.ref?.current?.canGoBack?.();
@@ -81,17 +78,14 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
81
78
 
82
79
  if (floating && sideBarStyle) {
83
80
  return (
84
- <Animated.View
85
- style={[sideBarStyle, counterStyle]}
86
- pointerEvents={'box-none'}
87
- >
81
+ <View style={sideBarStyle} pointerEvents={'box-none'}>
88
82
  <SideBarButton
89
83
  accessibilityLabel="btn_navigation_back"
90
84
  icon="arrow-back"
91
85
  tintColor={tintColor}
92
86
  onPress={goBackSafe}
93
87
  />
94
- </Animated.View>
88
+ </View>
95
89
  );
96
90
  }
97
91
 
@@ -13,12 +13,8 @@ import { PopupNotify } from '../../Popup';
13
13
  import { Tool, ToolGroup } from '../types';
14
14
  import { Icon } from '../../Icon';
15
15
  import { Text } from '../../Text';
16
- import Animated from 'react-native-reanimated';
17
16
  import { useSideBarItemStyle } from '../../Layout/utils';
18
- import { useSideBarCounterStyle } from './DuoQuickReturnHeader';
19
17
  import { SideBarButton, SideBarGroup, SideBarGroupItem } from './SideBarButton';
20
- import { useDuoBarCompression } from '../BottomTab/DuoBarCompression';
21
- import { DuoMetrics } from '../../Layout/utils';
22
18
 
23
19
  const DID_SYNC_NEW_HOME = 'did_sync_new_home';
24
20
 
@@ -30,7 +26,6 @@ const HeaderRight: React.FC<any> = props => {
30
26
  const { headerRight = {}, floating = false } = props;
31
27
  const { useOnBoarding = false, buttonOnBoarding, onPress } = headerRight;
32
28
  const sideBarStyle = useSideBarItemStyle('right', 1);
33
- const counterStyle = useSideBarCounterStyle();
34
29
 
35
30
  if (typeof props.headerRight === 'function') {
36
31
  return props.headerRight(props);
@@ -48,16 +43,13 @@ const HeaderRight: React.FC<any> = props => {
48
43
 
49
44
  if (floating && sideBarStyle) {
50
45
  return (
51
- <Animated.View
52
- style={[sideBarStyle, counterStyle]}
53
- pointerEvents={'box-none'}
54
- >
46
+ <View style={sideBarStyle} pointerEvents={'box-none'}>
55
47
  <HeaderToolkitAction
56
48
  {...props.headerRight}
57
49
  tintColor={props.tintColor}
58
50
  vertical
59
51
  />
60
- </Animated.View>
52
+ </View>
61
53
  );
62
54
  }
63
55
 
@@ -83,8 +75,6 @@ const HeaderToolkitAction: React.FC<any> = ({
83
75
  }) => {
84
76
  const { navigator } = useContext(ApplicationContext);
85
77
  const context = useContext<any>(MiniAppContext);
86
- const { toolbarCompressed, toolbarHidden, expandToolbar, setToolbarHeight } =
87
- useDuoBarCompression();
88
78
 
89
79
  const [isFavorite, setIsFavorite] = useState<boolean>(false);
90
80
  const [isLoading, setIsLoading] = useState(false);
@@ -291,41 +281,6 @@ const HeaderToolkitAction: React.FC<any> = ({
291
281
  ? '16_navigation_close_circle'
292
282
  : '16_basic_home';
293
283
 
294
- const groupCount = useHelpCenter ? 2 : 1;
295
- // Back slot, the optional shortcut, then the help/home capsule
296
- const fullToolbarHeight =
297
- DuoMetrics.controlSize +
298
- Spacing.M +
299
- (useShortcut ? DuoMetrics.controlSize + Spacing.M : 0) +
300
- (groupCount > 1
301
- ? groupCount * DuoMetrics.itemPitch
302
- : DuoMetrics.controlSize);
303
-
304
- useEffect(() => {
305
- if (vertical) {
306
- setToolbarHeight?.(fullToolbarHeight);
307
- }
308
- }, [vertical, fullToolbarHeight, setToolbarHeight]);
309
-
310
- // an open tab rail takes this slot for a moment
311
- if (vertical && toolbarHidden) {
312
- return null;
313
- }
314
-
315
- // HIG "Toolbar compressed": tools fold into one button; tapping it expands the
316
- // toolbar and compresses the tab bar instead. Not the useMore icon, to tell them apart
317
- if (vertical && toolbarCompressed) {
318
- return (
319
- <SideBarButton
320
- accessibilityLabel={'btn_navigation_toolbar'}
321
- icon={'navigation_menu'}
322
- tintColor={tintColor}
323
- showBadge={showBadge}
324
- onPress={expandToolbar}
325
- />
326
- );
327
- }
328
-
329
284
  if (vertical) {
330
285
  return (
331
286
  <View style={styles.sideBarColumn}>
@@ -31,7 +31,6 @@ import { HeaderTitle } from './Components/HeaderTitle';
31
31
  import { HeaderBackground } from './Components/HeaderBackground';
32
32
  import { type EdgeInsets } from 'react-native-safe-area-context';
33
33
  import { useScreenLayout } from '../Layout';
34
- import { ReservedRegionsProvider } from '../Layout/ReservedRegions';
35
34
 
36
35
  const Stack = createStackNavigator();
37
36
 
@@ -77,19 +76,17 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
77
76
  frame: { x: 0, y: 0, width: 0, height: 0 },
78
77
  }}
79
78
  >
80
- <ReservedRegionsProvider>
81
- <MiniAppContext.Provider value={mergedContext}>
82
- <Navigation
83
- screen={screen}
84
- theme={theme}
85
- options={options}
86
- maxApi={maxApi}
87
- setCurrentContext={setCurrentContext}
88
- initialParams={initialParams}
89
- localize={localize}
90
- />
91
- </MiniAppContext.Provider>
92
- </ReservedRegionsProvider>
79
+ <MiniAppContext.Provider value={mergedContext}>
80
+ <Navigation
81
+ screen={screen}
82
+ theme={theme}
83
+ options={options}
84
+ maxApi={maxApi}
85
+ setCurrentContext={setCurrentContext}
86
+ initialParams={initialParams}
87
+ localize={localize}
88
+ />
89
+ </MiniAppContext.Provider>
93
90
  </SafeAreaProvider>
94
91
  );
95
92
  };
@@ -333,11 +333,6 @@ export type BottomTabProps = {
333
333
  blur?: (e: EventArg<'blur', false, unknown>) => void;
334
334
  };
335
335
  floatingButton?: FloatingButtonProps;
336
- /**
337
- * iPhone Duo: when the side bar can't hold both bars, which one starts compressed;
338
- * tapping the compressed one swaps them. Defaults to 'toolbar'
339
- */
340
- compressionBehavior?: 'toolbar' | 'tabBar';
341
336
  };
342
337
 
343
338
  export type FloatingButtonProps = {
package/Layout/Screen.tsx CHANGED
@@ -9,7 +9,6 @@ import React, {
9
9
  useContext,
10
10
  useEffect,
11
11
  useImperativeHandle,
12
- useMemo,
13
12
  useRef,
14
13
  } from 'react';
15
14
  import {
@@ -25,11 +24,7 @@ import {
25
24
  View,
26
25
  ViewProps,
27
26
  } from 'react-native';
28
- import {
29
- useAnimatedReaction,
30
- useSharedValue,
31
- withTiming,
32
- } from 'react-native-reanimated';
27
+ import { useSharedValue, withTiming } from 'react-native-reanimated';
33
28
  import {
34
29
  useSafeAreaFrame,
35
30
  useSafeAreaInsets,
@@ -61,7 +56,6 @@ import {
61
56
  DuoGlassBackground,
62
57
  DuoScrollGradient,
63
58
  } from '../Application/Components/DuoGlassBackground';
64
- import { DuoQuickReturnHeader } from '../Application/Components/DuoQuickReturnHeader';
65
59
  import { useDuoTabBarInset } from '../Application/BottomTab/DuoTabBar';
66
60
 
67
61
  export interface ScreenProps extends ViewProps {
@@ -274,41 +268,6 @@ const Screen = forwardRef(
274
268
  !animatedHeader &&
275
269
  !inputSearchProps;
276
270
 
277
- // iPhone Duo: the header leaves with the content and returns on a reverse scroll
278
- const useQuickReturn =
279
- hasSideBar && scrollable && (useDuoGlass || !!animatedHeader);
280
- const headerHide = useSharedValue(0);
281
- const lastScrollY = useSharedValue(0);
282
-
283
- useAnimatedReaction(
284
- () => sharedValue.value,
285
- y => {
286
- const delta = y - lastScrollY.value;
287
- lastScrollY.value = y;
288
- if (!useQuickReturn || y <= 0) {
289
- headerHide.value = 0;
290
- return;
291
- }
292
- headerHide.value = Math.min(
293
- Math.max(headerHide.value + delta, 0),
294
- heightHeader,
295
- );
296
- },
297
- [useQuickReturn, heightHeader],
298
- );
299
-
300
- const quickReturnOptions: StackNavigationOptions = useMemo(
301
- () =>
302
- useQuickReturn
303
- ? {
304
- header: (props: any) => (
305
- <DuoQuickReturnHeader {...props} offset={headerHide} />
306
- ),
307
- }
308
- : { header: undefined },
309
- [useQuickReturn, headerHide],
310
- );
311
-
312
271
  let keyboardOffset = heightHeader - bottomInset;
313
272
  if (
314
273
  headerType === 'extended' ||
@@ -342,7 +301,6 @@ const Screen = forwardRef(
342
301
 
343
302
  case 'extended':
344
303
  options = {
345
- ...quickReturnOptions,
346
304
  headerShown: true,
347
305
  headerTransparent: true,
348
306
  headerBackground: () =>
@@ -373,7 +331,6 @@ const Screen = forwardRef(
373
331
  default:
374
332
  if (hasSideBar && !inputSearchProps) {
375
333
  options = {
376
- ...quickReturnOptions,
377
334
  headerTransparent: true,
378
335
  headerTintColor: Colors.black_17,
379
336
  headerShown: true,
@@ -418,7 +375,7 @@ const Screen = forwardRef(
418
375
  }
419
376
  }
420
377
 
421
- navigation?.instance?.setOptions({ header: undefined, ...options });
378
+ navigation?.instance?.setOptions(options);
422
379
  },
423
380
  [
424
381
  gradientColor,
@@ -428,7 +385,6 @@ const Screen = forwardRef(
428
385
  sharedValue,
429
386
  useShadowHeader,
430
387
  hasSideBar,
431
- quickReturnOptions,
432
388
  ],
433
389
  );
434
390
 
@@ -444,7 +400,6 @@ const Screen = forwardRef(
444
400
  }
445
401
 
446
402
  options = {
447
- ...quickReturnOptions,
448
403
  headerTintColor: currentTint.current ?? Colors.black_17,
449
404
  headerTransparent: true,
450
405
  headerBackground: (props: any) =>
@@ -487,7 +442,6 @@ const Screen = forwardRef(
487
442
  hasSideBar,
488
443
  layoutOffset,
489
444
  heightHeader,
490
- quickReturnOptions,
491
445
  ],
492
446
  );
493
447
 
@@ -511,7 +465,7 @@ const Screen = forwardRef(
511
465
  ),
512
466
  };
513
467
 
514
- navigation?.instance?.setOptions({ header: undefined, ...options });
468
+ navigation?.instance?.setOptions(options);
515
469
  }, [navigation?.instance, sharedValue]);
516
470
 
517
471
  /**
@@ -639,36 +593,8 @@ const Screen = forwardRef(
639
593
  * handle scroll end
640
594
  * @param e
641
595
  */
642
- /**
643
- * a half-shown quick return header settles fully in or out
644
- */
645
- const snapQuickReturnHeader = (offsetY: number) => {
646
- const hide = headerHide.value;
647
- // above heightHeader the header still sits on the content
648
- if (
649
- !useQuickReturn ||
650
- offsetY < heightHeader ||
651
- hide <= 0 ||
652
- hide >= heightHeader
653
- ) {
654
- return;
655
- }
656
- headerHide.value = withTiming(
657
- hide > heightHeader / 2 ? heightHeader : 0,
658
- { duration: 200 },
659
- );
660
- };
661
-
662
- const handleMomentumScrollEnd = (
663
- e: NativeSyntheticEvent<NativeScrollEvent>,
664
- ) => {
665
- snapQuickReturnHeader(e.nativeEvent.contentOffset.y);
666
- scrollViewProps?.onMomentumScrollEnd?.(e);
667
- };
668
-
669
596
  const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
670
597
  const offsetY = e.nativeEvent.contentOffset.y;
671
- snapQuickReturnHeader(offsetY);
672
598
  if (inputSearchProps && offsetY < 100 && offsetY > 0) {
673
599
  Animated.timing(animatedValue.current, {
674
600
  toValue: 0,
@@ -796,7 +722,6 @@ const Screen = forwardRef(
796
722
  headerBackground={headerBackground}
797
723
  heightHeader={heightHeader}
798
724
  extended={headerType === 'extended'}
799
- followScroll={useQuickReturn}
800
725
  />
801
726
  ) : (
802
727
  <HeaderExtendHeader
@@ -834,7 +759,6 @@ const Screen = forwardRef(
834
759
  showsVerticalScrollIndicator={false}
835
760
  onScroll={handleScroll}
836
761
  onScrollEndDrag={handleScrollEnd}
837
- onMomentumScrollEnd={handleMomentumScrollEnd}
838
762
  scrollEventThrottle={16}
839
763
  style={[
840
764
  Styles.flex,
package/Layout/utils.ts CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  SafeAreaInsetsContext,
6
6
  } from 'react-native-safe-area-context';
7
7
  import { Spacing } from '../Consts';
8
- import { ReservedRegion, ReservedRegionsContext } from './ReservedRegions';
9
8
 
10
9
  /**
11
10
  * validate children type
@@ -71,12 +70,6 @@ const HeaderMetrics = {
71
70
  * - firstItemTop: with the status bar in the strip, the first control sits under the
72
71
  * camera, clock and connectivity cluster (cluster ends near y 151 on the outer display,
73
72
  * y 98 on the inner display in landscape)
74
- * - outerHeaderHeight / outerCameraCenterY: on the outer display in portrait the header
75
- * is 4pt taller than iPhoneMinHeight and its title lines up with the camera punchout
76
- * (y 24–66 at the top of the strip)
77
- * - edgeInset: on the outer display in landscape the strip holds no status bar, so its
78
- * controls start this far from the top and stay this far above the bottom, unless a
79
- * reserved region (the camera at either end) asks for more
80
73
  */
81
74
  const DuoMetrics = {
82
75
  maxAspectRatio: 1.6,
@@ -85,40 +78,10 @@ const DuoMetrics = {
85
78
  itemPitch: 50,
86
79
  firstItemTop: 170,
87
80
  firstItemTopLandscape: 120,
88
- outerHeaderHeight: 76,
89
- outerCameraCenterY: 45,
90
- edgeInset: 24,
91
81
  };
92
82
 
93
83
  const NO_INSETS: EdgeInsets = { top: 0, right: 0, bottom: 0, left: 0 };
94
84
 
95
- /**
96
- * how far system regions reach into a side strip from its top and from its bottom end
97
- * (e.g. the camera, which sits at either end depending on the rotation)
98
- */
99
- const getStripOcclusion = (
100
- regions: ReservedRegion[],
101
- stripLeft: number,
102
- stripRight: number,
103
- windowHeight: number,
104
- ) =>
105
- regions.reduce(
106
- (occlusion, region) => {
107
- const overlapsStrip =
108
- region.x < stripRight && region.x + region.width > stripLeft;
109
- if (!overlapsStrip) {
110
- return occlusion;
111
- }
112
- if (region.y + region.height / 2 < windowHeight / 2) {
113
- occlusion.top = Math.max(occlusion.top, region.y + region.height);
114
- } else {
115
- occlusion.bottom = Math.max(occlusion.bottom, windowHeight - region.y);
116
- }
117
- return occlusion;
118
- },
119
- { top: 0, bottom: 0 },
120
- );
121
-
122
85
  const isIPhone = () =>
123
86
  Platform.OS === 'ios' && Platform.constants.interfaceIdiom === 'phone';
124
87
 
@@ -157,11 +120,6 @@ export type SideBarLayout = {
157
120
  * Window y of the first side bar item, below the navigation bar and the side status bar.
158
121
  */
159
122
  top: number;
160
-
161
- /**
162
- * Room to keep free above the window bottom, clear of a camera at the bottom end.
163
- */
164
- bottom: number;
165
123
  };
166
124
 
167
125
  export type ScreenLayout = {
@@ -243,9 +201,8 @@ const isIPhoneDuoLayout = (
243
201
  const useScreenLayout = (): ScreenLayout => {
244
202
  const { width, height } = useWindowDimensions();
245
203
  const insets = useContext(SafeAreaInsetsContext) ?? NO_INSETS;
246
- const reservedRegions = useContext(ReservedRegionsContext);
247
204
 
248
- let headerLayout = getHeaderLayout(insets.top);
205
+ const headerLayout = getHeaderLayout(insets.top);
249
206
  const isIPhoneDuo = isIPhoneDuoLayout(width, height, insets);
250
207
  if (!isIPhoneDuo) {
251
208
  return {
@@ -267,42 +224,17 @@ const useScreenLayout = (): ScreenLayout => {
267
224
  let top = insets.top + HeaderMetrics.barHeight + Spacing.M;
268
225
  if (insets.top < HeaderMetrics.statusBarMinInset) {
269
226
  // status bar lives in the strip: start under its cluster, as the native bar does
270
- const isLandscape = width > height;
271
- if (!isLandscape) {
272
- top = DuoMetrics.firstItemTop;
273
- } else if (height >= DuoMetrics.innerMinHeight) {
274
- top = DuoMetrics.firstItemTopLandscape;
275
- } else {
276
- // outer display in landscape: no status bar in the strip
277
- top = DuoMetrics.edgeInset;
278
- }
279
-
280
- if (height > width && width < DuoMetrics.innerMinHeight) {
281
- // outer display: the row starts below the top so the title centers on the camera
282
- headerLayout = {
283
- headerHeight: DuoMetrics.outerHeaderHeight,
284
- headerStatusBarHeight:
285
- DuoMetrics.outerCameraCenterY * 2 - DuoMetrics.outerHeaderHeight,
286
- };
287
- }
227
+ const isInnerLandscape =
228
+ width > height && height >= DuoMetrics.innerMinHeight;
229
+ top = isInnerLandscape
230
+ ? DuoMetrics.firstItemTopLandscape
231
+ : DuoMetrics.firstItemTop;
288
232
  }
289
233
 
290
- const edge = insets.right >= insets.left ? 'right' : 'left';
291
- const stripLeft = edge === 'right' ? width - sideInset : 0;
292
- // keep controls clear of the camera and of the status bar cluster, which grows for
293
- // calls and Live Activities. Region frames already include the system's margin
294
- const occlusion = getStripOcclusion(
295
- reservedRegions,
296
- stripLeft,
297
- stripLeft + sideInset,
298
- height,
299
- );
300
-
301
234
  sideBar = {
302
- edge,
235
+ edge: insets.right >= insets.left ? 'right' : 'left',
303
236
  width: sideInset,
304
- top: Math.max(top, occlusion.top),
305
- bottom: Math.max(DuoMetrics.edgeInset, occlusion.bottom),
237
+ top,
306
238
  };
307
239
  }
308
240
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.167.1-duo.4",
3
+ "version": "0.167.1-duo.5",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},
@@ -1,135 +0,0 @@
1
- import React, {
2
- createContext,
3
- ReactNode,
4
- useContext,
5
- useMemo,
6
- useState,
7
- } from 'react';
8
- import { Spacing } from '../../Consts';
9
- import { useScreenLayout } from '../../Layout';
10
- import { DuoMetrics } from '../../Layout/utils';
11
-
12
- export type DuoBarCompressionBehavior = 'toolbar' | 'tabBar';
13
-
14
- type DuoBarCompression = {
15
- toolbarCompressed: boolean;
16
- tabBarCompressed: boolean;
17
- // the open rail needs the toolbar button slot too: only Back stays
18
- toolbarHidden?: boolean;
19
- // tapping the compressed bar expands it and compresses the other one
20
- expandToolbar?: () => void;
21
- expandTabBar?: () => void;
22
- // a rail opened from the compressed state closes once a tab is picked
23
- onTabSelected?: () => void;
24
- // header buttons report their full height so the tab bar knows what is left
25
- setToolbarHeight?: (height: number) => void;
26
- };
27
-
28
- const RAIL_PADDING = 6;
29
- const RAIL_GAP = Spacing.XXS;
30
- // space kept between the top and the bottom groups (HIG Vertical controls)
31
- const GROUP_GAP = Spacing.M;
32
- // Back, then the right toolkit: help + home capsule
33
- const DEFAULT_TOOLBAR_HEIGHT =
34
- DuoMetrics.controlSize + Spacing.M + 2 * DuoMetrics.itemPitch;
35
- // Back, then the single button the toolbar folds into
36
- const COMPRESSED_TOOLBAR_HEIGHT = 2 * DuoMetrics.controlSize + Spacing.M;
37
- // Back alone, while a rail opened from the compressed state takes the toolbar slot
38
- const BACK_ONLY_HEIGHT = DuoMetrics.controlSize;
39
-
40
- const NO_COMPRESSION: DuoBarCompression = {
41
- toolbarCompressed: false,
42
- tabBarCompressed: false,
43
- };
44
-
45
- const DuoBarCompressionContext =
46
- createContext<DuoBarCompression>(NO_COMPRESSION);
47
-
48
- /**
49
- * height of the vertical tab rail, plus the QR button under it
50
- */
51
- const getRailHeight = (tabCount: number, hasQr: boolean) =>
52
- 2 * RAIL_PADDING +
53
- tabCount * DuoMetrics.controlSize +
54
- Math.max(tabCount - 1, 0) * RAIL_GAP +
55
- (hasQr ? Spacing.M + DuoMetrics.controlSize : 0);
56
-
57
- /**
58
- * keeps the side bar's toolbar and tab bar clear of each other (HIG, Designing for
59
- * iPhone Duo: Vertical controls):
60
- * - both fit: both stay whole
61
- * - one fits whole beside the other compressed: one is compressed, tapping it swaps them
62
- * - otherwise both rest compressed; tapping one opens it for a moment (the rail closes
63
- * again once a tab is picked)
64
- */
65
- const DuoBarCompressionProvider: React.FC<{
66
- behavior?: DuoBarCompressionBehavior;
67
- tabCount: number;
68
- hasQr: boolean;
69
- children: ReactNode;
70
- }> = ({ behavior = 'toolbar', tabCount, hasQr, children }) => {
71
- const { height, sideBar } = useScreenLayout();
72
- const [toolbarHeight, setToolbarHeight] = useState(DEFAULT_TOOLBAR_HEIGHT);
73
- // 'toolbar' compression keeps the tab bar whole until the toolbar is tapped
74
- const [expanded, setExpanded] = useState<'toolbar' | 'tabBar'>(
75
- behavior === 'toolbar' ? 'tabBar' : 'toolbar',
76
- );
77
- // bar opened for a moment while both rest compressed
78
- const [opened, setOpened] = useState<'toolbar' | 'tabBar'>();
79
-
80
- const value = useMemo(() => {
81
- if (!sideBar) {
82
- return NO_COMPRESSION;
83
- }
84
- const room = height - sideBar.bottom - sideBar.top - GROUP_GAP;
85
- const tabBar = getRailHeight(tabCount, hasQr);
86
- const compressedTabBar = getRailHeight(1, hasQr) - 2 * RAIL_PADDING;
87
- const fits = (toolbar: number, tabs: number) => toolbar + tabs <= room;
88
-
89
- if (fits(toolbarHeight, tabBar)) {
90
- return { ...NO_COMPRESSION, setToolbarHeight };
91
- }
92
- const canSwap =
93
- fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar) &&
94
- fits(toolbarHeight, compressedTabBar);
95
- if (canSwap) {
96
- return {
97
- toolbarCompressed: expanded === 'tabBar',
98
- tabBarCompressed: expanded === 'toolbar',
99
- expandToolbar: () => setExpanded('toolbar'),
100
- expandTabBar: () => setExpanded('tabBar'),
101
- setToolbarHeight,
102
- };
103
- }
104
-
105
- const open = {
106
- expandToolbar: () => setOpened('toolbar'),
107
- expandTabBar: () => setOpened('tabBar'),
108
- setToolbarHeight,
109
- };
110
- if (opened === 'tabBar' && fits(BACK_ONLY_HEIGHT, tabBar)) {
111
- return {
112
- ...open,
113
- toolbarCompressed: true,
114
- toolbarHidden: true,
115
- tabBarCompressed: false,
116
- onTabSelected: () => setOpened(undefined),
117
- };
118
- }
119
- return {
120
- ...open,
121
- toolbarCompressed: opened !== 'toolbar',
122
- tabBarCompressed: true,
123
- };
124
- }, [expanded, opened, hasQr, height, sideBar, tabCount, toolbarHeight]);
125
-
126
- return (
127
- <DuoBarCompressionContext.Provider value={value}>
128
- {children}
129
- </DuoBarCompressionContext.Provider>
130
- );
131
- };
132
-
133
- const useDuoBarCompression = () => useContext(DuoBarCompressionContext);
134
-
135
- export { DuoBarCompressionProvider, useDuoBarCompression };
@@ -1,42 +0,0 @@
1
- import React, { createContext, useContext } from 'react';
2
- import { StackHeaderProps, Header } from '@react-navigation/stack';
3
- import Animated, {
4
- SharedValue,
5
- useAnimatedStyle,
6
- } from 'react-native-reanimated';
7
-
8
- // how far the header is pushed above the top edge
9
- const DuoHeaderOffsetContext = createContext<SharedValue<number> | undefined>(
10
- undefined,
11
- );
12
-
13
- /**
14
- * iPhone Duo header that leaves with the content and comes back when scrolling back
15
- */
16
- const DuoQuickReturnHeader: React.FC<
17
- StackHeaderProps & { offset: SharedValue<number> }
18
- > = ({ offset, ...props }) => {
19
- const style = useAnimatedStyle(() => ({
20
- transform: [{ translateY: -offset.value }],
21
- }));
22
-
23
- return (
24
- <DuoHeaderOffsetContext.Provider value={offset}>
25
- <Animated.View pointerEvents={'box-none'} style={style}>
26
- <Header {...props} />
27
- </Animated.View>
28
- </DuoHeaderOffsetContext.Provider>
29
- );
30
- };
31
-
32
- /**
33
- * side bar controls render inside the header; this cancels its offset so they stay put
34
- */
35
- const useSideBarCounterStyle = () => {
36
- const offset = useContext(DuoHeaderOffsetContext);
37
- return useAnimatedStyle(() => ({
38
- transform: [{ translateY: offset ? offset.value : 0 }],
39
- }));
40
- };
41
-
42
- export { DuoQuickReturnHeader, useSideBarCounterStyle };
@@ -1,47 +0,0 @@
1
- import React, { createContext, ReactNode, useState } 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
- // native probe is optional so apps without @momo-kits/reserved-regions still bundle
12
- let ReservedRegionsView: React.ComponentType<any> | undefined;
13
- try {
14
- ReservedRegionsView =
15
- require('@momo-kits/reserved-regions').ReservedRegionsView;
16
- } catch {
17
- ReservedRegionsView = undefined;
18
- }
19
-
20
- /**
21
- * screen regions the system currently occludes, in window coordinates
22
- */
23
- const ReservedRegionsContext = createContext<ReservedRegion[]>([]);
24
-
25
- /**
26
- * tracks the system occlusion regions (iOS 27.1+), e.g. the iPhone Duo status bar
27
- * cluster growing for a call or a Live Activity
28
- */
29
- const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
30
- children,
31
- }) => {
32
- const [regions, setRegions] = useState<ReservedRegion[]>([]);
33
-
34
- return (
35
- <ReservedRegionsContext.Provider value={regions}>
36
- {children}
37
- {Platform.OS === 'ios' && !!ReservedRegionsView && (
38
- <ReservedRegionsView
39
- style={StyleSheet.absoluteFill}
40
- onRegionsChange={setRegions}
41
- />
42
- )}
43
- </ReservedRegionsContext.Provider>
44
- );
45
- };
46
-
47
- export { ReservedRegionsContext, ReservedRegionsProvider };