@react-navigation/elements 1.1.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 (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/lib/commonjs/Background.js +34 -0
  4. package/lib/commonjs/Background.js.map +1 -0
  5. package/lib/commonjs/Header/Header.js +250 -0
  6. package/lib/commonjs/Header/Header.js.map +1 -0
  7. package/lib/commonjs/Header/HeaderBackButton.js +222 -0
  8. package/lib/commonjs/Header/HeaderBackButton.js.map +1 -0
  9. package/lib/commonjs/Header/HeaderBackContext.js +15 -0
  10. package/lib/commonjs/Header/HeaderBackContext.js.map +1 -0
  11. package/lib/commonjs/Header/HeaderBackground.js +57 -0
  12. package/lib/commonjs/Header/HeaderBackground.js.map +1 -0
  13. package/lib/commonjs/Header/HeaderHeightContext.js +15 -0
  14. package/lib/commonjs/Header/HeaderHeightContext.js.map +1 -0
  15. package/lib/commonjs/Header/HeaderShownContext.js +15 -0
  16. package/lib/commonjs/Header/HeaderShownContext.js.map +1 -0
  17. package/lib/commonjs/Header/HeaderTitle.js +56 -0
  18. package/lib/commonjs/Header/HeaderTitle.js.map +1 -0
  19. package/lib/commonjs/Header/getDefaultHeaderHeight.js +40 -0
  20. package/lib/commonjs/Header/getDefaultHeaderHeight.js.map +1 -0
  21. package/lib/commonjs/Header/getHeaderTitle.js +11 -0
  22. package/lib/commonjs/Header/getHeaderTitle.js.map +1 -0
  23. package/lib/commonjs/Header/useHeaderHeight.js +27 -0
  24. package/lib/commonjs/Header/useHeaderHeight.js.map +1 -0
  25. package/lib/commonjs/MaskedView.android.js +16 -0
  26. package/lib/commonjs/MaskedView.android.js.map +1 -0
  27. package/lib/commonjs/MaskedView.ios.js +16 -0
  28. package/lib/commonjs/MaskedView.ios.js.map +1 -0
  29. package/lib/commonjs/MaskedView.js +16 -0
  30. package/lib/commonjs/MaskedView.js.map +1 -0
  31. package/lib/commonjs/MaskedViewNative.js +40 -0
  32. package/lib/commonjs/MaskedViewNative.js.map +1 -0
  33. package/lib/commonjs/MissingIcon.js +34 -0
  34. package/lib/commonjs/MissingIcon.js.map +1 -0
  35. package/lib/commonjs/PlatformPressable.js +77 -0
  36. package/lib/commonjs/PlatformPressable.js.map +1 -0
  37. package/lib/commonjs/ResourceSavingView.js +64 -0
  38. package/lib/commonjs/ResourceSavingView.js.map +1 -0
  39. package/lib/commonjs/SafeAreaProviderCompat.js +69 -0
  40. package/lib/commonjs/SafeAreaProviderCompat.js.map +1 -0
  41. package/lib/commonjs/Screen.js +89 -0
  42. package/lib/commonjs/Screen.js.map +1 -0
  43. package/lib/commonjs/assets/back-icon-mask.png +0 -0
  44. package/lib/commonjs/assets/back-icon.png +0 -0
  45. package/lib/commonjs/assets/back-icon@1.5x.android.png +0 -0
  46. package/lib/commonjs/assets/back-icon@1.5x.ios.png +0 -0
  47. package/lib/commonjs/assets/back-icon@1x.android.png +0 -0
  48. package/lib/commonjs/assets/back-icon@1x.ios.png +0 -0
  49. package/lib/commonjs/assets/back-icon@2x.android.png +0 -0
  50. package/lib/commonjs/assets/back-icon@2x.ios.png +0 -0
  51. package/lib/commonjs/assets/back-icon@3x.android.png +0 -0
  52. package/lib/commonjs/assets/back-icon@3x.ios.png +0 -0
  53. package/lib/commonjs/assets/back-icon@4x.android.png +0 -0
  54. package/lib/commonjs/assets/back-icon@4x.ios.png +0 -0
  55. package/lib/commonjs/getNamedContext.js +32 -0
  56. package/lib/commonjs/getNamedContext.js.map +1 -0
  57. package/lib/commonjs/index.js +175 -0
  58. package/lib/commonjs/index.js.map +1 -0
  59. package/lib/commonjs/types.js +6 -0
  60. package/lib/commonjs/types.js.map +1 -0
  61. package/lib/module/Background.js +20 -0
  62. package/lib/module/Background.js.map +1 -0
  63. package/lib/module/Header/Header.js +229 -0
  64. package/lib/module/Header/Header.js.map +1 -0
  65. package/lib/module/Header/HeaderBackButton.js +203 -0
  66. package/lib/module/Header/HeaderBackButton.js.map +1 -0
  67. package/lib/module/Header/HeaderBackContext.js +4 -0
  68. package/lib/module/Header/HeaderBackContext.js.map +1 -0
  69. package/lib/module/Header/HeaderBackground.js +42 -0
  70. package/lib/module/Header/HeaderBackground.js.map +1 -0
  71. package/lib/module/Header/HeaderHeightContext.js +4 -0
  72. package/lib/module/Header/HeaderHeightContext.js.map +1 -0
  73. package/lib/module/Header/HeaderShownContext.js +4 -0
  74. package/lib/module/Header/HeaderShownContext.js.map +1 -0
  75. package/lib/module/Header/HeaderTitle.js +41 -0
  76. package/lib/module/Header/HeaderTitle.js.map +1 -0
  77. package/lib/module/Header/getDefaultHeaderHeight.js +32 -0
  78. package/lib/module/Header/getDefaultHeaderHeight.js.map +1 -0
  79. package/lib/module/Header/getHeaderTitle.js +4 -0
  80. package/lib/module/Header/getHeaderTitle.js.map +1 -0
  81. package/lib/module/Header/useHeaderHeight.js +12 -0
  82. package/lib/module/Header/useHeaderHeight.js.map +1 -0
  83. package/lib/module/MaskedView.android.js +2 -0
  84. package/lib/module/MaskedView.android.js.map +1 -0
  85. package/lib/module/MaskedView.ios.js +2 -0
  86. package/lib/module/MaskedView.ios.js.map +1 -0
  87. package/lib/module/MaskedView.js +9 -0
  88. package/lib/module/MaskedView.js.map +1 -0
  89. package/lib/module/MaskedViewNative.js +26 -0
  90. package/lib/module/MaskedViewNative.js.map +1 -0
  91. package/lib/module/MissingIcon.js +20 -0
  92. package/lib/module/MissingIcon.js.map +1 -0
  93. package/lib/module/PlatformPressable.js +62 -0
  94. package/lib/module/PlatformPressable.js.map +1 -0
  95. package/lib/module/ResourceSavingView.js +50 -0
  96. package/lib/module/ResourceSavingView.js.map +1 -0
  97. package/lib/module/SafeAreaProviderCompat.js +51 -0
  98. package/lib/module/SafeAreaProviderCompat.js.map +1 -0
  99. package/lib/module/Screen.js +67 -0
  100. package/lib/module/Screen.js.map +1 -0
  101. package/lib/module/assets/back-icon-mask.png +0 -0
  102. package/lib/module/assets/back-icon.png +0 -0
  103. package/lib/module/assets/back-icon@1.5x.android.png +0 -0
  104. package/lib/module/assets/back-icon@1.5x.ios.png +0 -0
  105. package/lib/module/assets/back-icon@1x.android.png +0 -0
  106. package/lib/module/assets/back-icon@1x.ios.png +0 -0
  107. package/lib/module/assets/back-icon@2x.android.png +0 -0
  108. package/lib/module/assets/back-icon@2x.ios.png +0 -0
  109. package/lib/module/assets/back-icon@3x.android.png +0 -0
  110. package/lib/module/assets/back-icon@3x.ios.png +0 -0
  111. package/lib/module/assets/back-icon@4x.android.png +0 -0
  112. package/lib/module/assets/back-icon@4x.ios.png +0 -0
  113. package/lib/module/getNamedContext.js +19 -0
  114. package/lib/module/getNamedContext.js.map +1 -0
  115. package/lib/module/index.js +21 -0
  116. package/lib/module/index.js.map +1 -0
  117. package/lib/module/types.js +2 -0
  118. package/lib/module/types.js.map +1 -0
  119. package/lib/typescript/src/Background.d.ts +7 -0
  120. package/lib/typescript/src/Header/Header.d.ts +18 -0
  121. package/lib/typescript/src/Header/HeaderBackButton.d.ts +3 -0
  122. package/lib/typescript/src/Header/HeaderBackContext.d.ts +5 -0
  123. package/lib/typescript/src/Header/HeaderBackground.d.ts +8 -0
  124. package/lib/typescript/src/Header/HeaderHeightContext.d.ts +3 -0
  125. package/lib/typescript/src/Header/HeaderShownContext.d.ts +3 -0
  126. package/lib/typescript/src/Header/HeaderTitle.d.ts +9 -0
  127. package/lib/typescript/src/Header/getDefaultHeaderHeight.d.ts +2 -0
  128. package/lib/typescript/src/Header/getHeaderTitle.d.ts +5 -0
  129. package/lib/typescript/src/Header/useHeaderHeight.d.ts +1 -0
  130. package/lib/typescript/src/MaskedView.android.d.ts +1 -0
  131. package/lib/typescript/src/MaskedView.d.ts +10 -0
  132. package/lib/typescript/src/MaskedView.ios.d.ts +1 -0
  133. package/lib/typescript/src/MaskedViewNative.d.ts +10 -0
  134. package/lib/typescript/src/MissingIcon.d.ts +9 -0
  135. package/lib/typescript/src/PlatformPressable.d.ts +12 -0
  136. package/lib/typescript/src/ResourceSavingView.d.ts +9 -0
  137. package/lib/typescript/src/SafeAreaProviderCompat.d.ts +11 -0
  138. package/lib/typescript/src/Screen.d.ts +17 -0
  139. package/lib/typescript/src/getNamedContext.d.ts +5 -0
  140. package/lib/typescript/src/index.d.ts +18 -0
  141. package/lib/typescript/src/types.d.ts +202 -0
  142. package/package.json +72 -0
  143. package/src/Background.tsx +18 -0
  144. package/src/Header/Header.tsx +306 -0
  145. package/src/Header/HeaderBackButton.tsx +240 -0
  146. package/src/Header/HeaderBackContext.tsx +8 -0
  147. package/src/Header/HeaderBackground.tsx +56 -0
  148. package/src/Header/HeaderHeightContext.tsx +8 -0
  149. package/src/Header/HeaderShownContext.tsx +5 -0
  150. package/src/Header/HeaderTitle.tsx +52 -0
  151. package/src/Header/getDefaultHeaderHeight.tsx +39 -0
  152. package/src/Header/getHeaderTitle.tsx +12 -0
  153. package/src/Header/useHeaderHeight.tsx +15 -0
  154. package/src/MaskedView.android.tsx +1 -0
  155. package/src/MaskedView.ios.tsx +1 -0
  156. package/src/MaskedView.tsx +13 -0
  157. package/src/MaskedViewNative.tsx +33 -0
  158. package/src/MissingIcon.tsx +18 -0
  159. package/src/PlatformPressable.tsx +86 -0
  160. package/src/ResourceSavingView.tsx +70 -0
  161. package/src/SafeAreaProviderCompat.tsx +61 -0
  162. package/src/Screen.tsx +109 -0
  163. package/src/assets/back-icon-mask.png +0 -0
  164. package/src/assets/back-icon.png +0 -0
  165. package/src/assets/back-icon@1.5x.android.png +0 -0
  166. package/src/assets/back-icon@1.5x.ios.png +0 -0
  167. package/src/assets/back-icon@1x.android.png +0 -0
  168. package/src/assets/back-icon@1x.ios.png +0 -0
  169. package/src/assets/back-icon@2x.android.png +0 -0
  170. package/src/assets/back-icon@2x.ios.png +0 -0
  171. package/src/assets/back-icon@3x.android.png +0 -0
  172. package/src/assets/back-icon@3x.ios.png +0 -0
  173. package/src/assets/back-icon@4x.android.png +0 -0
  174. package/src/assets/back-icon@4x.ios.png +0 -0
  175. package/src/getNamedContext.tsx +28 -0
  176. package/src/index.tsx +25 -0
  177. package/src/types.tsx +208 -0
@@ -0,0 +1,240 @@
1
+ import { useTheme } from '@react-navigation/native';
2
+ import * as React from 'react';
3
+ import {
4
+ Animated,
5
+ I18nManager,
6
+ Image,
7
+ LayoutChangeEvent,
8
+ Platform,
9
+ StyleSheet,
10
+ View,
11
+ } from 'react-native';
12
+
13
+ import MaskedView from '../MaskedView';
14
+ import PlatformPressable from '../PlatformPressable';
15
+ import type { HeaderBackButtonProps } from '../types';
16
+
17
+ export default function HeaderBackButton({
18
+ disabled,
19
+ allowFontScaling,
20
+ backImage,
21
+ label,
22
+ labelStyle,
23
+ labelVisible,
24
+ onLabelLayout,
25
+ onPress,
26
+ pressColor,
27
+ pressOpacity,
28
+ screenLayout,
29
+ tintColor: customTintColor,
30
+ titleLayout,
31
+ truncatedLabel = 'Back',
32
+ accessibilityLabel = label && label !== 'Back' ? `${label}, back` : 'Go back',
33
+ testID,
34
+ style,
35
+ }: HeaderBackButtonProps) {
36
+ const { colors } = useTheme();
37
+
38
+ const [initialLabelWidth, setInitialLabelWidth] =
39
+ React.useState<undefined | number>(undefined);
40
+
41
+ const tintColor =
42
+ customTintColor !== undefined
43
+ ? customTintColor
44
+ : Platform.select({
45
+ ios: colors.primary,
46
+ default: colors.text,
47
+ });
48
+
49
+ const handleLabelLayout = (e: LayoutChangeEvent) => {
50
+ onLabelLayout?.(e);
51
+
52
+ setInitialLabelWidth(e.nativeEvent.layout.x + e.nativeEvent.layout.width);
53
+ };
54
+
55
+ const shouldTruncateLabel = () => {
56
+ return (
57
+ !label ||
58
+ (initialLabelWidth &&
59
+ titleLayout &&
60
+ screenLayout &&
61
+ (screenLayout.width - titleLayout.width) / 2 < initialLabelWidth + 26)
62
+ );
63
+ };
64
+
65
+ const renderBackImage = () => {
66
+ if (backImage) {
67
+ return backImage({ tintColor });
68
+ } else {
69
+ return (
70
+ <Image
71
+ style={[
72
+ styles.icon,
73
+ Boolean(labelVisible) && styles.iconWithLabel,
74
+ Boolean(tintColor) && { tintColor },
75
+ ]}
76
+ source={require('../assets/back-icon.png')}
77
+ fadeDuration={0}
78
+ />
79
+ );
80
+ }
81
+ };
82
+
83
+ const renderLabel = () => {
84
+ const leftLabelText = shouldTruncateLabel() ? truncatedLabel : label;
85
+
86
+ if (!labelVisible || leftLabelText === undefined) {
87
+ return null;
88
+ }
89
+
90
+ const labelElement = (
91
+ <View
92
+ style={
93
+ screenLayout
94
+ ? // We make the button extend till the middle of the screen
95
+ // Otherwise it appears to cut off when translating
96
+ [styles.labelWrapper, { minWidth: screenLayout.width / 2 - 27 }]
97
+ : null
98
+ }
99
+ >
100
+ <Animated.Text
101
+ accessible={false}
102
+ onLayout={
103
+ // This measurement is used to determine if we should truncate the label when it doesn't fit
104
+ // Only measure it when label is not truncated because we want the measurement of full label
105
+ leftLabelText === label ? handleLabelLayout : undefined
106
+ }
107
+ style={[
108
+ styles.label,
109
+ tintColor ? { color: tintColor } : null,
110
+ labelStyle,
111
+ ]}
112
+ numberOfLines={1}
113
+ allowFontScaling={!!allowFontScaling}
114
+ >
115
+ {leftLabelText}
116
+ </Animated.Text>
117
+ </View>
118
+ );
119
+
120
+ if (backImage || Platform.OS !== 'ios') {
121
+ // When a custom backimage is specified, we can't mask the label
122
+ // Otherwise there might be weird effect due to our mask not being the same as the image
123
+ return labelElement;
124
+ }
125
+
126
+ return (
127
+ <MaskedView
128
+ maskElement={
129
+ <View style={styles.iconMaskContainer}>
130
+ <Image
131
+ source={require('../assets/back-icon-mask.png')}
132
+ style={styles.iconMask}
133
+ />
134
+ <View style={styles.iconMaskFillerRect} />
135
+ </View>
136
+ }
137
+ >
138
+ {labelElement}
139
+ </MaskedView>
140
+ );
141
+ };
142
+
143
+ const handlePress = () => onPress && requestAnimationFrame(onPress);
144
+
145
+ return (
146
+ <PlatformPressable
147
+ disabled={disabled}
148
+ accessible
149
+ accessibilityRole="button"
150
+ accessibilityLabel={accessibilityLabel}
151
+ testID={testID}
152
+ onPress={disabled ? undefined : handlePress}
153
+ pressColor={pressColor}
154
+ pressOpacity={pressOpacity}
155
+ android_ripple={{ borderless: true }}
156
+ style={[styles.container, disabled && styles.disabled, style]}
157
+ hitSlop={Platform.select({
158
+ ios: undefined,
159
+ default: { top: 16, right: 16, bottom: 16, left: 16 },
160
+ })}
161
+ >
162
+ <React.Fragment>
163
+ {renderBackImage()}
164
+ {renderLabel()}
165
+ </React.Fragment>
166
+ </PlatformPressable>
167
+ );
168
+ }
169
+
170
+ const styles = StyleSheet.create({
171
+ container: {
172
+ alignItems: 'center',
173
+ flexDirection: 'row',
174
+ minWidth: StyleSheet.hairlineWidth, // Avoid collapsing when title is long
175
+ ...Platform.select({
176
+ ios: null,
177
+ default: {
178
+ marginVertical: 3,
179
+ marginHorizontal: 11,
180
+ },
181
+ }),
182
+ },
183
+ disabled: {
184
+ opacity: 0.5,
185
+ },
186
+ label: {
187
+ fontSize: 17,
188
+ // Title and back label are a bit different width due to title being bold
189
+ // Adjusting the letterSpacing makes them coincide better
190
+ letterSpacing: 0.35,
191
+ },
192
+ labelWrapper: {
193
+ // These styles will make sure that the label doesn't fill the available space
194
+ // Otherwise it messes with the measurement of the label
195
+ flexDirection: 'row',
196
+ alignItems: 'flex-start',
197
+ },
198
+ icon: Platform.select({
199
+ ios: {
200
+ height: 21,
201
+ width: 13,
202
+ marginLeft: 8,
203
+ marginRight: 22,
204
+ marginVertical: 12,
205
+ resizeMode: 'contain',
206
+ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
207
+ },
208
+ default: {
209
+ height: 24,
210
+ width: 24,
211
+ margin: 3,
212
+ resizeMode: 'contain',
213
+ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
214
+ },
215
+ }),
216
+ iconWithLabel:
217
+ Platform.OS === 'ios'
218
+ ? {
219
+ marginRight: 6,
220
+ }
221
+ : {},
222
+ iconMaskContainer: {
223
+ flex: 1,
224
+ flexDirection: 'row',
225
+ justifyContent: 'center',
226
+ },
227
+ iconMaskFillerRect: {
228
+ flex: 1,
229
+ backgroundColor: '#000',
230
+ },
231
+ iconMask: {
232
+ height: 21,
233
+ width: 13,
234
+ marginLeft: -14.5,
235
+ marginVertical: 12,
236
+ alignSelf: 'center',
237
+ resizeMode: 'contain',
238
+ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
239
+ },
240
+ });
@@ -0,0 +1,8 @@
1
+ import getNamedContext from '../getNamedContext';
2
+
3
+ const HeaderBackContext = getNamedContext<{ title: string } | undefined>(
4
+ 'HeaderBackContext',
5
+ undefined
6
+ );
7
+
8
+ export default HeaderBackContext;
@@ -0,0 +1,56 @@
1
+ import { useTheme } from '@react-navigation/native';
2
+ import * as React from 'react';
3
+ import {
4
+ Animated,
5
+ Platform,
6
+ StyleProp,
7
+ StyleSheet,
8
+ ViewProps,
9
+ ViewStyle,
10
+ } from 'react-native';
11
+
12
+ type Props = ViewProps & {
13
+ style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
14
+ children?: React.ReactNode;
15
+ };
16
+
17
+ export default function HeaderBackground({ style, ...rest }: Props) {
18
+ const { colors } = useTheme();
19
+
20
+ return (
21
+ <Animated.View
22
+ style={[
23
+ styles.container,
24
+ {
25
+ backgroundColor: colors.card,
26
+ borderBottomColor: colors.border,
27
+ shadowColor: colors.border,
28
+ },
29
+ style,
30
+ ]}
31
+ {...rest}
32
+ />
33
+ );
34
+ }
35
+
36
+ const styles = StyleSheet.create({
37
+ container: {
38
+ flex: 1,
39
+ ...Platform.select({
40
+ android: {
41
+ elevation: 4,
42
+ },
43
+ ios: {
44
+ shadowOpacity: 0.85,
45
+ shadowRadius: 0,
46
+ shadowOffset: {
47
+ width: 0,
48
+ height: StyleSheet.hairlineWidth,
49
+ },
50
+ },
51
+ default: {
52
+ borderBottomWidth: StyleSheet.hairlineWidth,
53
+ },
54
+ }),
55
+ },
56
+ });
@@ -0,0 +1,8 @@
1
+ import getNamedContext from '../getNamedContext';
2
+
3
+ const HeaderHeightContext = getNamedContext<number | undefined>(
4
+ 'HeaderHeightContext',
5
+ undefined
6
+ );
7
+
8
+ export default HeaderHeightContext;
@@ -0,0 +1,5 @@
1
+ import getNamedContext from '../getNamedContext';
2
+
3
+ const HeaderShownContext = getNamedContext('HeaderShownContext', false);
4
+
5
+ export default HeaderShownContext;
@@ -0,0 +1,52 @@
1
+ import { useTheme } from '@react-navigation/native';
2
+ import * as React from 'react';
3
+ import {
4
+ Animated,
5
+ Platform,
6
+ StyleProp,
7
+ StyleSheet,
8
+ TextProps,
9
+ TextStyle,
10
+ } from 'react-native';
11
+
12
+ type Props = Omit<TextProps, 'style'> & {
13
+ tintColor?: string;
14
+ children?: string;
15
+ style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
16
+ };
17
+
18
+ export default function HeaderTitle({ tintColor, style, ...rest }: Props) {
19
+ const { colors } = useTheme();
20
+
21
+ return (
22
+ <Animated.Text
23
+ accessibilityRole="header"
24
+ aria-level="1"
25
+ numberOfLines={1}
26
+ {...rest}
27
+ style={[
28
+ styles.title,
29
+ { color: tintColor === undefined ? colors.text : tintColor },
30
+ style,
31
+ ]}
32
+ />
33
+ );
34
+ }
35
+
36
+ const styles = StyleSheet.create({
37
+ title: Platform.select({
38
+ ios: {
39
+ fontSize: 17,
40
+ fontWeight: '600',
41
+ },
42
+ android: {
43
+ fontSize: 20,
44
+ fontFamily: 'sans-serif-medium',
45
+ fontWeight: 'normal',
46
+ },
47
+ default: {
48
+ fontSize: 18,
49
+ fontWeight: '500',
50
+ },
51
+ }),
52
+ });
@@ -0,0 +1,39 @@
1
+ import { Platform } from 'react-native';
2
+
3
+ import type { Layout } from '../types';
4
+
5
+ export default function getDefaultHeaderHeight(
6
+ layout: Layout,
7
+ modalPresentation: boolean,
8
+ statusBarHeight: number
9
+ ): number {
10
+ let headerHeight;
11
+
12
+ const isLandscape = layout.width > layout.height;
13
+
14
+ if (Platform.OS === 'ios') {
15
+ if (Platform.isPad) {
16
+ if (modalPresentation) {
17
+ headerHeight = 56;
18
+ } else {
19
+ headerHeight = 50;
20
+ }
21
+ } else {
22
+ if (isLandscape) {
23
+ headerHeight = 32;
24
+ } else {
25
+ if (modalPresentation) {
26
+ headerHeight = 56;
27
+ } else {
28
+ headerHeight = 44;
29
+ }
30
+ }
31
+ }
32
+ } else if (Platform.OS === 'android') {
33
+ headerHeight = 56;
34
+ } else {
35
+ headerHeight = 64;
36
+ }
37
+
38
+ return headerHeight + statusBarHeight;
39
+ }
@@ -0,0 +1,12 @@
1
+ import type { HeaderOptions } from '../types';
2
+
3
+ export default function getHeaderTitle(
4
+ options: { title?: string; headerTitle?: HeaderOptions['headerTitle'] },
5
+ fallback: string
6
+ ): string {
7
+ return typeof options.headerTitle === 'string'
8
+ ? options.headerTitle
9
+ : options.title !== undefined
10
+ ? options.title
11
+ : fallback;
12
+ }
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+
3
+ import HeaderHeightContext from './HeaderHeightContext';
4
+
5
+ export default function useHeaderHeight() {
6
+ const height = React.useContext(HeaderHeightContext);
7
+
8
+ if (height === undefined) {
9
+ throw new Error(
10
+ "Couldn't find the header height. Are you inside a screen in a navigator with a header?"
11
+ );
12
+ }
13
+
14
+ return height;
15
+ }
@@ -0,0 +1 @@
1
+ export { default } from './MaskedViewNative';
@@ -0,0 +1 @@
1
+ export { default } from './MaskedViewNative';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Use a stub for MaskedView on all Platforms that don't support it.
3
+ */
4
+ import type * as React from 'react';
5
+
6
+ type Props = {
7
+ maskElement: React.ReactElement;
8
+ children: React.ReactElement;
9
+ };
10
+
11
+ export default function MaskedView({ children }: Props) {
12
+ return children;
13
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The native MaskedView that we explicitly re-export for supported platforms: Android, iOS.
3
+ */
4
+ import * as React from 'react';
5
+ import { UIManager } from 'react-native';
6
+
7
+ type MaskedViewType =
8
+ typeof import('@react-native-masked-view/masked-view').default;
9
+
10
+ type Props = React.ComponentProps<MaskedViewType> & {
11
+ children: React.ReactElement;
12
+ };
13
+
14
+ let RNCMaskedView: MaskedViewType | undefined;
15
+
16
+ try {
17
+ // Add try/catch to support usage even if it's not installed, since it's optional.
18
+ // Newer versions of Metro will handle it properly.
19
+ RNCMaskedView = require('@react-native-masked-view/masked-view').default;
20
+ } catch (e) {
21
+ // Ignore
22
+ }
23
+
24
+ const isMaskedViewAvailable =
25
+ UIManager.getViewManagerConfig('RNCMaskedView') != null;
26
+
27
+ export default function MaskedView({ children, ...rest }: Props) {
28
+ if (isMaskedViewAvailable && RNCMaskedView) {
29
+ return <RNCMaskedView {...rest}>{children}</RNCMaskedView>;
30
+ }
31
+
32
+ return children;
33
+ }
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ import { StyleProp, StyleSheet, Text, TextStyle } from 'react-native';
3
+
4
+ type Props = {
5
+ color?: string;
6
+ size?: number;
7
+ style?: StyleProp<TextStyle>;
8
+ };
9
+
10
+ export default function MissingIcon({ color, size, style }: Props) {
11
+ return <Text style={[styles.icon, { color, fontSize: size }, style]}>⏷</Text>;
12
+ }
13
+
14
+ const styles = StyleSheet.create({
15
+ icon: {
16
+ backgroundColor: 'transparent',
17
+ },
18
+ });
@@ -0,0 +1,86 @@
1
+ import { useTheme } from '@react-navigation/native';
2
+ import * as React from 'react';
3
+ import {
4
+ Animated,
5
+ Easing,
6
+ GestureResponderEvent,
7
+ Platform,
8
+ Pressable,
9
+ PressableProps,
10
+ StyleProp,
11
+ ViewStyle,
12
+ } from 'react-native';
13
+
14
+ export type Props = Omit<PressableProps, 'style'> & {
15
+ pressColor?: string;
16
+ pressOpacity?: number;
17
+ style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
18
+ children: React.ReactNode;
19
+ };
20
+
21
+ const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
22
+
23
+ const ANDROID_VERSION_LOLLIPOP = 21;
24
+ const ANDROID_SUPPORTS_RIPPLE =
25
+ Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_LOLLIPOP;
26
+
27
+ /**
28
+ * PlatformPressable provides an abstraction on top of Pressable to handle platform differences.
29
+ */
30
+ export default function PlatformPressable({
31
+ onPressIn,
32
+ onPressOut,
33
+ android_ripple,
34
+ pressColor,
35
+ pressOpacity = 0.3,
36
+ style,
37
+ ...rest
38
+ }: Props) {
39
+ const { dark } = useTheme();
40
+ const [opacity] = React.useState(() => new Animated.Value(1));
41
+
42
+ const animateTo = (toValue: number, duration: number) => {
43
+ if (ANDROID_SUPPORTS_RIPPLE) {
44
+ return;
45
+ }
46
+
47
+ Animated.timing(opacity, {
48
+ toValue,
49
+ duration,
50
+ easing: Easing.inOut(Easing.quad),
51
+ useNativeDriver: true,
52
+ }).start();
53
+ };
54
+
55
+ const handlePressIn = (e: GestureResponderEvent) => {
56
+ animateTo(pressOpacity, 0);
57
+ onPressIn?.(e);
58
+ };
59
+
60
+ const handlePressOut = (e: GestureResponderEvent) => {
61
+ animateTo(1, 200);
62
+ onPressOut?.(e);
63
+ };
64
+
65
+ return (
66
+ <AnimatedPressable
67
+ onPressIn={handlePressIn}
68
+ onPressOut={handlePressOut}
69
+ android_ripple={
70
+ ANDROID_SUPPORTS_RIPPLE
71
+ ? {
72
+ color:
73
+ pressColor !== undefined
74
+ ? pressColor
75
+ : dark
76
+ ? 'rgba(255, 255, 255, .32)'
77
+ : 'rgba(0, 0, 0, .32)',
78
+ ...android_ripple,
79
+ }
80
+ : undefined
81
+ }
82
+ style={[{ opacity: !ANDROID_SUPPORTS_RIPPLE ? opacity : 1 }, style]}
83
+ {...rest}
84
+ />
85
+ );
86
+ }
@@ -0,0 +1,70 @@
1
+ import * as React from 'react';
2
+ import { Platform, StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
3
+
4
+ type Props = {
5
+ visible: boolean;
6
+ children: React.ReactNode;
7
+ style?: StyleProp<ViewStyle>;
8
+ };
9
+
10
+ const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
11
+
12
+ export default function ResourceSavingScene({
13
+ visible,
14
+ children,
15
+ style,
16
+ ...rest
17
+ }: Props) {
18
+ if (Platform.OS === 'web') {
19
+ return (
20
+ <View
21
+ // @ts-expect-error: hidden exists on web, but not in React Native
22
+ hidden={!visible}
23
+ style={[
24
+ { display: visible ? 'flex' : 'none' },
25
+ styles.container,
26
+ style,
27
+ ]}
28
+ pointerEvents={visible ? 'auto' : 'none'}
29
+ {...rest}
30
+ >
31
+ {children}
32
+ </View>
33
+ );
34
+ }
35
+
36
+ return (
37
+ <View
38
+ style={[styles.container, style]}
39
+ // box-none doesn't seem to work properly on Android
40
+ pointerEvents={visible ? 'auto' : 'none'}
41
+ >
42
+ <View
43
+ collapsable={false}
44
+ removeClippedSubviews={
45
+ // On iOS & macOS, set removeClippedSubviews to true only when not focused
46
+ // This is an workaround for a bug where the clipped view never re-appears
47
+ Platform.OS === 'ios' || Platform.OS === 'macos' ? !visible : true
48
+ }
49
+ pointerEvents={visible ? 'auto' : 'none'}
50
+ style={visible ? styles.attached : styles.detached}
51
+ >
52
+ {children}
53
+ </View>
54
+ </View>
55
+ );
56
+ }
57
+
58
+ const styles = StyleSheet.create({
59
+ container: {
60
+ flex: 1,
61
+ overflow: 'hidden',
62
+ },
63
+ attached: {
64
+ flex: 1,
65
+ },
66
+ detached: {
67
+ flex: 1,
68
+ top: FAR_FAR_AWAY,
69
+ },
70
+ });