@react-navigation/elements 2.9.5 → 3.0.0-alpha.1

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 +156 -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 +34 -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 +7 -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 +47 -31
  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 +230 -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 +35 -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 +50 -32
  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
@@ -1,30 +1,43 @@
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,
5
+ BackHandler,
6
+ type ColorValue,
6
7
  Image,
8
+ type NativeEventSubscription,
7
9
  Platform,
8
10
  type StyleProp,
9
11
  StyleSheet,
10
12
  TextInput,
11
- View,
13
+ type TextStyle,
12
14
  type ViewStyle,
13
15
  } from 'react-native';
14
16
 
15
17
  import clearIcon from '../assets/clear-icon.png';
16
18
  import closeIcon from '../assets/close-icon.png';
17
19
  import searchIcon from '../assets/search-icon.png';
20
+ import searchIconLegacy from '../assets/search-icon-legacy.png';
21
+ import { Color } from '../Color';
22
+ import {
23
+ AnimatedLiquidGlassContainerView,
24
+ isLiquidGlassSupported,
25
+ } from '../LiquidGlassView';
18
26
  import { PlatformPressable } from '../PlatformPressable';
19
27
  import { Text } from '../Text';
20
28
  import type { HeaderSearchBarOptions, HeaderSearchBarRef } from '../types';
21
- import { HeaderButton } from './HeaderButton';
29
+ import { HeaderBackButton } from './HeaderBackButton';
30
+ import { BUTTON_SIZE, BUTTON_SPACING, HeaderButton } from './HeaderButton';
31
+ import { HeaderButtonBackground } from './HeaderButtonBackground';
22
32
  import { HeaderIcon } from './HeaderIcon';
23
33
 
24
34
  type Props = Omit<HeaderSearchBarOptions, 'ref'> & {
25
35
  visible: boolean;
26
36
  onClose: () => void;
27
- tintColor?: string;
37
+ tintColor?: ColorValue;
38
+ pressColor?: ColorValue;
39
+ pressOpacity?: number;
40
+ statusBarHeight: number;
28
41
  style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
29
42
  };
30
43
 
@@ -44,11 +57,14 @@ function HeaderSearchBarInternal(
44
57
  autoFocus = true,
45
58
  autoCapitalize,
46
59
  placeholder = 'Search',
47
- cancelButtonText = 'Cancel',
48
60
  enterKeyHint = 'search',
49
- onChangeText,
61
+ cancelButtonText = 'Cancel',
62
+ onChange,
50
63
  onClose,
51
64
  tintColor,
65
+ pressColor,
66
+ pressOpacity,
67
+ statusBarHeight,
52
68
  style,
53
69
  ...rest
54
70
  }: Props,
@@ -57,38 +73,11 @@ function HeaderSearchBarInternal(
57
73
  const navigation = useNavigation();
58
74
  const { dark, colors, fonts } = useTheme();
59
75
  const [value, setValue] = React.useState('');
60
- const [rendered, setRendered] = React.useState(visible);
61
- const [visibleAnim] = React.useState(
62
- () => new Animated.Value(visible ? 1 : 0)
63
- );
64
76
  const [clearVisibleAnim] = React.useState(() => new Animated.Value(0));
65
77
 
66
- const visibleValueRef = React.useRef(visible);
67
78
  const clearVisibleValueRef = React.useRef(false);
68
79
  const inputRef = React.useRef<TextInput>(null);
69
80
 
70
- React.useEffect(() => {
71
- // Avoid act warning in tests just by rendering header
72
- if (visible === visibleValueRef.current) {
73
- return;
74
- }
75
-
76
- Animated.timing(visibleAnim, {
77
- toValue: visible ? 1 : 0,
78
- duration: 100,
79
- useNativeDriver,
80
- }).start(({ finished }) => {
81
- if (finished) {
82
- setRendered(visible);
83
- visibleValueRef.current = visible;
84
- }
85
- });
86
-
87
- return () => {
88
- visibleAnim.stopAnimation();
89
- };
90
- }, [visible, visibleAnim]);
91
-
92
81
  const hasText = value !== '';
93
82
 
94
83
  React.useEffect(() => {
@@ -117,18 +106,49 @@ function HeaderSearchBarInternal(
117
106
  clearText();
118
107
  // FIXME: figure out how to create a SyntheticEvent
119
108
  // @ts-expect-error: we don't have the native event here
120
- onChangeText?.({ nativeEvent: { text: '' } });
121
- }, [clearText, onChangeText]);
109
+ onChange?.({ nativeEvent: { text: '' } });
110
+ }, [clearText, onChange]);
122
111
 
123
112
  const cancelSearch = React.useCallback(() => {
124
- onClear();
113
+ // FIXME: figure out how to create a SyntheticEvent
114
+ // @ts-expect-error: we don't have the native event here
115
+ onChange?.({ nativeEvent: { text: '' } });
125
116
  onClose();
126
- }, [onClear, onClose]);
117
+ setValue('');
118
+ }, [onChange, onClose]);
127
119
 
128
- React.useEffect(
129
- () => navigation?.addListener('blur', cancelSearch),
130
- [cancelSearch, navigation]
131
- );
120
+ React.useEffect(() => {
121
+ const unsubscribeBlur = navigation?.addListener('blur', cancelSearch);
122
+
123
+ const onKeyup = (e: KeyboardEvent) => {
124
+ if (e.key === 'Escape') {
125
+ cancelSearch();
126
+ }
127
+ };
128
+
129
+ let backHandlerSubscription: NativeEventSubscription | undefined;
130
+
131
+ if (Platform.OS === 'web') {
132
+ document?.body?.addEventListener?.('keyup', onKeyup);
133
+ } else {
134
+ backHandlerSubscription = BackHandler.addEventListener(
135
+ 'hardwareBackPress',
136
+ () => {
137
+ cancelSearch();
138
+ return true;
139
+ }
140
+ );
141
+ }
142
+
143
+ return () => {
144
+ unsubscribeBlur();
145
+ backHandlerSubscription?.remove();
146
+
147
+ if (Platform.OS === 'web') {
148
+ document?.body?.removeEventListener?.('keyup', onKeyup);
149
+ }
150
+ };
151
+ }, [cancelSearch, navigation]);
132
152
 
133
153
  React.useImperativeHandle(
134
154
  ref,
@@ -149,29 +169,60 @@ function HeaderSearchBarInternal(
149
169
  [cancelSearch, clearText]
150
170
  );
151
171
 
152
- if (!visible && !rendered) {
153
- return null;
154
- }
155
-
156
172
  const textColor = tintColor ?? colors.text;
157
173
 
174
+ // When status bar height is provided, add spacing below it
175
+ // Otherwise, use a smaller top margin to align with the header content
176
+ const STATUS_BAR_TOP_ADJUSTMENT = 2;
177
+ const topMargin = Platform.select({
178
+ ios: statusBarHeight
179
+ ? statusBarHeight + BUTTON_SPACING
180
+ : BUTTON_SPACING - STATUS_BAR_TOP_ADJUSTMENT,
181
+ default: statusBarHeight,
182
+ });
183
+
158
184
  return (
159
- <Animated.View
160
- pointerEvents={visible ? 'auto' : 'none'}
185
+ <AnimatedLiquidGlassContainerView
186
+ spacing={BUTTON_SPACING}
161
187
  aria-live="polite"
162
188
  aria-hidden={!visible}
163
- style={[styles.container, { opacity: visibleAnim }, style]}
189
+ style={[
190
+ styles.container,
191
+ Platform.OS === 'ios' && {
192
+ gap: BUTTON_SPACING,
193
+ margin: BUTTON_SPACING,
194
+ },
195
+ {
196
+ marginTop: topMargin,
197
+ },
198
+ style,
199
+ ]}
164
200
  >
165
- <View style={styles.searchbarContainer}>
166
- <HeaderIcon
167
- source={searchIcon}
168
- tintColor={textColor}
169
- style={styles.inputSearchIcon}
201
+ {Platform.OS !== 'ios' ? (
202
+ <HeaderBackButton
203
+ accessibilityLabel={cancelButtonText}
204
+ tintColor={tintColor ?? colors.text}
205
+ pressColor={pressColor}
206
+ pressOpacity={pressOpacity}
207
+ onPress={cancelSearch}
208
+ style={styles.backButton}
170
209
  />
210
+ ) : null}
211
+ <HeaderButtonBackground style={styles.searchbarContainer}>
212
+ {Platform.OS === 'ios' ? (
213
+ <HeaderIcon
214
+ source={isLiquidGlassSupported ? searchIcon : searchIconLegacy}
215
+ tintColor={textColor}
216
+ style={[
217
+ styles.inputSearchIconIos,
218
+ !isLiquidGlassSupported && styles.inputSearchIconIosLegacy,
219
+ ]}
220
+ />
221
+ ) : null}
171
222
  <TextInput
172
223
  {...rest}
173
224
  ref={inputRef}
174
- onChange={onChangeText}
225
+ onChange={onChange}
175
226
  onChangeText={setValue}
176
227
  autoFocus={autoFocus}
177
228
  autoCapitalize={
@@ -180,144 +231,191 @@ function HeaderSearchBarInternal(
180
231
  inputMode={INPUT_TYPE_TO_MODE[inputType ?? 'text']}
181
232
  enterKeyHint={enterKeyHint}
182
233
  placeholder={placeholder}
183
- placeholderTextColor={Color(textColor).alpha(0.5).string()}
234
+ placeholderTextColor={Color(textColor)?.alpha(0.5).string()}
184
235
  cursorColor={colors.primary}
185
236
  selectionHandleColor={colors.primary}
186
- selectionColor={Color(colors.primary).alpha(0.3).string()}
237
+ selectionColor={Color(colors.primary)?.alpha(0.3).string()}
187
238
  style={[
188
- fonts.regular,
239
+ Platform.select({
240
+ ios: isLiquidGlassSupported ? fonts.medium : fonts.regular,
241
+ default: fonts.regular,
242
+ }),
189
243
  styles.searchbar,
190
- {
191
- backgroundColor: Platform.select({
192
- ios: dark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)',
193
- default: 'transparent',
194
- }),
195
- color: textColor,
196
- borderBottomColor: Color(textColor).alpha(0.2).string(),
197
- },
244
+ Platform.OS === 'ios' &&
245
+ !isLiquidGlassSupported && {
246
+ backgroundColor: dark
247
+ ? 'rgba(255, 255, 255, 0.1)'
248
+ : 'rgba(0, 0, 0, 0.1)',
249
+ },
250
+ { color: textColor },
198
251
  ]}
199
252
  />
200
253
  {Platform.OS === 'ios' ? (
201
254
  <PlatformPressable
255
+ accessibilityLabel="Clear"
202
256
  onPress={onClear}
203
257
  style={[
204
258
  {
205
259
  opacity: clearVisibleAnim,
206
260
  transform: [{ scale: clearVisibleAnim }],
207
261
  },
208
- styles.clearButton,
262
+ styles.clearButtonIos,
263
+ !isLiquidGlassSupported && styles.clearButtonIosLegacy,
209
264
  ]}
210
265
  >
211
266
  <Image
212
267
  source={clearIcon}
213
268
  resizeMode="contain"
214
269
  tintColor={textColor}
215
- style={styles.clearIcon}
270
+ style={styles.clearIconIos}
216
271
  />
217
272
  </PlatformPressable>
218
273
  ) : null}
219
- </View>
274
+ </HeaderButtonBackground>
220
275
  {Platform.OS !== 'ios' ? (
221
276
  <HeaderButton
222
- onPress={() => {
223
- if (value) {
224
- onClear();
225
- } else {
226
- onClose();
227
- }
228
- }}
229
- style={styles.closeButton}
277
+ accessibilityLabel="Clear"
278
+ onPress={onClear}
279
+ style={[styles.closeButton, { opacity: clearVisibleAnim }]}
230
280
  >
231
281
  <HeaderIcon source={closeIcon} tintColor={textColor} />
232
282
  </HeaderButton>
233
283
  ) : null}
234
284
  {Platform.OS === 'ios' ? (
235
- <PlatformPressable onPress={cancelSearch} style={styles.cancelButton}>
236
- <Text
237
- style={[
238
- fonts.regular,
239
- { color: tintColor ?? colors.primary },
240
- styles.cancelText,
241
- ]}
285
+ isLiquidGlassSupported ? (
286
+ <HeaderButtonBackground style={styles.closeButtonContainerIos}>
287
+ <HeaderButton
288
+ accessibilityLabel={cancelButtonText}
289
+ onPress={cancelSearch}
290
+ >
291
+ <HeaderIcon source={closeIcon} tintColor={textColor} />
292
+ </HeaderButton>
293
+ </HeaderButtonBackground>
294
+ ) : (
295
+ <PlatformPressable
296
+ accessibilityLabel={cancelButtonText}
297
+ onPress={cancelSearch}
298
+ style={styles.cancelButton}
242
299
  >
243
- {cancelButtonText}
244
- </Text>
245
- </PlatformPressable>
300
+ <Text
301
+ style={[
302
+ fonts.regular,
303
+ { color: tintColor ?? colors.primary },
304
+ styles.cancelText,
305
+ ]}
306
+ >
307
+ {cancelButtonText}
308
+ </Text>
309
+ </PlatformPressable>
310
+ )
246
311
  ) : null}
247
- </Animated.View>
312
+ </AnimatedLiquidGlassContainerView>
248
313
  );
249
314
  }
250
315
 
316
+ const SEARCH_ICON_SIZE = 18;
317
+ const CLEAR_ICON_SIZE = 16;
318
+ const CANCEL_FONT_SIZE = 17;
319
+ const SEARCHBAR_FONT_SIZE = Platform.OS === 'ios' ? 17 : 18;
320
+
321
+ const SEARCHBAR_ICON_SPACING = 5;
322
+ const SEARCHBAR_HEIGHT_IOS = isLiquidGlassSupported ? BUTTON_SIZE : 36;
323
+
324
+ // The top inset on iOS is a bit less than the status bar height
325
+ const SEARCHBAR_LEGACY_VERTICAL_OFFSET_IOS = -4;
326
+
251
327
  const styles = StyleSheet.create({
252
328
  container: {
253
329
  flex: 1,
254
330
  flexDirection: 'row',
255
331
  alignItems: 'stretch',
256
332
  },
257
- inputSearchIcon: {
333
+ inputSearchIconIos: {
258
334
  position: 'absolute',
259
335
  opacity: 0.5,
260
- left: Platform.select({ ios: 16, default: 4 }),
261
- top: Platform.select({ ios: -1, default: 17 }),
262
- ...Platform.select({
263
- ios: {
264
- height: 18,
265
- width: 18,
266
- },
267
- default: {},
268
- }),
336
+ top: SEARCHBAR_ICON_SPACING,
337
+ left: SEARCHBAR_ICON_SPACING,
338
+ height: SEARCH_ICON_SIZE,
339
+ width: SEARCH_ICON_SIZE,
340
+ },
341
+ inputSearchIconIosLegacy: {
342
+ top: SEARCHBAR_LEGACY_VERTICAL_OFFSET_IOS,
343
+ },
344
+ backButton: {
345
+ alignSelf: 'center',
346
+ marginLeft: BUTTON_SPACING / 2,
269
347
  },
270
348
  closeButton: {
271
349
  position: 'absolute',
272
350
  opacity: 0.5,
273
- right: Platform.select({ ios: 0, default: 8 }),
274
- top: Platform.select({ ios: -2, default: 17 }),
351
+ right: BUTTON_SPACING,
352
+ height: '100%',
353
+ },
354
+ closeButtonContainerIos: {
355
+ alignSelf: 'center',
275
356
  },
276
- clearButton: {
357
+ clearButtonIos: {
277
358
  position: 'absolute',
278
359
  right: 0,
279
- top: -7,
360
+ top: 0,
280
361
  bottom: 0,
362
+ width: SEARCHBAR_HEIGHT_IOS,
281
363
  justifyContent: 'center',
282
- padding: 8,
364
+ alignItems: 'center',
283
365
  },
284
- clearIcon: {
285
- height: 16,
286
- width: 16,
366
+ clearButtonIosLegacy: {
367
+ top: SEARCHBAR_LEGACY_VERTICAL_OFFSET_IOS - SEARCHBAR_ICON_SPACING,
368
+ },
369
+ clearIconIos: {
370
+ height: CLEAR_ICON_SIZE,
371
+ width: CLEAR_ICON_SIZE,
287
372
  opacity: 0.5,
288
373
  },
289
374
  cancelButton: {
290
375
  alignSelf: 'center',
291
- top: -4,
292
376
  },
293
377
  cancelText: {
294
- fontSize: 17,
295
- marginHorizontal: 12,
378
+ fontSize: CANCEL_FONT_SIZE,
379
+ marginRight: BUTTON_SPACING / 2,
296
380
  },
297
381
  searchbarContainer: {
298
382
  flex: 1,
383
+ flexDirection: 'row',
384
+ alignItems: 'stretch',
385
+ ...Platform.select<ViewStyle>({
386
+ ios: isLiquidGlassSupported
387
+ ? {}
388
+ : {
389
+ minHeight: SEARCHBAR_HEIGHT_IOS,
390
+ },
391
+ default: {
392
+ borderRadius: 0,
393
+ },
394
+ }),
395
+ },
396
+ searchbar: {
397
+ flex: 1,
398
+ backgroundColor: 'transparent',
399
+ fontSize: SEARCHBAR_FONT_SIZE,
400
+ ...Platform.select<TextStyle>({
401
+ ios: {
402
+ paddingHorizontal:
403
+ BUTTON_SPACING + SEARCH_ICON_SIZE + SEARCHBAR_ICON_SPACING * 2,
404
+ ...(!isLiquidGlassSupported
405
+ ? {
406
+ marginTop: SEARCHBAR_LEGACY_VERTICAL_OFFSET_IOS,
407
+ marginBottom: SEARCHBAR_ICON_SPACING,
408
+ borderRadius: BUTTON_SPACING,
409
+ borderCurve: 'continuous',
410
+ }
411
+ : null),
412
+ },
413
+ default: {
414
+ paddingLeft: BUTTON_SPACING,
415
+ paddingRight: BUTTON_SIZE + BUTTON_SPACING,
416
+ },
417
+ }),
299
418
  },
300
- searchbar: Platform.select({
301
- ios: {
302
- flex: 1,
303
- fontSize: 17,
304
- paddingHorizontal: 32,
305
- marginLeft: 16,
306
- marginTop: -1,
307
- marginBottom: 4,
308
- borderRadius: 8,
309
- borderCurve: 'continuous',
310
- },
311
- default: {
312
- flex: 1,
313
- fontSize: 18,
314
- paddingHorizontal: 36,
315
- marginRight: 8,
316
- marginTop: 8,
317
- marginBottom: 8,
318
- borderBottomWidth: 1,
319
- },
320
- }),
321
419
  });
322
420
 
323
421
  export const HeaderSearchBar = React.forwardRef(HeaderSearchBarInternal);
@@ -1,6 +1,7 @@
1
1
  import { useTheme } from '@react-navigation/native';
2
2
  import {
3
3
  Animated,
4
+ type ColorValue,
4
5
  Platform,
5
6
  type StyleProp,
6
7
  StyleSheet,
@@ -9,7 +10,7 @@ import {
9
10
  } from 'react-native';
10
11
 
11
12
  type Props = Omit<TextProps, 'style'> & {
12
- tintColor?: string;
13
+ tintColor?: ColorValue;
13
14
  children?: string;
14
15
  style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
15
16
  };
@@ -1,35 +1,46 @@
1
1
  import { PixelRatio, Platform } from 'react-native';
2
2
 
3
- import type { Layout } from '../types';
3
+ import { isLiquidGlassSupported } from '../LiquidGlassView';
4
4
 
5
- export function getDefaultHeaderHeight(
6
- layout: Layout,
7
- modalPresentation: boolean,
8
- topInset: number
9
- ): number {
5
+ export function getDefaultHeaderHeight({
6
+ landscape,
7
+ modalPresentation,
8
+ topInset,
9
+ }: {
10
+ landscape: boolean;
11
+ modalPresentation: boolean;
12
+ topInset: number;
13
+ }): number {
10
14
  let headerHeight;
11
15
 
12
16
  // On models with Dynamic Island the status bar height is smaller than the safe area top inset.
13
17
  const hasDynamicIsland = Platform.OS === 'ios' && topInset > 50;
14
- const statusBarHeight = hasDynamicIsland
15
- ? topInset - (5 + 1 / PixelRatio.get())
16
- : topInset;
17
-
18
- const isLandscape = layout.width > layout.height;
18
+ const statusBarHeight = Math.max(
19
+ topInset - (hasDynamicIsland ? 5 + 1 / PixelRatio.get() : 0),
20
+ 0
21
+ );
19
22
 
20
23
  if (Platform.OS === 'ios') {
21
- if (Platform.isPad || Platform.isTV) {
22
- if (modalPresentation) {
23
- headerHeight = 56;
24
+ if (isLiquidGlassSupported) {
25
+ if (modalPresentation && !Platform.isPad && !Platform.isTV) {
26
+ headerHeight = 70;
24
27
  } else {
25
- headerHeight = 50;
28
+ if (hasDynamicIsland) {
29
+ headerHeight = 60;
30
+ } else {
31
+ headerHeight = 64;
32
+ }
26
33
  }
27
34
  } else {
28
- if (isLandscape) {
29
- headerHeight = 32;
30
- } else {
35
+ if (Platform.isPad || Platform.isTV) {
31
36
  if (modalPresentation) {
32
37
  headerHeight = 56;
38
+ } else {
39
+ headerHeight = 50;
40
+ }
41
+ } else {
42
+ if (modalPresentation && !landscape) {
43
+ headerHeight = 56;
33
44
  } else {
34
45
  headerHeight = 44;
35
46
  }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type ColorValue,
2
3
  type StyleProp,
3
4
  StyleSheet,
4
5
  type TextProps,
@@ -8,7 +9,7 @@ import {
8
9
  import { Text } from '../Text';
9
10
 
10
11
  type Props = Omit<TextProps, 'style'> & {
11
- tintColor?: string;
12
+ tintColor?: ColorValue;
12
13
  children?: string;
13
14
  style?: StyleProp<TextStyle>;
14
15
  };
@@ -0,0 +1,39 @@
1
+ import { Animated, View } from 'react-native';
2
+
3
+ type CallstackLiquidGlass = typeof import('@callstack/liquid-glass');
4
+
5
+ let isLiquidGlassSupported: boolean,
6
+ LiquidGlassView: CallstackLiquidGlass['LiquidGlassView'],
7
+ LiquidGlassContainerView: CallstackLiquidGlass['LiquidGlassContainerView'],
8
+ AnimatedLiquidGlassView: Animated.AnimatedComponent<typeof LiquidGlassView>,
9
+ AnimatedLiquidGlassContainerView: Animated.AnimatedComponent<
10
+ typeof LiquidGlassContainerView
11
+ >;
12
+
13
+ try {
14
+ // Add try/catch to support usage even if it's not installed, since it's optional.
15
+ isLiquidGlassSupported =
16
+ require('@callstack/liquid-glass').isLiquidGlassSupported;
17
+ LiquidGlassView = require('@callstack/liquid-glass').LiquidGlassView;
18
+ LiquidGlassContainerView =
19
+ require('@callstack/liquid-glass').LiquidGlassContainerView;
20
+ AnimatedLiquidGlassView = Animated.createAnimatedComponent(LiquidGlassView);
21
+ AnimatedLiquidGlassContainerView = Animated.createAnimatedComponent(
22
+ LiquidGlassContainerView
23
+ );
24
+ } catch (e) {
25
+ isLiquidGlassSupported = false;
26
+ LiquidGlassView = View;
27
+ // @ts-expect-error: this is fine
28
+ AnimatedLiquidGlassView = Animated.View;
29
+ // @ts-expect-error: this is fine
30
+ AnimatedLiquidGlassContainerView = Animated.View;
31
+ }
32
+
33
+ export {
34
+ AnimatedLiquidGlassContainerView,
35
+ AnimatedLiquidGlassView,
36
+ isLiquidGlassSupported,
37
+ LiquidGlassContainerView,
38
+ LiquidGlassView,
39
+ };
@@ -0,0 +1,20 @@
1
+ import { Animated, View } from 'react-native';
2
+
3
+ type CallstackLiquidGlass = typeof import('@callstack/liquid-glass');
4
+
5
+ export const LiquidGlassView: CallstackLiquidGlass['LiquidGlassView'] = View;
6
+
7
+ export const LiquidGlassContainerView: CallstackLiquidGlass['LiquidGlassContainerView'] =
8
+ View;
9
+
10
+ // @ts-expect-error: this is fine
11
+ export const AnimatedLiquidGlassView: Animated.AnimatedComponent<
12
+ typeof LiquidGlassView
13
+ > = Animated.View;
14
+
15
+ // @ts-expect-error: this is fine
16
+ export const AnimatedLiquidGlassContainerView: Animated.AnimatedComponent<
17
+ typeof LiquidGlassContainerView
18
+ > = Animated.View;
19
+
20
+ export const isLiquidGlassSupported = false;
@@ -1,15 +1,24 @@
1
- import { type StyleProp, StyleSheet, type TextStyle } from 'react-native';
1
+ import {
2
+ type ColorValue,
3
+ type StyleProp,
4
+ StyleSheet,
5
+ type TextStyle,
6
+ } from 'react-native';
2
7
 
3
8
  import { Text } from './Text';
4
9
 
5
10
  type Props = {
6
- color?: string;
11
+ color?: ColorValue;
7
12
  size?: number;
8
13
  style?: StyleProp<TextStyle>;
9
14
  };
10
15
 
11
16
  export function MissingIcon({ color, size, style }: Props) {
12
- return <Text style={[styles.icon, { color, fontSize: size }, style]}>⏷</Text>;
17
+ return (
18
+ <Text aria-hidden style={[styles.icon, { color, fontSize: size }, style]}>
19
+
20
+ </Text>
21
+ );
13
22
  }
14
23
 
15
24
  const styles = StyleSheet.create({
@@ -0,0 +1 @@
1
+ export { PlatformColor } from 'react-native';