@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.
Files changed (222) hide show
  1. package/LICENSE +21 -0
  2. package/README.OpenSource +11 -0
  3. package/README.md +9 -0
  4. package/lib/module/Background.js +22 -0
  5. package/lib/module/Background.js.map +1 -0
  6. package/lib/module/Button.js +106 -0
  7. package/lib/module/Button.js.map +1 -0
  8. package/lib/module/Header/Header.js +338 -0
  9. package/lib/module/Header/Header.js.map +1 -0
  10. package/lib/module/Header/HeaderBackButton.js +188 -0
  11. package/lib/module/Header/HeaderBackButton.js.map +1 -0
  12. package/lib/module/Header/HeaderBackContext.js +5 -0
  13. package/lib/module/Header/HeaderBackContext.js.map +1 -0
  14. package/lib/module/Header/HeaderBackground.js +47 -0
  15. package/lib/module/Header/HeaderBackground.js.map +1 -0
  16. package/lib/module/Header/HeaderButton.js +56 -0
  17. package/lib/module/Header/HeaderButton.js.map +1 -0
  18. package/lib/module/Header/HeaderHeightContext.js +5 -0
  19. package/lib/module/Header/HeaderHeightContext.js.map +1 -0
  20. package/lib/module/Header/HeaderIcon.js +38 -0
  21. package/lib/module/Header/HeaderIcon.js.map +1 -0
  22. package/lib/module/Header/HeaderSearchBar.js +288 -0
  23. package/lib/module/Header/HeaderSearchBar.js.map +1 -0
  24. package/lib/module/Header/HeaderShownContext.js +5 -0
  25. package/lib/module/Header/HeaderShownContext.js.map +1 -0
  26. package/lib/module/Header/HeaderTitle.js +41 -0
  27. package/lib/module/Header/HeaderTitle.js.map +1 -0
  28. package/lib/module/Header/getDefaultHeaderHeight.js +45 -0
  29. package/lib/module/Header/getDefaultHeaderHeight.js.map +1 -0
  30. package/lib/module/Header/getHeaderTitle.js +6 -0
  31. package/lib/module/Header/getHeaderTitle.js.map +1 -0
  32. package/lib/module/Header/useHeaderHeight.js +12 -0
  33. package/lib/module/Header/useHeaderHeight.js.map +1 -0
  34. package/lib/module/Label/Label.js +25 -0
  35. package/lib/module/Label/Label.js.map +1 -0
  36. package/lib/module/Label/getLabel.js +6 -0
  37. package/lib/module/Label/getLabel.js.map +1 -0
  38. package/lib/module/MaskedView.android.js +4 -0
  39. package/lib/module/MaskedView.android.js.map +1 -0
  40. package/lib/module/MaskedView.ios.js +4 -0
  41. package/lib/module/MaskedView.ios.js.map +1 -0
  42. package/lib/module/MaskedView.js +12 -0
  43. package/lib/module/MaskedView.js.map +1 -0
  44. package/lib/module/MaskedViewNative.js +30 -0
  45. package/lib/module/MaskedViewNative.js.map +1 -0
  46. package/lib/module/MissingIcon.js +24 -0
  47. package/lib/module/MissingIcon.js.map +1 -0
  48. package/lib/module/PlatformPressable.js +141 -0
  49. package/lib/module/PlatformPressable.js.map +1 -0
  50. package/lib/module/ResourceSavingView.js +57 -0
  51. package/lib/module/ResourceSavingView.js.map +1 -0
  52. package/lib/module/SafeAreaProviderCompat.js +58 -0
  53. package/lib/module/SafeAreaProviderCompat.js.map +1 -0
  54. package/lib/module/Screen.js +83 -0
  55. package/lib/module/Screen.js.map +1 -0
  56. package/lib/module/Text.js +22 -0
  57. package/lib/module/Text.js.map +1 -0
  58. package/lib/module/assets/back-icon-mask.png +0 -0
  59. package/lib/module/assets/back-icon.png +0 -0
  60. package/lib/module/assets/back-icon@1x.android.png +0 -0
  61. package/lib/module/assets/back-icon@1x.ios.png +0 -0
  62. package/lib/module/assets/back-icon@2x.android.png +0 -0
  63. package/lib/module/assets/back-icon@2x.ios.png +0 -0
  64. package/lib/module/assets/back-icon@3x.android.png +0 -0
  65. package/lib/module/assets/back-icon@3x.ios.png +0 -0
  66. package/lib/module/assets/back-icon@4x.android.png +0 -0
  67. package/lib/module/assets/back-icon@4x.ios.png +0 -0
  68. package/lib/module/assets/clear-icon.png +0 -0
  69. package/lib/module/assets/clear-icon@1x.png +0 -0
  70. package/lib/module/assets/clear-icon@2x.png +0 -0
  71. package/lib/module/assets/clear-icon@3x.png +0 -0
  72. package/lib/module/assets/clear-icon@4x.png +0 -0
  73. package/lib/module/assets/close-icon.png +0 -0
  74. package/lib/module/assets/close-icon@1x.png +0 -0
  75. package/lib/module/assets/close-icon@2x.png +0 -0
  76. package/lib/module/assets/close-icon@3x.png +0 -0
  77. package/lib/module/assets/close-icon@4x.png +0 -0
  78. package/lib/module/assets/search-icon.png +0 -0
  79. package/lib/module/assets/search-icon@1x.android.png +0 -0
  80. package/lib/module/assets/search-icon@1x.ios.png +0 -0
  81. package/lib/module/assets/search-icon@2x.android.png +0 -0
  82. package/lib/module/assets/search-icon@2x.ios.png +0 -0
  83. package/lib/module/assets/search-icon@3x.android.png +0 -0
  84. package/lib/module/assets/search-icon@3x.ios.png +0 -0
  85. package/lib/module/assets/search-icon@4x.android.png +0 -0
  86. package/lib/module/assets/search-icon@4x.ios.png +0 -0
  87. package/lib/module/getDefaultSidebarWidth.js +18 -0
  88. package/lib/module/getDefaultSidebarWidth.js.map +1 -0
  89. package/lib/module/getNamedContext.js +17 -0
  90. package/lib/module/getNamedContext.js.map +1 -0
  91. package/lib/module/index.js +32 -0
  92. package/lib/module/index.js.map +1 -0
  93. package/lib/module/package.json +1 -0
  94. package/lib/module/types.js +4 -0
  95. package/lib/module/types.js.map +1 -0
  96. package/lib/typescript/package.json +1 -0
  97. package/lib/typescript/src/Background.d.ts +9 -0
  98. package/lib/typescript/src/Background.d.ts.map +1 -0
  99. package/lib/typescript/src/Button.d.ts +13 -0
  100. package/lib/typescript/src/Button.d.ts.map +1 -0
  101. package/lib/typescript/src/Header/Header.d.ts +31 -0
  102. package/lib/typescript/src/Header/Header.d.ts.map +1 -0
  103. package/lib/typescript/src/Header/HeaderBackButton.d.ts +3 -0
  104. package/lib/typescript/src/Header/HeaderBackButton.d.ts.map +1 -0
  105. package/lib/typescript/src/Header/HeaderBackContext.d.ts +5 -0
  106. package/lib/typescript/src/Header/HeaderBackContext.d.ts.map +1 -0
  107. package/lib/typescript/src/Header/HeaderBackground.d.ts +9 -0
  108. package/lib/typescript/src/Header/HeaderBackground.d.ts.map +1 -0
  109. package/lib/typescript/src/Header/HeaderButton.d.ts +3 -0
  110. package/lib/typescript/src/Header/HeaderButton.d.ts.map +1 -0
  111. package/lib/typescript/src/Header/HeaderHeightContext.d.ts +2 -0
  112. package/lib/typescript/src/Header/HeaderHeightContext.d.ts.map +1 -0
  113. package/lib/typescript/src/Header/HeaderIcon.d.ts +5 -0
  114. package/lib/typescript/src/Header/HeaderIcon.d.ts.map +1 -0
  115. package/lib/typescript/src/Header/HeaderSearchBar.d.ts +10 -0
  116. package/lib/typescript/src/Header/HeaderSearchBar.d.ts.map +1 -0
  117. package/lib/typescript/src/Header/HeaderShownContext.d.ts +2 -0
  118. package/lib/typescript/src/Header/HeaderShownContext.d.ts.map +1 -0
  119. package/lib/typescript/src/Header/HeaderTitle.d.ts +9 -0
  120. package/lib/typescript/src/Header/HeaderTitle.d.ts.map +1 -0
  121. package/lib/typescript/src/Header/getDefaultHeaderHeight.d.ts +3 -0
  122. package/lib/typescript/src/Header/getDefaultHeaderHeight.d.ts.map +1 -0
  123. package/lib/typescript/src/Header/getHeaderTitle.d.ts +6 -0
  124. package/lib/typescript/src/Header/getHeaderTitle.d.ts.map +1 -0
  125. package/lib/typescript/src/Header/useHeaderHeight.d.ts +2 -0
  126. package/lib/typescript/src/Header/useHeaderHeight.d.ts.map +1 -0
  127. package/lib/typescript/src/Label/Label.d.ts +9 -0
  128. package/lib/typescript/src/Label/Label.d.ts.map +1 -0
  129. package/lib/typescript/src/Label/getLabel.d.ts +5 -0
  130. package/lib/typescript/src/Label/getLabel.d.ts.map +1 -0
  131. package/lib/typescript/src/MaskedView.android.d.ts +2 -0
  132. package/lib/typescript/src/MaskedView.android.d.ts.map +1 -0
  133. package/lib/typescript/src/MaskedView.d.ts +11 -0
  134. package/lib/typescript/src/MaskedView.d.ts.map +1 -0
  135. package/lib/typescript/src/MaskedView.ios.d.ts +2 -0
  136. package/lib/typescript/src/MaskedView.ios.d.ts.map +1 -0
  137. package/lib/typescript/src/MaskedViewNative.d.ts +11 -0
  138. package/lib/typescript/src/MaskedViewNative.d.ts.map +1 -0
  139. package/lib/typescript/src/MissingIcon.d.ts +9 -0
  140. package/lib/typescript/src/MissingIcon.d.ts.map +1 -0
  141. package/lib/typescript/src/PlatformPressable.d.ts +21 -0
  142. package/lib/typescript/src/PlatformPressable.d.ts.map +1 -0
  143. package/lib/typescript/src/ResourceSavingView.d.ts +10 -0
  144. package/lib/typescript/src/ResourceSavingView.d.ts.map +1 -0
  145. package/lib/typescript/src/SafeAreaProviderCompat.d.ts +12 -0
  146. package/lib/typescript/src/SafeAreaProviderCompat.d.ts.map +1 -0
  147. package/lib/typescript/src/Screen.d.ts +18 -0
  148. package/lib/typescript/src/Screen.d.ts.map +1 -0
  149. package/lib/typescript/src/Text.d.ts +3 -0
  150. package/lib/typescript/src/Text.d.ts.map +1 -0
  151. package/lib/typescript/src/__tests__/PlatformPressable.test.d.ts +2 -0
  152. package/lib/typescript/src/__tests__/PlatformPressable.test.d.ts.map +1 -0
  153. package/lib/typescript/src/getDefaultSidebarWidth.d.ts +4 -0
  154. package/lib/typescript/src/getDefaultSidebarWidth.d.ts.map +1 -0
  155. package/lib/typescript/src/getNamedContext.d.ts +6 -0
  156. package/lib/typescript/src/getNamedContext.d.ts.map +1 -0
  157. package/lib/typescript/src/index.d.ts +25 -0
  158. package/lib/typescript/src/index.d.ts.map +1 -0
  159. package/lib/typescript/src/types.d.ts +325 -0
  160. package/lib/typescript/src/types.d.ts.map +1 -0
  161. package/package.json +83 -0
  162. package/src/Background.tsx +24 -0
  163. package/src/Button.tsx +120 -0
  164. package/src/Header/Header.tsx +450 -0
  165. package/src/Header/HeaderBackButton.tsx +249 -0
  166. package/src/Header/HeaderBackContext.tsx +5 -0
  167. package/src/Header/HeaderBackground.tsx +60 -0
  168. package/src/Header/HeaderButton.tsx +55 -0
  169. package/src/Header/HeaderHeightContext.tsx +6 -0
  170. package/src/Header/HeaderIcon.tsx +32 -0
  171. package/src/Header/HeaderSearchBar.tsx +323 -0
  172. package/src/Header/HeaderShownContext.tsx +3 -0
  173. package/src/Header/HeaderTitle.tsx +48 -0
  174. package/src/Header/getDefaultHeaderHeight.tsx +54 -0
  175. package/src/Header/getHeaderTitle.tsx +12 -0
  176. package/src/Header/useHeaderHeight.tsx +15 -0
  177. package/src/Label/Label.tsx +31 -0
  178. package/src/Label/getLabel.tsx +10 -0
  179. package/src/MaskedView.android.tsx +1 -0
  180. package/src/MaskedView.ios.tsx +1 -0
  181. package/src/MaskedView.tsx +13 -0
  182. package/src/MaskedViewNative.tsx +33 -0
  183. package/src/MissingIcon.tsx +19 -0
  184. package/src/PlatformPressable.tsx +196 -0
  185. package/src/ResourceSavingView.tsx +76 -0
  186. package/src/SafeAreaProviderCompat.tsx +61 -0
  187. package/src/Screen.tsx +123 -0
  188. package/src/Text.tsx +14 -0
  189. package/src/__tests__/PlatformPressable.test.tsx +81 -0
  190. package/src/assets/back-icon-mask.png +0 -0
  191. package/src/assets/back-icon.png +0 -0
  192. package/src/assets/back-icon@1x.android.png +0 -0
  193. package/src/assets/back-icon@1x.ios.png +0 -0
  194. package/src/assets/back-icon@2x.android.png +0 -0
  195. package/src/assets/back-icon@2x.ios.png +0 -0
  196. package/src/assets/back-icon@3x.android.png +0 -0
  197. package/src/assets/back-icon@3x.ios.png +0 -0
  198. package/src/assets/back-icon@4x.android.png +0 -0
  199. package/src/assets/back-icon@4x.ios.png +0 -0
  200. package/src/assets/clear-icon.png +0 -0
  201. package/src/assets/clear-icon@1x.png +0 -0
  202. package/src/assets/clear-icon@2x.png +0 -0
  203. package/src/assets/clear-icon@3x.png +0 -0
  204. package/src/assets/clear-icon@4x.png +0 -0
  205. package/src/assets/close-icon.png +0 -0
  206. package/src/assets/close-icon@1x.png +0 -0
  207. package/src/assets/close-icon@2x.png +0 -0
  208. package/src/assets/close-icon@3x.png +0 -0
  209. package/src/assets/close-icon@4x.png +0 -0
  210. package/src/assets/search-icon.png +0 -0
  211. package/src/assets/search-icon@1x.android.png +0 -0
  212. package/src/assets/search-icon@1x.ios.png +0 -0
  213. package/src/assets/search-icon@2x.android.png +0 -0
  214. package/src/assets/search-icon@2x.ios.png +0 -0
  215. package/src/assets/search-icon@3x.android.png +0 -0
  216. package/src/assets/search-icon@3x.ios.png +0 -0
  217. package/src/assets/search-icon@4x.android.png +0 -0
  218. package/src/assets/search-icon@4x.ios.png +0 -0
  219. package/src/getDefaultSidebarWidth.tsx +15 -0
  220. package/src/getNamedContext.tsx +30 -0
  221. package/src/index.tsx +38 -0
  222. 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
@@ -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
+ }