@react-navigation/elements 2.9.3 → 3.0.0-alpha.0

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 (184) hide show
  1. package/lib/module/Badge.js +2 -2
  2. package/lib/module/Badge.js.map +1 -1
  3. package/lib/module/BlurEffectBackground.js +59 -0
  4. package/lib/module/BlurEffectBackground.js.map +1 -0
  5. package/lib/module/Button.js +7 -6
  6. package/lib/module/Button.js.map +1 -1
  7. package/lib/module/Color.js +11 -0
  8. package/lib/module/Color.js.map +1 -0
  9. package/lib/module/Container.js +42 -0
  10. package/lib/module/Container.js.map +1 -0
  11. package/lib/module/Header/Header.js +152 -97
  12. package/lib/module/Header/Header.js.map +1 -1
  13. package/lib/module/Header/HeaderBackButton.js +130 -121
  14. package/lib/module/Header/HeaderBackButton.js.map +1 -1
  15. package/lib/module/Header/HeaderBackground.js +10 -17
  16. package/lib/module/Header/HeaderBackground.js.map +1 -1
  17. package/lib/module/Header/HeaderButton.js +6 -2
  18. package/lib/module/Header/HeaderButton.js.map +1 -1
  19. package/lib/module/Header/HeaderButtonBackground.js +27 -0
  20. package/lib/module/Header/HeaderButtonBackground.js.map +1 -0
  21. package/lib/module/Header/HeaderSearchBar.js +174 -123
  22. package/lib/module/Header/HeaderSearchBar.js.map +1 -1
  23. package/lib/module/Header/HeaderTitle.js.map +1 -1
  24. package/lib/module/Header/getDefaultHeaderHeight.js +22 -10
  25. package/lib/module/Header/getDefaultHeaderHeight.js.map +1 -1
  26. package/lib/module/Label/Label.js.map +1 -1
  27. package/lib/module/LiquidGlassView.ios.js +21 -0
  28. package/lib/module/LiquidGlassView.ios.js.map +1 -0
  29. package/lib/module/LiquidGlassView.js +13 -0
  30. package/lib/module/LiquidGlassView.js.map +1 -0
  31. package/lib/module/MissingIcon.js +1 -0
  32. package/lib/module/MissingIcon.js.map +1 -1
  33. package/lib/module/PlatformColor.js +9 -0
  34. package/lib/module/PlatformColor.js.map +1 -0
  35. package/lib/module/PlatformColor.native.js +4 -0
  36. package/lib/module/PlatformColor.native.js.map +1 -0
  37. package/lib/module/PlatformPressable.js.map +1 -1
  38. package/lib/module/Screen.js +29 -23
  39. package/lib/module/Screen.js.map +1 -1
  40. package/lib/module/assets/back-icon.ios.svg +4 -0
  41. package/lib/module/assets/back-icon@1x.ios.png +0 -0
  42. package/lib/module/assets/back-icon@2x.ios.png +0 -0
  43. package/lib/module/assets/back-icon@3x.ios.png +0 -0
  44. package/lib/module/assets/back-icon@4x.ios.png +0 -0
  45. package/lib/module/assets/search-icon-legacy.png +0 -0
  46. package/lib/module/assets/search-icon-legacy@1x.ios.png +0 -0
  47. package/lib/module/assets/search-icon-legacy@2x.ios.png +0 -0
  48. package/lib/module/assets/search-icon-legacy@3x.ios.png +0 -0
  49. package/lib/module/assets/search-icon-legacy@4x.ios.png +0 -0
  50. package/lib/module/assets/search-icon.ios.svg +4 -0
  51. package/lib/module/assets/search-icon@1x.ios.png +0 -0
  52. package/lib/module/assets/search-icon@2x.ios.png +0 -0
  53. package/lib/module/assets/search-icon@3x.ios.png +0 -0
  54. package/lib/module/assets/search-icon@4x.ios.png +0 -0
  55. package/lib/module/getBlurBackgroundColor.js +48 -0
  56. package/lib/module/getBlurBackgroundColor.js.map +1 -0
  57. package/lib/module/index.js +2 -8
  58. package/lib/module/index.js.map +1 -1
  59. package/lib/module/internal.js +10 -0
  60. package/lib/module/internal.js.map +1 -0
  61. package/lib/module/useFrameSize.js +4 -4
  62. package/lib/module/useFrameSize.js.map +1 -1
  63. package/lib/typescript/src/Badge.d.ts.map +1 -1
  64. package/lib/typescript/src/BlurEffectBackground.d.ts +16 -0
  65. package/lib/typescript/src/BlurEffectBackground.d.ts.map +1 -0
  66. package/lib/typescript/src/Button.d.ts +5 -4
  67. package/lib/typescript/src/Button.d.ts.map +1 -1
  68. package/lib/typescript/src/Color.d.ts +13 -0
  69. package/lib/typescript/src/Color.d.ts.map +1 -0
  70. package/lib/typescript/src/Container.d.ts +8 -0
  71. package/lib/typescript/src/Container.d.ts.map +1 -0
  72. package/lib/typescript/src/Header/Header.d.ts +1 -5
  73. package/lib/typescript/src/Header/Header.d.ts.map +1 -1
  74. package/lib/typescript/src/Header/HeaderBackButton.d.ts +1 -1
  75. package/lib/typescript/src/Header/HeaderBackButton.d.ts.map +1 -1
  76. package/lib/typescript/src/Header/HeaderBackground.d.ts +5 -3
  77. package/lib/typescript/src/Header/HeaderBackground.d.ts.map +1 -1
  78. package/lib/typescript/src/Header/HeaderButton.d.ts +2 -0
  79. package/lib/typescript/src/Header/HeaderButton.d.ts.map +1 -1
  80. package/lib/typescript/src/Header/HeaderButtonBackground.d.ts +5 -0
  81. package/lib/typescript/src/Header/HeaderButtonBackground.d.ts.map +1 -0
  82. package/lib/typescript/src/Header/HeaderSearchBar.d.ts +5 -2
  83. package/lib/typescript/src/Header/HeaderSearchBar.d.ts.map +1 -1
  84. package/lib/typescript/src/Header/HeaderTitle.d.ts +2 -2
  85. package/lib/typescript/src/Header/HeaderTitle.d.ts.map +1 -1
  86. package/lib/typescript/src/Header/getDefaultHeaderHeight.d.ts +5 -2
  87. package/lib/typescript/src/Header/getDefaultHeaderHeight.d.ts.map +1 -1
  88. package/lib/typescript/src/Label/Label.d.ts +2 -2
  89. package/lib/typescript/src/Label/Label.d.ts.map +1 -1
  90. package/lib/typescript/src/LiquidGlassView.d.ts +9 -0
  91. package/lib/typescript/src/LiquidGlassView.d.ts.map +1 -0
  92. package/lib/typescript/src/LiquidGlassView.ios.d.ts +5 -0
  93. package/lib/typescript/src/LiquidGlassView.ios.d.ts.map +1 -0
  94. package/lib/typescript/src/MissingIcon.d.ts +2 -2
  95. package/lib/typescript/src/MissingIcon.d.ts.map +1 -1
  96. package/lib/typescript/src/PlatformColor.d.ts +7 -0
  97. package/lib/typescript/src/PlatformColor.d.ts.map +1 -0
  98. package/lib/typescript/src/PlatformColor.native.d.ts +2 -0
  99. package/lib/typescript/src/PlatformColor.native.d.ts.map +1 -0
  100. package/lib/typescript/src/PlatformPressable.d.ts +3 -3
  101. package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
  102. package/lib/typescript/src/Screen.d.ts.map +1 -1
  103. package/lib/typescript/src/getBlurBackgroundColor.d.ts +7 -0
  104. package/lib/typescript/src/getBlurBackgroundColor.d.ts.map +1 -0
  105. package/lib/typescript/src/index.d.ts +0 -6
  106. package/lib/typescript/src/index.d.ts.map +1 -1
  107. package/lib/typescript/src/internal.d.ts +8 -0
  108. package/lib/typescript/src/internal.d.ts.map +1 -0
  109. package/lib/typescript/src/types.d.ts +20 -22
  110. package/lib/typescript/src/types.d.ts.map +1 -1
  111. package/package.json +19 -17
  112. package/src/Badge.tsx +3 -2
  113. package/src/BlurEffectBackground.tsx +90 -0
  114. package/src/Button.tsx +33 -21
  115. package/src/Color.tsx +21 -0
  116. package/src/Container.tsx +44 -0
  117. package/src/Header/Header.tsx +226 -156
  118. package/src/Header/HeaderBackButton.tsx +194 -168
  119. package/src/Header/HeaderBackground.tsx +17 -19
  120. package/src/Header/HeaderButton.tsx +7 -2
  121. package/src/Header/HeaderButtonBackground.tsx +29 -0
  122. package/src/Header/HeaderSearchBar.tsx +227 -129
  123. package/src/Header/HeaderTitle.tsx +2 -1
  124. package/src/Header/getDefaultHeaderHeight.tsx +29 -18
  125. package/src/Label/Label.tsx +2 -1
  126. package/src/LiquidGlassView.ios.tsx +39 -0
  127. package/src/LiquidGlassView.tsx +20 -0
  128. package/src/MissingIcon.tsx +12 -3
  129. package/src/PlatformColor.native.tsx +1 -0
  130. package/src/PlatformColor.tsx +8 -0
  131. package/src/PlatformPressable.tsx +2 -1
  132. package/src/Screen.tsx +24 -25
  133. package/src/assets/back-icon.ios.svg +4 -0
  134. package/src/assets/back-icon@1x.ios.png +0 -0
  135. package/src/assets/back-icon@2x.ios.png +0 -0
  136. package/src/assets/back-icon@3x.ios.png +0 -0
  137. package/src/assets/back-icon@4x.ios.png +0 -0
  138. package/src/assets/search-icon-legacy.png +0 -0
  139. package/src/assets/search-icon-legacy@1x.ios.png +0 -0
  140. package/src/assets/search-icon-legacy@2x.ios.png +0 -0
  141. package/src/assets/search-icon-legacy@3x.ios.png +0 -0
  142. package/src/assets/search-icon-legacy@4x.ios.png +0 -0
  143. package/src/assets/search-icon.ios.svg +4 -0
  144. package/src/assets/search-icon@1x.ios.png +0 -0
  145. package/src/assets/search-icon@2x.ios.png +0 -0
  146. package/src/assets/search-icon@3x.ios.png +0 -0
  147. package/src/assets/search-icon@4x.ios.png +0 -0
  148. package/src/getBlurBackgroundColor.tsx +68 -0
  149. package/src/index.tsx +2 -8
  150. package/src/internal.tsx +7 -0
  151. package/src/types.tsx +21 -21
  152. package/src/useFrameSize.tsx +4 -4
  153. package/lib/module/Background.js +0 -22
  154. package/lib/module/Background.js.map +0 -1
  155. package/lib/module/MaskedView.android.js +0 -4
  156. package/lib/module/MaskedView.android.js.map +0 -1
  157. package/lib/module/MaskedView.ios.js +0 -4
  158. package/lib/module/MaskedView.ios.js.map +0 -1
  159. package/lib/module/MaskedView.js +0 -12
  160. package/lib/module/MaskedView.js.map +0 -1
  161. package/lib/module/MaskedViewNative.js +0 -30
  162. package/lib/module/MaskedViewNative.js.map +0 -1
  163. package/lib/module/ResourceSavingView.js +0 -57
  164. package/lib/module/ResourceSavingView.js.map +0 -1
  165. package/lib/module/assets/back-icon-mask.png +0 -0
  166. package/lib/typescript/src/Background.d.ts +0 -9
  167. package/lib/typescript/src/Background.d.ts.map +0 -1
  168. package/lib/typescript/src/MaskedView.android.d.ts +0 -2
  169. package/lib/typescript/src/MaskedView.android.d.ts.map +0 -1
  170. package/lib/typescript/src/MaskedView.d.ts +0 -11
  171. package/lib/typescript/src/MaskedView.d.ts.map +0 -1
  172. package/lib/typescript/src/MaskedView.ios.d.ts +0 -2
  173. package/lib/typescript/src/MaskedView.ios.d.ts.map +0 -1
  174. package/lib/typescript/src/MaskedViewNative.d.ts +0 -11
  175. package/lib/typescript/src/MaskedViewNative.d.ts.map +0 -1
  176. package/lib/typescript/src/ResourceSavingView.d.ts +0 -10
  177. package/lib/typescript/src/ResourceSavingView.d.ts.map +0 -1
  178. package/src/Background.tsx +0 -24
  179. package/src/MaskedView.android.tsx +0 -1
  180. package/src/MaskedView.ios.tsx +0 -1
  181. package/src/MaskedView.tsx +0 -13
  182. package/src/MaskedViewNative.tsx +0 -33
  183. package/src/ResourceSavingView.tsx +0 -76
  184. package/src/assets/back-icon-mask.png +0 -0
@@ -0,0 +1,44 @@
1
+ import { Platform, View, type ViewStyle } from 'react-native';
2
+
3
+ export type Props = {
4
+ inert?: boolean;
5
+ style?: React.CSSProperties & ViewStyle;
6
+ children: React.ReactNode;
7
+ };
8
+
9
+ export function Container({ inert, children, style }: Props) {
10
+ if (Platform.OS === 'web') {
11
+ return (
12
+ <div
13
+ inert={inert}
14
+ aria-hidden={inert}
15
+ style={{ ...DEFAULT_STYLE, ...style }}
16
+ >
17
+ {children}
18
+ </div>
19
+ );
20
+ }
21
+
22
+ return (
23
+ <View
24
+ aria-hidden={inert}
25
+ style={[{ pointerEvents: inert ? 'none' : 'box-none' }, style]}
26
+ collapsable={false}
27
+ >
28
+ {children}
29
+ </View>
30
+ );
31
+ }
32
+
33
+ const DEFAULT_STYLE = {
34
+ display: 'flex',
35
+ alignContent: 'flex-start',
36
+ alignItems: 'stretch',
37
+ boxSizing: 'border-box',
38
+ flexBasis: 'auto',
39
+ flexDirection: 'column',
40
+ flexShrink: 0,
41
+ minHeight: 0,
42
+ minWidth: 0,
43
+ position: 'relative',
44
+ } as const satisfies React.CSSProperties;
@@ -1,9 +1,8 @@
1
1
  import { useNavigation, useTheme } from '@react-navigation/native';
2
- import Color from 'color';
3
2
  import * as React from 'react';
4
3
  import {
5
4
  Animated,
6
- type LayoutChangeEvent,
5
+ Easing,
7
6
  Platform,
8
7
  StyleSheet,
9
8
  View,
@@ -12,20 +11,21 @@ import {
12
11
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
13
12
 
14
13
  import searchIcon from '../assets/search-icon.png';
15
- import type { HeaderOptions, Layout } from '../types';
14
+ import { Color } from '../Color';
15
+ import { isLiquidGlassSupported } from '../LiquidGlassView';
16
+ import { PlatformColor } from '../PlatformColor';
17
+ import type { HeaderOptions } from '../types';
16
18
  import { useFrameSize } from '../useFrameSize';
17
19
  import { getDefaultHeaderHeight } from './getDefaultHeaderHeight';
18
20
  import { HeaderBackButton } from './HeaderBackButton';
19
21
  import { HeaderBackground } from './HeaderBackground';
20
- import { HeaderButton } from './HeaderButton';
22
+ import { BUTTON_SIZE, BUTTON_SPACING, HeaderButton } from './HeaderButton';
23
+ import { HeaderButtonBackground } from './HeaderButtonBackground';
21
24
  import { HeaderIcon } from './HeaderIcon';
22
25
  import { HeaderSearchBar } from './HeaderSearchBar';
23
26
  import { HeaderShownContext } from './HeaderShownContext';
24
27
  import { HeaderTitle } from './HeaderTitle';
25
28
 
26
- // Width of the screen in split layout on portrait mode on iPad Mini
27
- const IPAD_MINI_MEDIUM_WIDTH = 414;
28
-
29
29
  type Props = HeaderOptions & {
30
30
  /**
31
31
  * Options for the back button.
@@ -44,16 +44,18 @@ type Props = HeaderOptions & {
44
44
  * Whether the header is in a modal
45
45
  */
46
46
  modal?: boolean;
47
- /**
48
- * Layout of the screen.
49
- */
50
- layout?: Layout;
51
47
  /**
52
48
  * Title text for the header.
53
49
  */
54
50
  title: string;
55
51
  };
56
52
 
53
+ const STATUS_BAR_OFFSET = Platform.select({
54
+ // The top inset on iOS is a bit less than the status bar height
55
+ ios: -7,
56
+ default: 0,
57
+ });
58
+
57
59
  const warnIfHeaderStylesDefined = (styles: Record<string, any>) => {
58
60
  Object.keys(styles).forEach((styleProp) => {
59
61
  const value = styles[styleProp];
@@ -70,37 +72,18 @@ const warnIfHeaderStylesDefined = (styles: Record<string, any>) => {
70
72
  });
71
73
  };
72
74
 
75
+ const useNativeDriver = Platform.OS !== 'web';
76
+
73
77
  export function Header(props: Props) {
74
78
  const insets = useSafeAreaInsets();
75
- const frame = useFrameSize((size) => size, true);
76
79
  const { colors } = useTheme();
77
80
 
78
81
  const navigation = useNavigation();
79
82
  const isParentHeaderShown = React.useContext(HeaderShownContext);
80
83
 
81
84
  const [searchBarVisible, setSearchBarVisible] = React.useState(false);
82
- const [titleLayout, setTitleLayout] = React.useState<Layout | undefined>(
83
- undefined
84
- );
85
-
86
- const onTitleLayout = (e: LayoutChangeEvent) => {
87
- const { height, width } = e.nativeEvent.layout;
88
-
89
- setTitleLayout((titleLayout) => {
90
- if (
91
- titleLayout &&
92
- height === titleLayout.height &&
93
- width === titleLayout.width
94
- ) {
95
- return titleLayout;
96
- }
97
-
98
- return { height, width };
99
- });
100
- };
101
85
 
102
86
  const {
103
- layout = frame,
104
87
  modal = false,
105
88
  back,
106
89
  title,
@@ -111,13 +94,17 @@ export function Header(props: Props) {
111
94
  headerTransparent,
112
95
  headerTintColor,
113
96
  headerBackground,
97
+ headerBlurEffect,
114
98
  headerRight,
115
99
  headerTitleAllowFontScaling: titleAllowFontScaling,
116
100
  headerTitleStyle: titleStyle,
117
101
  headerLeftContainerStyle: leftContainerStyle,
118
102
  headerRightContainerStyle: rightContainerStyle,
119
103
  headerTitleContainerStyle: titleContainerStyle,
120
- headerBackButtonDisplayMode = Platform.OS === 'ios' ? 'default' : 'minimal',
104
+ headerBackButtonDisplayMode = Platform.OS !== 'ios' ||
105
+ isLiquidGlassSupported
106
+ ? 'minimal'
107
+ : 'default',
121
108
  headerBackTitleStyle,
122
109
  headerBackgroundContainerStyle: backgroundContainerStyle,
123
110
  headerStyle: customHeaderStyle,
@@ -127,10 +114,12 @@ export function Header(props: Props) {
127
114
  headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
128
115
  } = props;
129
116
 
130
- const defaultHeight = getDefaultHeaderHeight(
131
- layout,
132
- modal,
133
- headerStatusBarHeight
117
+ const defaultHeight = useFrameSize((frame) =>
118
+ getDefaultHeaderHeight({
119
+ landscape: frame.width > frame.height,
120
+ modalPresentation: modal,
121
+ topInset: headerStatusBarHeight,
122
+ })
134
123
  );
135
124
 
136
125
  const {
@@ -271,7 +260,10 @@ export function Header(props: Props) {
271
260
  const iconTintColor =
272
261
  headerTintColor ??
273
262
  Platform.select({
274
- ios: colors.primary,
263
+ ios:
264
+ isLiquidGlassSupported && PlatformColor
265
+ ? PlatformColor('label')
266
+ : colors.primary,
275
267
  default: colors.text,
276
268
  });
277
269
 
@@ -281,8 +273,6 @@ export function Header(props: Props) {
281
273
  pressColor: headerPressColor,
282
274
  pressOpacity: headerPressOpacity,
283
275
  displayMode: headerBackButtonDisplayMode,
284
- titleLayout,
285
- screenLayout: layout,
286
276
  canGoBack: Boolean(back),
287
277
  onPress: back ? navigation.goBack : undefined,
288
278
  label: back?.title,
@@ -307,166 +297,246 @@ export function Header(props: Props) {
307
297
  )
308
298
  : customTitle;
309
299
 
300
+ const buttonMinWidth =
301
+ headerTitleAlign === 'center' && (leftButton || rightButton)
302
+ ? BUTTON_SIZE
303
+ : 0;
304
+
305
+ const [searchBarRendered, setSearchBarRendered] =
306
+ React.useState(searchBarVisible);
307
+ const searchBarVisibleRef = React.useRef(searchBarVisible);
308
+ const [searchBarVisibleAnim] = React.useState(
309
+ () => new Animated.Value(searchBarVisible ? 1 : 0)
310
+ );
311
+
312
+ if (searchBarVisible && !searchBarRendered) {
313
+ setSearchBarRendered(true);
314
+ }
315
+
316
+ React.useEffect(() => {
317
+ // Avoid act warning in tests just by rendering header
318
+ if (searchBarVisible === searchBarVisibleRef.current) {
319
+ return;
320
+ }
321
+
322
+ Animated.timing(searchBarVisibleAnim, {
323
+ toValue: searchBarVisible ? 1 : 0,
324
+ duration: 150,
325
+ useNativeDriver,
326
+ easing: Easing.in(Easing.linear),
327
+ }).start(({ finished }) => {
328
+ if (finished) {
329
+ setSearchBarRendered(searchBarVisible);
330
+ searchBarVisibleRef.current = searchBarVisible;
331
+ }
332
+ });
333
+
334
+ return () => {
335
+ searchBarVisibleAnim.stopAnimation();
336
+ };
337
+ }, [searchBarVisible, searchBarVisibleAnim]);
338
+
339
+ const headerOpacity = searchBarVisibleAnim.interpolate({
340
+ inputRange: [0, 1],
341
+ outputRange: [1, 0],
342
+ });
343
+
344
+ const searchBarOpacity = searchBarVisibleAnim.interpolate({
345
+ inputRange: [0, 1],
346
+ outputRange: [
347
+ // FIXME: Liquid glass views don't work properly with `opacity: 0`
348
+ // So we use a small value instead to workaround this issue.
349
+ 0.1, 1,
350
+ ],
351
+ });
352
+
353
+ const statusBarSpacing = Math.max(
354
+ headerStatusBarHeight + STATUS_BAR_OFFSET,
355
+ 0
356
+ );
357
+
310
358
  return (
311
359
  <Animated.View
312
- pointerEvents="box-none"
313
- style={[{ height, minHeight, maxHeight, opacity, transform }]}
360
+ style={[
361
+ {
362
+ pointerEvents: 'box-none',
363
+ height,
364
+ minHeight,
365
+ maxHeight,
366
+ opacity,
367
+ transform,
368
+ },
369
+ ]}
314
370
  >
315
- <Animated.View
316
- pointerEvents="box-none"
317
- style={[StyleSheet.absoluteFill, backgroundContainerStyle]}
318
- >
371
+ <Animated.View style={[styles.background, backgroundContainerStyle]}>
319
372
  {headerBackground ? (
320
373
  headerBackground({ style: backgroundStyle })
321
374
  ) : (
322
375
  <HeaderBackground
323
- pointerEvents={
324
- // Allow touch through the header when background color is transparent
325
- headerTransparent &&
326
- (backgroundStyle.backgroundColor === 'transparent' ||
327
- Color(backgroundStyle.backgroundColor).alpha() === 0)
328
- ? 'none'
329
- : 'auto'
330
- }
331
- style={backgroundStyle}
376
+ blurEffect={headerBlurEffect}
377
+ style={[
378
+ {
379
+ // Allow touch through the header when background color is transparent
380
+ pointerEvents:
381
+ headerTransparent &&
382
+ backgroundStyle.backgroundColor &&
383
+ (backgroundStyle.backgroundColor === 'transparent' ||
384
+ Color(backgroundStyle.backgroundColor)?.alpha() === 0)
385
+ ? 'none'
386
+ : 'auto',
387
+ },
388
+ backgroundStyle,
389
+ ]}
332
390
  />
333
391
  )}
334
392
  </Animated.View>
335
- <View pointerEvents="none" style={{ height: headerStatusBarHeight }} />
336
- <View
337
- pointerEvents="box-none"
393
+ <Animated.View
338
394
  style={[
339
395
  styles.content,
340
- Platform.OS === 'ios' && frame.width >= IPAD_MINI_MEDIUM_WIDTH
341
- ? styles.large
342
- : null,
396
+ {
397
+ pointerEvents: searchBarVisible ? 'none' : 'auto',
398
+ marginTop: statusBarSpacing,
399
+ opacity: headerOpacity,
400
+ },
343
401
  ]}
344
402
  >
345
- <Animated.View
346
- pointerEvents="box-none"
403
+ <View
347
404
  style={[
348
405
  styles.start,
349
- !searchBarVisible && headerTitleAlign === 'center' && styles.expand,
350
- { marginStart: insets.left },
351
- leftContainerStyle,
406
+ headerTitleAlign === 'center' ? styles.expand : styles.shrink,
407
+ {
408
+ minWidth: buttonMinWidth,
409
+ marginStart: insets.left,
410
+ },
352
411
  ]}
353
412
  >
354
- {leftButton}
413
+ <HeaderButtonBackground
414
+ style={[styles.buttonContainer, leftContainerStyle]}
415
+ >
416
+ {leftButton}
417
+ </HeaderButtonBackground>
418
+ </View>
419
+ <Animated.View
420
+ style={[
421
+ styles.title,
422
+ !leftButton && styles.titleStart,
423
+ titleContainerStyle,
424
+ ]}
425
+ >
426
+ {headerTitle({
427
+ children: title,
428
+ allowFontScaling: titleAllowFontScaling,
429
+ tintColor: headerTintColor,
430
+ style: [styles.titleText, titleStyle],
431
+ })}
355
432
  </Animated.View>
356
- {Platform.OS === 'ios' || !searchBarVisible ? (
357
- <>
358
- <Animated.View
359
- pointerEvents="box-none"
360
- style={[
361
- styles.title,
362
- {
363
- // Avoid the title from going offscreen or overlapping buttons
364
- maxWidth:
365
- headerTitleAlign === 'center'
366
- ? layout.width -
367
- ((leftButton
368
- ? headerBackButtonDisplayMode !== 'minimal'
369
- ? 80
370
- : 32
371
- : 16) +
372
- (rightButton || headerSearchBarOptions ? 16 : 0) +
373
- Math.max(insets.left, insets.right)) *
374
- 2
375
- : layout.width -
376
- ((leftButton ? 52 : 16) +
377
- (rightButton || headerSearchBarOptions ? 52 : 16) +
378
- insets.left -
379
- insets.right),
380
- },
381
- headerTitleAlign === 'left' && leftButton
382
- ? { marginStart: 4 }
383
- : { marginHorizontal: 16 },
384
- titleContainerStyle,
385
- ]}
386
- >
387
- {headerTitle({
388
- children: title,
389
- allowFontScaling: titleAllowFontScaling,
390
- tintColor: headerTintColor,
391
- onLayout: onTitleLayout,
392
- style: titleStyle,
393
- })}
394
- </Animated.View>
395
- <Animated.View
396
- pointerEvents="box-none"
397
- style={[
398
- styles.end,
399
- styles.expand,
400
- { marginEnd: insets.right },
401
- rightContainerStyle,
402
- ]}
403
- >
404
- {rightButton}
405
- {headerSearchBarOptions ? (
406
- <HeaderButton
407
- tintColor={iconTintColor}
408
- pressColor={headerPressColor}
409
- pressOpacity={headerPressOpacity}
410
- onPress={() => {
411
- setSearchBarVisible(true);
412
- headerSearchBarOptions?.onOpen?.();
413
- }}
414
- >
415
- <HeaderIcon source={searchIcon} tintColor={iconTintColor} />
416
- </HeaderButton>
417
- ) : null}
418
- </Animated.View>
419
- </>
420
- ) : null}
421
- {Platform.OS === 'ios' || searchBarVisible ? (
422
- <HeaderSearchBar
423
- {...headerSearchBarOptions}
424
- visible={searchBarVisible}
425
- onClose={() => {
426
- setSearchBarVisible(false);
427
- headerSearchBarOptions?.onClose?.();
428
- }}
429
- tintColor={headerTintColor}
430
- style={[
431
- Platform.OS === 'ios'
432
- ? [
433
- StyleSheet.absoluteFill,
434
- { paddingTop: headerStatusBarHeight ? 0 : 4 },
435
- { backgroundColor: backgroundColor ?? colors.card },
436
- ]
437
- : !leftButton && { marginStart: 8 },
438
- ]}
439
- />
440
- ) : null}
441
- </View>
433
+ <View
434
+ style={[
435
+ styles.end,
436
+ styles.expand,
437
+ {
438
+ minWidth: buttonMinWidth,
439
+ marginEnd: insets.right,
440
+ },
441
+ ]}
442
+ >
443
+ <HeaderButtonBackground
444
+ style={[styles.buttonContainer, rightContainerStyle]}
445
+ >
446
+ {rightButton}
447
+ {headerSearchBarOptions ? (
448
+ <HeaderButton
449
+ tintColor={iconTintColor}
450
+ pressColor={headerPressColor}
451
+ pressOpacity={headerPressOpacity}
452
+ onPress={() => {
453
+ setSearchBarVisible(true);
454
+ headerSearchBarOptions?.onOpen?.();
455
+ }}
456
+ >
457
+ <HeaderIcon source={searchIcon} tintColor={iconTintColor} />
458
+ </HeaderButton>
459
+ ) : null}
460
+ </HeaderButtonBackground>
461
+ </View>
462
+ </Animated.View>
463
+ {searchBarRendered ? (
464
+ <HeaderSearchBar
465
+ {...headerSearchBarOptions}
466
+ statusBarHeight={statusBarSpacing}
467
+ visible={searchBarVisible}
468
+ onClose={() => {
469
+ setSearchBarVisible(false);
470
+ headerSearchBarOptions?.onClose?.();
471
+ }}
472
+ tintColor={headerTintColor}
473
+ style={[StyleSheet.absoluteFill, { opacity: searchBarOpacity }]}
474
+ />
475
+ ) : null}
442
476
  </Animated.View>
443
477
  );
444
478
  }
445
479
 
480
+ const BUTTON_OFFSET = Platform.OS === 'ios' ? 10 : 4;
481
+ const TITLE_START_OFFSET =
482
+ Platform.OS === 'ios'
483
+ ? 0
484
+ : // Since button container is always present,
485
+ // We need to account for its horizontal margin as well
486
+ 16 - BUTTON_OFFSET * 2;
487
+
446
488
  const styles = StyleSheet.create({
447
489
  content: {
490
+ pointerEvents: 'box-none',
448
491
  flex: 1,
449
492
  flexDirection: 'row',
450
493
  alignItems: 'stretch',
451
494
  },
452
- large: {
453
- marginHorizontal: 5,
454
- },
455
495
  title: {
496
+ flexShrink: 1,
497
+ minWidth: 0,
456
498
  justifyContent: 'center',
499
+ pointerEvents: 'box-none',
500
+ // Make sure title goes below liquid glass buttons
501
+ zIndex: -1,
502
+ },
503
+ titleStart: {
504
+ marginLeft: TITLE_START_OFFSET,
505
+ },
506
+ titleText: {
507
+ textAlign: 'center',
508
+ },
509
+ buttonContainer: {
510
+ flexDirection: 'row',
511
+ pointerEvents: 'box-none',
512
+ gap: BUTTON_SPACING,
513
+ marginHorizontal: BUTTON_OFFSET,
457
514
  },
458
515
  start: {
459
516
  flexDirection: 'row',
460
517
  alignItems: 'center',
461
518
  justifyContent: 'flex-start',
519
+ pointerEvents: 'box-none',
462
520
  },
463
521
  end: {
464
522
  flexDirection: 'row',
465
523
  alignItems: 'center',
466
524
  justifyContent: 'flex-end',
525
+ pointerEvents: 'box-none',
467
526
  },
468
527
  expand: {
469
528
  flexGrow: 1,
529
+ flexShrink: 1,
470
530
  flexBasis: 0,
471
531
  },
532
+ shrink: {
533
+ flexGrow: 0,
534
+ flexShrink: 1,
535
+ minWidth: 0,
536
+ maxWidth: '50%',
537
+ },
538
+ background: {
539
+ ...StyleSheet.absoluteFillObject,
540
+ pointerEvents: 'box-none',
541
+ },
472
542
  });