@react-native-ohos/elements 2.3.9-rc.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.OpenSource +11 -0
- package/README.md +9 -0
- package/lib/module/Background.js +22 -0
- package/lib/module/Background.js.map +1 -0
- package/lib/module/Button.js +106 -0
- package/lib/module/Button.js.map +1 -0
- package/lib/module/Header/Header.js +338 -0
- package/lib/module/Header/Header.js.map +1 -0
- package/lib/module/Header/HeaderBackButton.js +188 -0
- package/lib/module/Header/HeaderBackButton.js.map +1 -0
- package/lib/module/Header/HeaderBackContext.js +5 -0
- package/lib/module/Header/HeaderBackContext.js.map +1 -0
- package/lib/module/Header/HeaderBackground.js +47 -0
- package/lib/module/Header/HeaderBackground.js.map +1 -0
- package/lib/module/Header/HeaderButton.js +56 -0
- package/lib/module/Header/HeaderButton.js.map +1 -0
- package/lib/module/Header/HeaderHeightContext.js +5 -0
- package/lib/module/Header/HeaderHeightContext.js.map +1 -0
- package/lib/module/Header/HeaderIcon.js +38 -0
- package/lib/module/Header/HeaderIcon.js.map +1 -0
- package/lib/module/Header/HeaderSearchBar.js +288 -0
- package/lib/module/Header/HeaderSearchBar.js.map +1 -0
- package/lib/module/Header/HeaderShownContext.js +5 -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 +45 -0
- package/lib/module/Header/getDefaultHeaderHeight.js.map +1 -0
- package/lib/module/Header/getHeaderTitle.js +6 -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/Label/Label.js +25 -0
- package/lib/module/Label/Label.js.map +1 -0
- package/lib/module/Label/getLabel.js +6 -0
- package/lib/module/Label/getLabel.js.map +1 -0
- package/lib/module/MaskedView.android.js +4 -0
- package/lib/module/MaskedView.android.js.map +1 -0
- package/lib/module/MaskedView.ios.js +4 -0
- package/lib/module/MaskedView.ios.js.map +1 -0
- package/lib/module/MaskedView.js +12 -0
- package/lib/module/MaskedView.js.map +1 -0
- package/lib/module/MaskedViewNative.js +30 -0
- package/lib/module/MaskedViewNative.js.map +1 -0
- package/lib/module/MissingIcon.js +24 -0
- package/lib/module/MissingIcon.js.map +1 -0
- package/lib/module/PlatformPressable.js +141 -0
- package/lib/module/PlatformPressable.js.map +1 -0
- package/lib/module/ResourceSavingView.js +57 -0
- package/lib/module/ResourceSavingView.js.map +1 -0
- package/lib/module/SafeAreaProviderCompat.js +58 -0
- package/lib/module/SafeAreaProviderCompat.js.map +1 -0
- package/lib/module/Screen.js +83 -0
- package/lib/module/Screen.js.map +1 -0
- package/lib/module/Text.js +22 -0
- package/lib/module/Text.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@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/assets/clear-icon.png +0 -0
- package/lib/module/assets/clear-icon@1x.png +0 -0
- package/lib/module/assets/clear-icon@2x.png +0 -0
- package/lib/module/assets/clear-icon@3x.png +0 -0
- package/lib/module/assets/clear-icon@4x.png +0 -0
- package/lib/module/assets/close-icon.png +0 -0
- package/lib/module/assets/close-icon@1x.png +0 -0
- package/lib/module/assets/close-icon@2x.png +0 -0
- package/lib/module/assets/close-icon@3x.png +0 -0
- package/lib/module/assets/close-icon@4x.png +0 -0
- package/lib/module/assets/search-icon.png +0 -0
- package/lib/module/assets/search-icon@1x.android.png +0 -0
- package/lib/module/assets/search-icon@1x.ios.png +0 -0
- package/lib/module/assets/search-icon@2x.android.png +0 -0
- package/lib/module/assets/search-icon@2x.ios.png +0 -0
- package/lib/module/assets/search-icon@3x.android.png +0 -0
- package/lib/module/assets/search-icon@3x.ios.png +0 -0
- package/lib/module/assets/search-icon@4x.android.png +0 -0
- package/lib/module/assets/search-icon@4x.ios.png +0 -0
- package/lib/module/getDefaultSidebarWidth.js +18 -0
- package/lib/module/getDefaultSidebarWidth.js.map +1 -0
- package/lib/module/getNamedContext.js +17 -0
- package/lib/module/getNamedContext.js.map +1 -0
- package/lib/module/index.js +32 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/Background.d.ts +9 -0
- package/lib/typescript/src/Background.d.ts.map +1 -0
- package/lib/typescript/src/Button.d.ts +13 -0
- package/lib/typescript/src/Button.d.ts.map +1 -0
- package/lib/typescript/src/Header/Header.d.ts +31 -0
- package/lib/typescript/src/Header/Header.d.ts.map +1 -0
- package/lib/typescript/src/Header/HeaderBackButton.d.ts +3 -0
- package/lib/typescript/src/Header/HeaderBackButton.d.ts.map +1 -0
- package/lib/typescript/src/Header/HeaderBackContext.d.ts +5 -0
- package/lib/typescript/src/Header/HeaderBackContext.d.ts.map +1 -0
- package/lib/typescript/src/Header/HeaderBackground.d.ts +9 -0
- package/lib/typescript/src/Header/HeaderBackground.d.ts.map +1 -0
- package/lib/typescript/src/Header/HeaderButton.d.ts +3 -0
- package/lib/typescript/src/Header/HeaderButton.d.ts.map +1 -0
- package/lib/typescript/src/Header/HeaderHeightContext.d.ts +2 -0
- package/lib/typescript/src/Header/HeaderHeightContext.d.ts.map +1 -0
- package/lib/typescript/src/Header/HeaderIcon.d.ts +5 -0
- package/lib/typescript/src/Header/HeaderIcon.d.ts.map +1 -0
- package/lib/typescript/src/Header/HeaderSearchBar.d.ts +10 -0
- package/lib/typescript/src/Header/HeaderSearchBar.d.ts.map +1 -0
- package/lib/typescript/src/Header/HeaderShownContext.d.ts +2 -0
- package/lib/typescript/src/Header/HeaderShownContext.d.ts.map +1 -0
- package/lib/typescript/src/Header/HeaderTitle.d.ts +9 -0
- package/lib/typescript/src/Header/HeaderTitle.d.ts.map +1 -0
- package/lib/typescript/src/Header/getDefaultHeaderHeight.d.ts +3 -0
- package/lib/typescript/src/Header/getDefaultHeaderHeight.d.ts.map +1 -0
- package/lib/typescript/src/Header/getHeaderTitle.d.ts +6 -0
- package/lib/typescript/src/Header/getHeaderTitle.d.ts.map +1 -0
- package/lib/typescript/src/Header/useHeaderHeight.d.ts +2 -0
- package/lib/typescript/src/Header/useHeaderHeight.d.ts.map +1 -0
- package/lib/typescript/src/Label/Label.d.ts +9 -0
- package/lib/typescript/src/Label/Label.d.ts.map +1 -0
- package/lib/typescript/src/Label/getLabel.d.ts +5 -0
- package/lib/typescript/src/Label/getLabel.d.ts.map +1 -0
- package/lib/typescript/src/MaskedView.android.d.ts +2 -0
- package/lib/typescript/src/MaskedView.android.d.ts.map +1 -0
- package/lib/typescript/src/MaskedView.d.ts +11 -0
- package/lib/typescript/src/MaskedView.d.ts.map +1 -0
- package/lib/typescript/src/MaskedView.ios.d.ts +2 -0
- package/lib/typescript/src/MaskedView.ios.d.ts.map +1 -0
- package/lib/typescript/src/MaskedViewNative.d.ts +11 -0
- package/lib/typescript/src/MaskedViewNative.d.ts.map +1 -0
- package/lib/typescript/src/MissingIcon.d.ts +9 -0
- package/lib/typescript/src/MissingIcon.d.ts.map +1 -0
- package/lib/typescript/src/PlatformPressable.d.ts +21 -0
- package/lib/typescript/src/PlatformPressable.d.ts.map +1 -0
- package/lib/typescript/src/ResourceSavingView.d.ts +10 -0
- package/lib/typescript/src/ResourceSavingView.d.ts.map +1 -0
- package/lib/typescript/src/SafeAreaProviderCompat.d.ts +12 -0
- package/lib/typescript/src/SafeAreaProviderCompat.d.ts.map +1 -0
- package/lib/typescript/src/Screen.d.ts +18 -0
- package/lib/typescript/src/Screen.d.ts.map +1 -0
- package/lib/typescript/src/Text.d.ts +3 -0
- package/lib/typescript/src/Text.d.ts.map +1 -0
- package/lib/typescript/src/__tests__/PlatformPressable.test.d.ts +2 -0
- package/lib/typescript/src/__tests__/PlatformPressable.test.d.ts.map +1 -0
- package/lib/typescript/src/getDefaultSidebarWidth.d.ts +4 -0
- package/lib/typescript/src/getDefaultSidebarWidth.d.ts.map +1 -0
- package/lib/typescript/src/getNamedContext.d.ts +6 -0
- package/lib/typescript/src/getNamedContext.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +25 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +325 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +83 -0
- package/src/Background.tsx +24 -0
- package/src/Button.tsx +120 -0
- package/src/Header/Header.tsx +450 -0
- package/src/Header/HeaderBackButton.tsx +249 -0
- package/src/Header/HeaderBackContext.tsx +5 -0
- package/src/Header/HeaderBackground.tsx +60 -0
- package/src/Header/HeaderButton.tsx +55 -0
- package/src/Header/HeaderHeightContext.tsx +6 -0
- package/src/Header/HeaderIcon.tsx +32 -0
- package/src/Header/HeaderSearchBar.tsx +323 -0
- package/src/Header/HeaderShownContext.tsx +3 -0
- package/src/Header/HeaderTitle.tsx +48 -0
- package/src/Header/getDefaultHeaderHeight.tsx +54 -0
- package/src/Header/getHeaderTitle.tsx +12 -0
- package/src/Header/useHeaderHeight.tsx +15 -0
- package/src/Label/Label.tsx +31 -0
- package/src/Label/getLabel.tsx +10 -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 +19 -0
- package/src/PlatformPressable.tsx +196 -0
- package/src/ResourceSavingView.tsx +76 -0
- package/src/SafeAreaProviderCompat.tsx +61 -0
- package/src/Screen.tsx +123 -0
- package/src/Text.tsx +14 -0
- package/src/__tests__/PlatformPressable.test.tsx +81 -0
- package/src/assets/back-icon-mask.png +0 -0
- package/src/assets/back-icon.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/assets/clear-icon.png +0 -0
- package/src/assets/clear-icon@1x.png +0 -0
- package/src/assets/clear-icon@2x.png +0 -0
- package/src/assets/clear-icon@3x.png +0 -0
- package/src/assets/clear-icon@4x.png +0 -0
- package/src/assets/close-icon.png +0 -0
- package/src/assets/close-icon@1x.png +0 -0
- package/src/assets/close-icon@2x.png +0 -0
- package/src/assets/close-icon@3x.png +0 -0
- package/src/assets/close-icon@4x.png +0 -0
- package/src/assets/search-icon.png +0 -0
- package/src/assets/search-icon@1x.android.png +0 -0
- package/src/assets/search-icon@1x.ios.png +0 -0
- package/src/assets/search-icon@2x.android.png +0 -0
- package/src/assets/search-icon@2x.ios.png +0 -0
- package/src/assets/search-icon@3x.android.png +0 -0
- package/src/assets/search-icon@3x.ios.png +0 -0
- package/src/assets/search-icon@4x.android.png +0 -0
- package/src/assets/search-icon@4x.ios.png +0 -0
- package/src/getDefaultSidebarWidth.tsx +15 -0
- package/src/getNamedContext.tsx +30 -0
- package/src/index.tsx +38 -0
- package/src/types.tsx +338 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { useTheme } from '@react-navigation/native';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Animated,
|
|
5
|
+
Easing,
|
|
6
|
+
type GestureResponderEvent,
|
|
7
|
+
Platform,
|
|
8
|
+
Pressable,
|
|
9
|
+
type PressableProps,
|
|
10
|
+
type StyleProp,
|
|
11
|
+
type ViewStyle,
|
|
12
|
+
} from 'react-native';
|
|
13
|
+
|
|
14
|
+
type HoverEffectProps = {
|
|
15
|
+
color?: string;
|
|
16
|
+
hoverOpacity?: number;
|
|
17
|
+
activeOpacity?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type Props = Omit<PressableProps, 'style'> & {
|
|
21
|
+
pressColor?: string;
|
|
22
|
+
pressOpacity?: number;
|
|
23
|
+
hoverEffect?: HoverEffectProps;
|
|
24
|
+
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
|
25
|
+
href?: string;
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
|
30
|
+
|
|
31
|
+
const ANDROID_VERSION_LOLLIPOP = 21;
|
|
32
|
+
const ANDROID_SUPPORTS_RIPPLE =
|
|
33
|
+
Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_LOLLIPOP;
|
|
34
|
+
|
|
35
|
+
const useNativeDriver = Platform.OS !== 'web';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* PlatformPressable provides an abstraction on top of Pressable to handle platform differences.
|
|
39
|
+
*/
|
|
40
|
+
export function PlatformPressable({
|
|
41
|
+
disabled,
|
|
42
|
+
onPress,
|
|
43
|
+
onPressIn,
|
|
44
|
+
onPressOut,
|
|
45
|
+
android_ripple,
|
|
46
|
+
pressColor,
|
|
47
|
+
pressOpacity = 0.3,
|
|
48
|
+
hoverEffect,
|
|
49
|
+
style,
|
|
50
|
+
children,
|
|
51
|
+
...rest
|
|
52
|
+
}: Props) {
|
|
53
|
+
const { dark } = useTheme();
|
|
54
|
+
const [opacity] = React.useState(() => new Animated.Value(1));
|
|
55
|
+
|
|
56
|
+
const animateTo = (toValue: number, duration: number) => {
|
|
57
|
+
if (ANDROID_SUPPORTS_RIPPLE) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Animated.timing(opacity, {
|
|
62
|
+
toValue,
|
|
63
|
+
duration,
|
|
64
|
+
easing: Easing.inOut(Easing.quad),
|
|
65
|
+
useNativeDriver,
|
|
66
|
+
}).start();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handlePress = (e: GestureResponderEvent) => {
|
|
70
|
+
if (Platform.OS === 'web' && rest.href != null) {
|
|
71
|
+
// @ts-expect-error: these properties exist on web, but not in React Native
|
|
72
|
+
const hasModifierKey = e.metaKey || e.altKey || e.ctrlKey || e.shiftKey; // ignore clicks with modifier keys
|
|
73
|
+
// @ts-expect-error: these properties exist on web, but not in React Native
|
|
74
|
+
const isLeftClick = e.button == null || e.button === 0; // only handle left clicks
|
|
75
|
+
const isSelfTarget = [undefined, null, '', 'self'].includes(
|
|
76
|
+
// @ts-expect-error: these properties exist on web, but not in React Native
|
|
77
|
+
e.currentTarget?.target
|
|
78
|
+
); // let browser handle "target=_blank" etc.
|
|
79
|
+
if (!hasModifierKey && isLeftClick && isSelfTarget) {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
onPress?.(e);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
onPress?.(e);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handlePressIn = (e: GestureResponderEvent) => {
|
|
89
|
+
animateTo(pressOpacity, 0);
|
|
90
|
+
onPressIn?.(e);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handlePressOut = (e: GestureResponderEvent) => {
|
|
94
|
+
animateTo(1, 200);
|
|
95
|
+
onPressOut?.(e);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<AnimatedPressable
|
|
100
|
+
accessible
|
|
101
|
+
accessibilityRole={
|
|
102
|
+
Platform.OS === 'web' && rest.href != null ? 'link' : 'button'
|
|
103
|
+
}
|
|
104
|
+
onPress={disabled ? undefined : handlePress}
|
|
105
|
+
onPressIn={handlePressIn}
|
|
106
|
+
onPressOut={handlePressOut}
|
|
107
|
+
android_ripple={
|
|
108
|
+
ANDROID_SUPPORTS_RIPPLE
|
|
109
|
+
? {
|
|
110
|
+
color:
|
|
111
|
+
pressColor !== undefined
|
|
112
|
+
? pressColor
|
|
113
|
+
: dark
|
|
114
|
+
? 'rgba(255, 255, 255, .32)'
|
|
115
|
+
: 'rgba(0, 0, 0, .32)',
|
|
116
|
+
...android_ripple,
|
|
117
|
+
}
|
|
118
|
+
: undefined
|
|
119
|
+
}
|
|
120
|
+
style={[
|
|
121
|
+
{
|
|
122
|
+
cursor:
|
|
123
|
+
Platform.OS === 'web' || Platform.OS === 'ios'
|
|
124
|
+
? // Pointer cursor on web
|
|
125
|
+
// Hover effect on iPad and visionOS
|
|
126
|
+
'pointer'
|
|
127
|
+
: 'auto',
|
|
128
|
+
opacity: !ANDROID_SUPPORTS_RIPPLE ? opacity : 1,
|
|
129
|
+
},
|
|
130
|
+
style,
|
|
131
|
+
]}
|
|
132
|
+
{...rest}
|
|
133
|
+
>
|
|
134
|
+
<HoverEffect {...hoverEffect} />
|
|
135
|
+
{children}
|
|
136
|
+
</AnimatedPressable>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const css = String.raw;
|
|
141
|
+
|
|
142
|
+
const CLASS_NAME = `__react-navigation_elements_Pressable_hover`;
|
|
143
|
+
|
|
144
|
+
const CSS_TEXT = css`
|
|
145
|
+
.${CLASS_NAME} {
|
|
146
|
+
position: absolute;
|
|
147
|
+
top: 0;
|
|
148
|
+
left: 0;
|
|
149
|
+
right: 0;
|
|
150
|
+
bottom: 0;
|
|
151
|
+
border-radius: inherit;
|
|
152
|
+
background-color: var(--overlay-color);
|
|
153
|
+
opacity: 0;
|
|
154
|
+
transition: opacity 0.15s;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
a:hover > .${CLASS_NAME}, button:hover > .${CLASS_NAME} {
|
|
158
|
+
opacity: var(--overlay-hover-opacity);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
a:active > .${CLASS_NAME}, button:active > .${CLASS_NAME} {
|
|
162
|
+
opacity: var(--overlay-active-opacity);
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
const HoverEffect = ({
|
|
167
|
+
color,
|
|
168
|
+
hoverOpacity = 0.08,
|
|
169
|
+
activeOpacity = 0.16,
|
|
170
|
+
}: HoverEffectProps) => {
|
|
171
|
+
if (Platform.OS !== 'web' || color == null) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<>
|
|
177
|
+
<style
|
|
178
|
+
// @ts-expect-error: href and precedence are only available on React 19
|
|
179
|
+
href={CLASS_NAME}
|
|
180
|
+
// eslint-disable-next-line @eslint-react/dom/no-unknown-property
|
|
181
|
+
precedence="elements"
|
|
182
|
+
>
|
|
183
|
+
{CSS_TEXT}
|
|
184
|
+
</style>
|
|
185
|
+
<div
|
|
186
|
+
className={CLASS_NAME}
|
|
187
|
+
style={{
|
|
188
|
+
// @ts-expect-error: CSS variables are not typed
|
|
189
|
+
'--overlay-color': color,
|
|
190
|
+
'--overlay-hover-opacity': hoverOpacity,
|
|
191
|
+
'--overlay-active-opacity': activeOpacity,
|
|
192
|
+
}}
|
|
193
|
+
/>
|
|
194
|
+
</>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
type StyleProp,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
View,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
visible: boolean;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
style?: StyleProp<ViewStyle>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
|
17
|
+
|
|
18
|
+
export function ResourceSavingView({
|
|
19
|
+
visible,
|
|
20
|
+
children,
|
|
21
|
+
style,
|
|
22
|
+
...rest
|
|
23
|
+
}: Props) {
|
|
24
|
+
if (Platform.OS === 'web') {
|
|
25
|
+
return (
|
|
26
|
+
<View
|
|
27
|
+
// @ts-expect-error: hidden exists on web, but not in React Native
|
|
28
|
+
hidden={!visible}
|
|
29
|
+
style={[
|
|
30
|
+
{ display: visible ? 'flex' : 'none' },
|
|
31
|
+
styles.container,
|
|
32
|
+
style,
|
|
33
|
+
]}
|
|
34
|
+
pointerEvents={visible ? 'auto' : 'none'}
|
|
35
|
+
{...rest}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<View
|
|
44
|
+
style={[styles.container, style]}
|
|
45
|
+
// box-none doesn't seem to work properly on Android
|
|
46
|
+
pointerEvents={visible ? 'auto' : 'none'}
|
|
47
|
+
>
|
|
48
|
+
<View
|
|
49
|
+
collapsable={false}
|
|
50
|
+
removeClippedSubviews={
|
|
51
|
+
// On iOS & macOS, set removeClippedSubviews to true only when not focused
|
|
52
|
+
// This is an workaround for a bug where the clipped view never re-appears
|
|
53
|
+
Platform.OS === 'ios' || Platform.OS === 'macos' ? !visible : true
|
|
54
|
+
}
|
|
55
|
+
pointerEvents={visible ? 'auto' : 'none'}
|
|
56
|
+
style={visible ? styles.attached : styles.detached}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</View>
|
|
60
|
+
</View>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const styles = StyleSheet.create({
|
|
65
|
+
container: {
|
|
66
|
+
flex: 1,
|
|
67
|
+
overflow: 'hidden',
|
|
68
|
+
},
|
|
69
|
+
attached: {
|
|
70
|
+
flex: 1,
|
|
71
|
+
},
|
|
72
|
+
detached: {
|
|
73
|
+
flex: 1,
|
|
74
|
+
top: FAR_FAR_AWAY,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Dimensions,
|
|
4
|
+
Platform,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
View,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {
|
|
11
|
+
initialWindowMetrics,
|
|
12
|
+
SafeAreaInsetsContext,
|
|
13
|
+
SafeAreaProvider,
|
|
14
|
+
} from 'react-native-safe-area-context';
|
|
15
|
+
|
|
16
|
+
type Props = {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
style?: StyleProp<ViewStyle>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const { width = 0, height = 0 } = Dimensions.get('window');
|
|
22
|
+
|
|
23
|
+
// To support SSR on web, we need to have empty insets for initial values
|
|
24
|
+
// Otherwise there can be mismatch between SSR and client output
|
|
25
|
+
// We also need to specify empty values to support tests environments
|
|
26
|
+
const initialMetrics =
|
|
27
|
+
Platform.OS === 'web' || initialWindowMetrics == null
|
|
28
|
+
? {
|
|
29
|
+
frame: { x: 0, y: 0, width, height },
|
|
30
|
+
insets: { top: 0, left: 0, right: 0, bottom: 0 },
|
|
31
|
+
}
|
|
32
|
+
: initialWindowMetrics;
|
|
33
|
+
|
|
34
|
+
export function SafeAreaProviderCompat({ children, style }: Props) {
|
|
35
|
+
return (
|
|
36
|
+
<SafeAreaInsetsContext.Consumer>
|
|
37
|
+
{(insets) => {
|
|
38
|
+
if (insets) {
|
|
39
|
+
// If we already have insets, don't wrap the stack in another safe area provider
|
|
40
|
+
// This avoids an issue with updates at the cost of potentially incorrect values
|
|
41
|
+
// https://github.com/react-navigation/react-navigation/issues/174
|
|
42
|
+
return <View style={[styles.container, style]}>{children}</View>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<SafeAreaProvider initialMetrics={initialMetrics} style={style}>
|
|
47
|
+
{children}
|
|
48
|
+
</SafeAreaProvider>
|
|
49
|
+
);
|
|
50
|
+
}}
|
|
51
|
+
</SafeAreaInsetsContext.Consumer>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
SafeAreaProviderCompat.initialMetrics = initialMetrics;
|
|
56
|
+
|
|
57
|
+
const styles = StyleSheet.create({
|
|
58
|
+
container: {
|
|
59
|
+
flex: 1,
|
|
60
|
+
},
|
|
61
|
+
});
|
package/src/Screen.tsx
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NavigationContext,
|
|
3
|
+
type NavigationProp,
|
|
4
|
+
NavigationRouteContext,
|
|
5
|
+
type ParamListBase,
|
|
6
|
+
type RouteProp,
|
|
7
|
+
} from '@react-navigation/native';
|
|
8
|
+
import * as React from 'react';
|
|
9
|
+
import {
|
|
10
|
+
Animated,
|
|
11
|
+
type StyleProp,
|
|
12
|
+
StyleSheet,
|
|
13
|
+
View,
|
|
14
|
+
type ViewStyle,
|
|
15
|
+
} from 'react-native';
|
|
16
|
+
import {
|
|
17
|
+
useSafeAreaFrame,
|
|
18
|
+
useSafeAreaInsets,
|
|
19
|
+
} from 'react-native-safe-area-context';
|
|
20
|
+
|
|
21
|
+
import { Background } from './Background';
|
|
22
|
+
import { getDefaultHeaderHeight } from './Header/getDefaultHeaderHeight';
|
|
23
|
+
import { HeaderHeightContext } from './Header/HeaderHeightContext';
|
|
24
|
+
import { HeaderShownContext } from './Header/HeaderShownContext';
|
|
25
|
+
|
|
26
|
+
type Props = {
|
|
27
|
+
focused: boolean;
|
|
28
|
+
modal?: boolean;
|
|
29
|
+
navigation: NavigationProp<ParamListBase>;
|
|
30
|
+
route: RouteProp<ParamListBase>;
|
|
31
|
+
header: React.ReactNode;
|
|
32
|
+
headerShown?: boolean;
|
|
33
|
+
headerStatusBarHeight?: number;
|
|
34
|
+
headerTransparent?: boolean;
|
|
35
|
+
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
|
36
|
+
children: React.ReactNode;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function Screen(props: Props) {
|
|
40
|
+
const dimensions = useSafeAreaFrame();
|
|
41
|
+
const insets = useSafeAreaInsets();
|
|
42
|
+
|
|
43
|
+
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
|
44
|
+
const parentHeaderHeight = React.useContext(HeaderHeightContext);
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
focused,
|
|
48
|
+
modal = false,
|
|
49
|
+
header,
|
|
50
|
+
headerShown = true,
|
|
51
|
+
headerTransparent,
|
|
52
|
+
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
|
53
|
+
navigation,
|
|
54
|
+
route,
|
|
55
|
+
children,
|
|
56
|
+
style,
|
|
57
|
+
} = props;
|
|
58
|
+
|
|
59
|
+
const [headerHeight, setHeaderHeight] = React.useState(() =>
|
|
60
|
+
getDefaultHeaderHeight(dimensions, modal, headerStatusBarHeight)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Background
|
|
65
|
+
accessibilityElementsHidden={!focused}
|
|
66
|
+
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
|
|
67
|
+
style={[styles.container, style]}
|
|
68
|
+
// On Fabric we need to disable collapsing for the background to ensure
|
|
69
|
+
// that we won't render unnecessary views due to the view flattening.
|
|
70
|
+
collapsable={false}
|
|
71
|
+
>
|
|
72
|
+
{headerShown ? (
|
|
73
|
+
<NavigationContext.Provider value={navigation}>
|
|
74
|
+
<NavigationRouteContext.Provider value={route}>
|
|
75
|
+
<View
|
|
76
|
+
pointerEvents="box-none"
|
|
77
|
+
onLayout={(e) => {
|
|
78
|
+
const { height } = e.nativeEvent.layout;
|
|
79
|
+
|
|
80
|
+
setHeaderHeight(height);
|
|
81
|
+
}}
|
|
82
|
+
style={[
|
|
83
|
+
styles.header,
|
|
84
|
+
headerTransparent ? styles.absolute : null,
|
|
85
|
+
]}
|
|
86
|
+
>
|
|
87
|
+
{header}
|
|
88
|
+
</View>
|
|
89
|
+
</NavigationRouteContext.Provider>
|
|
90
|
+
</NavigationContext.Provider>
|
|
91
|
+
) : null}
|
|
92
|
+
<View style={styles.content}>
|
|
93
|
+
<HeaderShownContext.Provider
|
|
94
|
+
value={isParentHeaderShown || headerShown !== false}
|
|
95
|
+
>
|
|
96
|
+
<HeaderHeightContext.Provider
|
|
97
|
+
value={headerShown ? headerHeight : parentHeaderHeight ?? 0}
|
|
98
|
+
>
|
|
99
|
+
{children}
|
|
100
|
+
</HeaderHeightContext.Provider>
|
|
101
|
+
</HeaderShownContext.Provider>
|
|
102
|
+
</View>
|
|
103
|
+
</Background>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const styles = StyleSheet.create({
|
|
108
|
+
container: {
|
|
109
|
+
flex: 1,
|
|
110
|
+
},
|
|
111
|
+
content: {
|
|
112
|
+
flex: 1,
|
|
113
|
+
},
|
|
114
|
+
header: {
|
|
115
|
+
zIndex: 1,
|
|
116
|
+
},
|
|
117
|
+
absolute: {
|
|
118
|
+
position: 'absolute',
|
|
119
|
+
top: 0,
|
|
120
|
+
start: 0,
|
|
121
|
+
end: 0,
|
|
122
|
+
},
|
|
123
|
+
});
|
package/src/Text.tsx
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useTheme } from '@react-navigation/native';
|
|
2
|
+
// eslint-disable-next-line no-restricted-imports
|
|
3
|
+
import { Text as NativeText, type TextProps } from 'react-native';
|
|
4
|
+
|
|
5
|
+
export function Text({ style, ...rest }: TextProps) {
|
|
6
|
+
const { colors, fonts } = useTheme();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<NativeText
|
|
10
|
+
{...rest}
|
|
11
|
+
style={[{ color: colors.text }, fonts.regular, style]}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
jest,
|
|
7
|
+
test,
|
|
8
|
+
} from '@jest/globals';
|
|
9
|
+
import { NavigationContainer } from '@react-navigation/native';
|
|
10
|
+
import { fireEvent, render } from '@testing-library/react-native';
|
|
11
|
+
import { Platform, View } from 'react-native';
|
|
12
|
+
|
|
13
|
+
import { PlatformPressable } from '../PlatformPressable';
|
|
14
|
+
|
|
15
|
+
jest.useFakeTimers();
|
|
16
|
+
|
|
17
|
+
test('triggers onPress on press event', () => {
|
|
18
|
+
const onPress = jest.fn();
|
|
19
|
+
|
|
20
|
+
const { getByTestId } = render(
|
|
21
|
+
<PlatformPressable onPress={onPress} testID="pressable">
|
|
22
|
+
<View />
|
|
23
|
+
</PlatformPressable>,
|
|
24
|
+
{ wrapper: NavigationContainer }
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
fireEvent.press(getByTestId('pressable'));
|
|
28
|
+
|
|
29
|
+
jest.runAllTimers();
|
|
30
|
+
|
|
31
|
+
expect(onPress).toHaveBeenCalled();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('web', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.replaceProperty(Platform, 'OS', 'web');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
jest.restoreAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('triggers press on left click', () => {
|
|
44
|
+
const onPress = jest.fn();
|
|
45
|
+
const preventDefault = jest.fn();
|
|
46
|
+
|
|
47
|
+
const { getByTestId } = render(
|
|
48
|
+
<PlatformPressable onPress={onPress} testID="pressable" href={'/'}>
|
|
49
|
+
<View />
|
|
50
|
+
</PlatformPressable>,
|
|
51
|
+
{ wrapper: NavigationContainer }
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
fireEvent.press(getByTestId('pressable'), {
|
|
55
|
+
button: 0,
|
|
56
|
+
preventDefault,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
jest.runAllTimers();
|
|
60
|
+
|
|
61
|
+
expect(preventDefault).toHaveBeenCalled();
|
|
62
|
+
expect(onPress).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('ignores press non-left clicks', () => {
|
|
66
|
+
const onPress = jest.fn();
|
|
67
|
+
|
|
68
|
+
const { getByTestId } = render(
|
|
69
|
+
<PlatformPressable onPress={onPress} testID="pressable" href={'/'}>
|
|
70
|
+
<View />
|
|
71
|
+
</PlatformPressable>,
|
|
72
|
+
{ wrapper: NavigationContainer }
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
fireEvent.press(getByTestId('pressable'), { button: 1 });
|
|
76
|
+
|
|
77
|
+
jest.runAllTimers();
|
|
78
|
+
|
|
79
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const APPROX_APP_BAR_HEIGHT = 56;
|
|
2
|
+
const DEFAULT_DRAWER_WIDTH = 360;
|
|
3
|
+
|
|
4
|
+
export const getDefaultSidebarWidth = ({ width }: { width: number }) => {
|
|
5
|
+
/**
|
|
6
|
+
* Default sidebar width is 360dp
|
|
7
|
+
* On screens smaller than 320dp, ideally the drawer would collapse to a tab bar
|
|
8
|
+
* https://m3.material.io/components/navigation-drawer/specs
|
|
9
|
+
*/
|
|
10
|
+
if (width - APPROX_APP_BAR_HEIGHT <= 360) {
|
|
11
|
+
return width - APPROX_APP_BAR_HEIGHT;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return DEFAULT_DRAWER_WIDTH;
|
|
15
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
const contexts = '__react_navigation__elements_contexts';
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
// eslint-disable-next-line no-var
|
|
7
|
+
var __react_navigation__elements_contexts: Map<string, React.Context<any>>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// We use a global variable to keep our contexts so that we can reuse same contexts across packages
|
|
11
|
+
globalThis[contexts] =
|
|
12
|
+
globalThis[contexts] ?? new Map<string, React.Context<any>>();
|
|
13
|
+
|
|
14
|
+
export function getNamedContext<T>(
|
|
15
|
+
name: string,
|
|
16
|
+
initialValue: T
|
|
17
|
+
): React.Context<T> {
|
|
18
|
+
let context = globalThis[contexts].get(name);
|
|
19
|
+
|
|
20
|
+
if (context) {
|
|
21
|
+
return context;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
context = React.createContext<T>(initialValue);
|
|
25
|
+
context.displayName = name;
|
|
26
|
+
|
|
27
|
+
globalThis[contexts].set(name, context);
|
|
28
|
+
|
|
29
|
+
return context;
|
|
30
|
+
}
|