@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.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/lib/commonjs/Background.js +34 -0
- package/lib/commonjs/Background.js.map +1 -0
- package/lib/commonjs/Header/Header.js +250 -0
- package/lib/commonjs/Header/Header.js.map +1 -0
- package/lib/commonjs/Header/HeaderBackButton.js +222 -0
- package/lib/commonjs/Header/HeaderBackButton.js.map +1 -0
- package/lib/commonjs/Header/HeaderBackContext.js +15 -0
- package/lib/commonjs/Header/HeaderBackContext.js.map +1 -0
- package/lib/commonjs/Header/HeaderBackground.js +57 -0
- package/lib/commonjs/Header/HeaderBackground.js.map +1 -0
- package/lib/commonjs/Header/HeaderHeightContext.js +15 -0
- package/lib/commonjs/Header/HeaderHeightContext.js.map +1 -0
- package/lib/commonjs/Header/HeaderShownContext.js +15 -0
- package/lib/commonjs/Header/HeaderShownContext.js.map +1 -0
- package/lib/commonjs/Header/HeaderTitle.js +56 -0
- package/lib/commonjs/Header/HeaderTitle.js.map +1 -0
- package/lib/commonjs/Header/getDefaultHeaderHeight.js +40 -0
- package/lib/commonjs/Header/getDefaultHeaderHeight.js.map +1 -0
- package/lib/commonjs/Header/getHeaderTitle.js +11 -0
- package/lib/commonjs/Header/getHeaderTitle.js.map +1 -0
- package/lib/commonjs/Header/useHeaderHeight.js +27 -0
- package/lib/commonjs/Header/useHeaderHeight.js.map +1 -0
- package/lib/commonjs/MaskedView.android.js +16 -0
- package/lib/commonjs/MaskedView.android.js.map +1 -0
- package/lib/commonjs/MaskedView.ios.js +16 -0
- package/lib/commonjs/MaskedView.ios.js.map +1 -0
- package/lib/commonjs/MaskedView.js +16 -0
- package/lib/commonjs/MaskedView.js.map +1 -0
- package/lib/commonjs/MaskedViewNative.js +40 -0
- package/lib/commonjs/MaskedViewNative.js.map +1 -0
- package/lib/commonjs/MissingIcon.js +34 -0
- package/lib/commonjs/MissingIcon.js.map +1 -0
- package/lib/commonjs/PlatformPressable.js +77 -0
- package/lib/commonjs/PlatformPressable.js.map +1 -0
- package/lib/commonjs/ResourceSavingView.js +64 -0
- package/lib/commonjs/ResourceSavingView.js.map +1 -0
- package/lib/commonjs/SafeAreaProviderCompat.js +69 -0
- package/lib/commonjs/SafeAreaProviderCompat.js.map +1 -0
- package/lib/commonjs/Screen.js +89 -0
- package/lib/commonjs/Screen.js.map +1 -0
- package/lib/commonjs/assets/back-icon-mask.png +0 -0
- package/lib/commonjs/assets/back-icon.png +0 -0
- package/lib/commonjs/assets/back-icon@1.5x.android.png +0 -0
- package/lib/commonjs/assets/back-icon@1.5x.ios.png +0 -0
- package/lib/commonjs/assets/back-icon@1x.android.png +0 -0
- package/lib/commonjs/assets/back-icon@1x.ios.png +0 -0
- package/lib/commonjs/assets/back-icon@2x.android.png +0 -0
- package/lib/commonjs/assets/back-icon@2x.ios.png +0 -0
- package/lib/commonjs/assets/back-icon@3x.android.png +0 -0
- package/lib/commonjs/assets/back-icon@3x.ios.png +0 -0
- package/lib/commonjs/assets/back-icon@4x.android.png +0 -0
- package/lib/commonjs/assets/back-icon@4x.ios.png +0 -0
- package/lib/commonjs/getNamedContext.js +32 -0
- package/lib/commonjs/getNamedContext.js.map +1 -0
- package/lib/commonjs/index.js +175 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/Background.js +20 -0
- package/lib/module/Background.js.map +1 -0
- package/lib/module/Header/Header.js +229 -0
- package/lib/module/Header/Header.js.map +1 -0
- package/lib/module/Header/HeaderBackButton.js +203 -0
- package/lib/module/Header/HeaderBackButton.js.map +1 -0
- package/lib/module/Header/HeaderBackContext.js +4 -0
- package/lib/module/Header/HeaderBackContext.js.map +1 -0
- package/lib/module/Header/HeaderBackground.js +42 -0
- package/lib/module/Header/HeaderBackground.js.map +1 -0
- package/lib/module/Header/HeaderHeightContext.js +4 -0
- package/lib/module/Header/HeaderHeightContext.js.map +1 -0
- package/lib/module/Header/HeaderShownContext.js +4 -0
- package/lib/module/Header/HeaderShownContext.js.map +1 -0
- package/lib/module/Header/HeaderTitle.js +41 -0
- package/lib/module/Header/HeaderTitle.js.map +1 -0
- package/lib/module/Header/getDefaultHeaderHeight.js +32 -0
- package/lib/module/Header/getDefaultHeaderHeight.js.map +1 -0
- package/lib/module/Header/getHeaderTitle.js +4 -0
- package/lib/module/Header/getHeaderTitle.js.map +1 -0
- package/lib/module/Header/useHeaderHeight.js +12 -0
- package/lib/module/Header/useHeaderHeight.js.map +1 -0
- package/lib/module/MaskedView.android.js +2 -0
- package/lib/module/MaskedView.android.js.map +1 -0
- package/lib/module/MaskedView.ios.js +2 -0
- package/lib/module/MaskedView.ios.js.map +1 -0
- package/lib/module/MaskedView.js +9 -0
- package/lib/module/MaskedView.js.map +1 -0
- package/lib/module/MaskedViewNative.js +26 -0
- package/lib/module/MaskedViewNative.js.map +1 -0
- package/lib/module/MissingIcon.js +20 -0
- package/lib/module/MissingIcon.js.map +1 -0
- package/lib/module/PlatformPressable.js +62 -0
- package/lib/module/PlatformPressable.js.map +1 -0
- package/lib/module/ResourceSavingView.js +50 -0
- package/lib/module/ResourceSavingView.js.map +1 -0
- package/lib/module/SafeAreaProviderCompat.js +51 -0
- package/lib/module/SafeAreaProviderCompat.js.map +1 -0
- package/lib/module/Screen.js +67 -0
- package/lib/module/Screen.js.map +1 -0
- package/lib/module/assets/back-icon-mask.png +0 -0
- package/lib/module/assets/back-icon.png +0 -0
- package/lib/module/assets/back-icon@1.5x.android.png +0 -0
- package/lib/module/assets/back-icon@1.5x.ios.png +0 -0
- package/lib/module/assets/back-icon@1x.android.png +0 -0
- package/lib/module/assets/back-icon@1x.ios.png +0 -0
- package/lib/module/assets/back-icon@2x.android.png +0 -0
- package/lib/module/assets/back-icon@2x.ios.png +0 -0
- package/lib/module/assets/back-icon@3x.android.png +0 -0
- package/lib/module/assets/back-icon@3x.ios.png +0 -0
- package/lib/module/assets/back-icon@4x.android.png +0 -0
- package/lib/module/assets/back-icon@4x.ios.png +0 -0
- package/lib/module/getNamedContext.js +19 -0
- package/lib/module/getNamedContext.js.map +1 -0
- package/lib/module/index.js +21 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/src/Background.d.ts +7 -0
- package/lib/typescript/src/Header/Header.d.ts +18 -0
- package/lib/typescript/src/Header/HeaderBackButton.d.ts +3 -0
- package/lib/typescript/src/Header/HeaderBackContext.d.ts +5 -0
- package/lib/typescript/src/Header/HeaderBackground.d.ts +8 -0
- package/lib/typescript/src/Header/HeaderHeightContext.d.ts +3 -0
- package/lib/typescript/src/Header/HeaderShownContext.d.ts +3 -0
- package/lib/typescript/src/Header/HeaderTitle.d.ts +9 -0
- package/lib/typescript/src/Header/getDefaultHeaderHeight.d.ts +2 -0
- package/lib/typescript/src/Header/getHeaderTitle.d.ts +5 -0
- package/lib/typescript/src/Header/useHeaderHeight.d.ts +1 -0
- package/lib/typescript/src/MaskedView.android.d.ts +1 -0
- package/lib/typescript/src/MaskedView.d.ts +10 -0
- package/lib/typescript/src/MaskedView.ios.d.ts +1 -0
- package/lib/typescript/src/MaskedViewNative.d.ts +10 -0
- package/lib/typescript/src/MissingIcon.d.ts +9 -0
- package/lib/typescript/src/PlatformPressable.d.ts +12 -0
- package/lib/typescript/src/ResourceSavingView.d.ts +9 -0
- package/lib/typescript/src/SafeAreaProviderCompat.d.ts +11 -0
- package/lib/typescript/src/Screen.d.ts +17 -0
- package/lib/typescript/src/getNamedContext.d.ts +5 -0
- package/lib/typescript/src/index.d.ts +18 -0
- package/lib/typescript/src/types.d.ts +202 -0
- package/package.json +72 -0
- package/src/Background.tsx +18 -0
- package/src/Header/Header.tsx +306 -0
- package/src/Header/HeaderBackButton.tsx +240 -0
- package/src/Header/HeaderBackContext.tsx +8 -0
- package/src/Header/HeaderBackground.tsx +56 -0
- package/src/Header/HeaderHeightContext.tsx +8 -0
- package/src/Header/HeaderShownContext.tsx +5 -0
- package/src/Header/HeaderTitle.tsx +52 -0
- package/src/Header/getDefaultHeaderHeight.tsx +39 -0
- package/src/Header/getHeaderTitle.tsx +12 -0
- package/src/Header/useHeaderHeight.tsx +15 -0
- package/src/MaskedView.android.tsx +1 -0
- package/src/MaskedView.ios.tsx +1 -0
- package/src/MaskedView.tsx +13 -0
- package/src/MaskedViewNative.tsx +33 -0
- package/src/MissingIcon.tsx +18 -0
- package/src/PlatformPressable.tsx +86 -0
- package/src/ResourceSavingView.tsx +70 -0
- package/src/SafeAreaProviderCompat.tsx +61 -0
- package/src/Screen.tsx +109 -0
- package/src/assets/back-icon-mask.png +0 -0
- package/src/assets/back-icon.png +0 -0
- package/src/assets/back-icon@1.5x.android.png +0 -0
- package/src/assets/back-icon@1.5x.ios.png +0 -0
- package/src/assets/back-icon@1x.android.png +0 -0
- package/src/assets/back-icon@1x.ios.png +0 -0
- package/src/assets/back-icon@2x.android.png +0 -0
- package/src/assets/back-icon@2x.ios.png +0 -0
- package/src/assets/back-icon@3x.android.png +0 -0
- package/src/assets/back-icon@3x.ios.png +0 -0
- package/src/assets/back-icon@4x.android.png +0 -0
- package/src/assets/back-icon@4x.ios.png +0 -0
- package/src/getNamedContext.tsx +28 -0
- package/src/index.tsx +25 -0
- 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,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,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
|
+
});
|