@react-navigation/elements 3.0.0-alpha.4 → 3.0.0-alpha.40

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 (212) hide show
  1. package/lib/module/ActivityView.js +90 -0
  2. package/lib/module/ActivityView.js.map +1 -0
  3. package/lib/module/ActivityView.native.js +67 -0
  4. package/lib/module/ActivityView.native.js.map +1 -0
  5. package/lib/module/Badge.js +5 -4
  6. package/lib/module/Badge.js.map +1 -1
  7. package/lib/module/BlurEffectBackground.js +3 -3
  8. package/lib/module/BlurEffectBackground.js.map +1 -1
  9. package/lib/module/Button.js +96 -14
  10. package/lib/module/Button.js.map +1 -1
  11. package/lib/module/Color.js +565 -4
  12. package/lib/module/Color.js.map +1 -1
  13. package/lib/module/Container.js +11 -1
  14. package/lib/module/Container.js.map +1 -1
  15. package/lib/module/Header/Header.js +29 -17
  16. package/lib/module/Header/Header.js.map +1 -1
  17. package/lib/module/Header/HeaderBackButton.js +27 -13
  18. package/lib/module/Header/HeaderBackButton.js.map +1 -1
  19. package/lib/module/Header/HeaderButton.js +4 -5
  20. package/lib/module/Header/HeaderButton.js.map +1 -1
  21. package/lib/module/Header/HeaderButtonBackground.js +1 -2
  22. package/lib/module/Header/HeaderButtonBackground.js.map +1 -1
  23. package/lib/module/Header/HeaderIcon.js +14 -17
  24. package/lib/module/Header/HeaderIcon.js.map +1 -1
  25. package/lib/module/Header/HeaderSearchBar.js +35 -25
  26. package/lib/module/Header/HeaderSearchBar.js.map +1 -1
  27. package/lib/module/Header/getHeaderTitle.js.map +1 -1
  28. package/lib/module/Header/useHeaderHeight.js +1 -1
  29. package/lib/module/Header/useHeaderHeight.js.map +1 -1
  30. package/lib/module/Label/getLabel.js.map +1 -1
  31. package/lib/module/LiquidGlassView.ios.js +1 -2
  32. package/lib/module/LiquidGlassView.ios.js.map +1 -1
  33. package/lib/module/LiquidGlassView.js +0 -4
  34. package/lib/module/LiquidGlassView.js.map +1 -1
  35. package/lib/module/PlatformColor.js +1 -0
  36. package/lib/module/PlatformColor.js.map +1 -1
  37. package/lib/module/PlatformColor.native.js +1 -1
  38. package/lib/module/PlatformColor.native.js.map +1 -1
  39. package/lib/module/PlatformIcon.js +46 -0
  40. package/lib/module/PlatformIcon.js.map +1 -0
  41. package/lib/module/PlatformPressable.js +56 -14
  42. package/lib/module/PlatformPressable.js.map +1 -1
  43. package/lib/module/SafeAreaProviderCompat.js +1 -1
  44. package/lib/module/SafeAreaProviderCompat.js.map +1 -1
  45. package/lib/module/Screen.js +31 -18
  46. package/lib/module/Screen.js.map +1 -1
  47. package/lib/module/index.js +2 -7
  48. package/lib/module/index.js.map +1 -1
  49. package/lib/module/internal.js +1 -1
  50. package/lib/module/internal.js.map +1 -1
  51. package/lib/module/useFrameSize.js +6 -2
  52. package/lib/module/useFrameSize.js.map +1 -1
  53. package/lib/typescript/src/ActivityView.d.ts +31 -0
  54. package/lib/typescript/src/ActivityView.d.ts.map +1 -0
  55. package/lib/typescript/src/ActivityView.native.d.ts +3 -0
  56. package/lib/typescript/src/ActivityView.native.d.ts.map +1 -0
  57. package/lib/typescript/src/Badge.d.ts +6 -5
  58. package/lib/typescript/src/Badge.d.ts.map +1 -1
  59. package/lib/typescript/src/BlurEffectBackground.d.ts +2 -2
  60. package/lib/typescript/src/BlurEffectBackground.d.ts.map +1 -1
  61. package/lib/typescript/src/Button.d.ts +54 -6
  62. package/lib/typescript/src/Button.d.ts.map +1 -1
  63. package/lib/typescript/src/Color.d.ts +4 -4
  64. package/lib/typescript/src/Color.d.ts.map +1 -1
  65. package/lib/typescript/src/Container.d.ts +7 -4
  66. package/lib/typescript/src/Container.d.ts.map +1 -1
  67. package/lib/typescript/src/Header/Header.d.ts +5 -4
  68. package/lib/typescript/src/Header/Header.d.ts.map +1 -1
  69. package/lib/typescript/src/Header/HeaderBackButton.d.ts +3 -2
  70. package/lib/typescript/src/Header/HeaderBackButton.d.ts.map +1 -1
  71. package/lib/typescript/src/Header/HeaderBackground.d.ts +5 -5
  72. package/lib/typescript/src/Header/HeaderBackground.d.ts.map +1 -1
  73. package/lib/typescript/src/Header/HeaderButton.d.ts +7 -2
  74. package/lib/typescript/src/Header/HeaderButton.d.ts.map +1 -1
  75. package/lib/typescript/src/Header/HeaderButtonBackground.d.ts +1 -1
  76. package/lib/typescript/src/Header/HeaderButtonBackground.d.ts.map +1 -1
  77. package/lib/typescript/src/Header/HeaderIcon.d.ts +9 -4
  78. package/lib/typescript/src/Header/HeaderIcon.d.ts.map +1 -1
  79. package/lib/typescript/src/Header/HeaderSearchBar.d.ts +10 -7
  80. package/lib/typescript/src/Header/HeaderSearchBar.d.ts.map +1 -1
  81. package/lib/typescript/src/Header/HeaderTitle.d.ts +4 -4
  82. package/lib/typescript/src/Header/HeaderTitle.d.ts.map +1 -1
  83. package/lib/typescript/src/Header/getHeaderTitle.d.ts +2 -2
  84. package/lib/typescript/src/Header/getHeaderTitle.d.ts.map +1 -1
  85. package/lib/typescript/src/Label/Label.d.ts +1 -1
  86. package/lib/typescript/src/Label/Label.d.ts.map +1 -1
  87. package/lib/typescript/src/Label/getLabel.d.ts +2 -2
  88. package/lib/typescript/src/Label/getLabel.d.ts.map +1 -1
  89. package/lib/typescript/src/LiquidGlassView.d.ts +7 -5
  90. package/lib/typescript/src/LiquidGlassView.d.ts.map +1 -1
  91. package/lib/typescript/src/LiquidGlassView.ios.d.ts +4 -2
  92. package/lib/typescript/src/LiquidGlassView.ios.d.ts.map +1 -1
  93. package/lib/typescript/src/MissingIcon.d.ts +1 -1
  94. package/lib/typescript/src/MissingIcon.d.ts.map +1 -1
  95. package/lib/typescript/src/PlatformColor.d.ts +1 -0
  96. package/lib/typescript/src/PlatformColor.d.ts.map +1 -1
  97. package/lib/typescript/src/PlatformColor.native.d.ts +1 -1
  98. package/lib/typescript/src/PlatformColor.native.d.ts.map +1 -1
  99. package/lib/typescript/src/PlatformIcon.d.ts +10 -0
  100. package/lib/typescript/src/PlatformIcon.d.ts.map +1 -0
  101. package/lib/typescript/src/PlatformPressable.d.ts +12 -15
  102. package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
  103. package/lib/typescript/src/SafeAreaProviderCompat.d.ts +1 -1
  104. package/lib/typescript/src/SafeAreaProviderCompat.d.ts.map +1 -1
  105. package/lib/typescript/src/Screen.d.ts +7 -7
  106. package/lib/typescript/src/Screen.d.ts.map +1 -1
  107. package/lib/typescript/src/Text.d.ts +1 -1
  108. package/lib/typescript/src/Text.d.ts.map +1 -1
  109. package/lib/typescript/src/index.d.ts +21 -21
  110. package/lib/typescript/src/index.d.ts.map +1 -1
  111. package/lib/typescript/src/internal.d.ts +6 -6
  112. package/lib/typescript/src/internal.d.ts.map +1 -1
  113. package/lib/typescript/src/types.d.ts +120 -70
  114. package/lib/typescript/src/types.d.ts.map +1 -1
  115. package/lib/typescript/src/useFrameSize.d.ts +1 -1
  116. package/lib/typescript/src/useFrameSize.d.ts.map +1 -1
  117. package/package.json +23 -22
  118. package/src/ActivityView.native.tsx +90 -0
  119. package/src/ActivityView.tsx +129 -0
  120. package/src/Badge.tsx +10 -8
  121. package/src/BlurEffectBackground.tsx +3 -3
  122. package/src/Button.tsx +173 -28
  123. package/src/Color.tsx +755 -8
  124. package/src/Container.tsx +20 -4
  125. package/src/Header/Header.tsx +46 -26
  126. package/src/Header/HeaderBackButton.tsx +31 -17
  127. package/src/Header/HeaderBackground.tsx +3 -3
  128. package/src/Header/HeaderButton.tsx +16 -18
  129. package/src/Header/HeaderButtonBackground.tsx +1 -4
  130. package/src/Header/HeaderIcon.tsx +27 -13
  131. package/src/Header/HeaderSearchBar.tsx +52 -44
  132. package/src/Header/HeaderTitle.tsx +3 -3
  133. package/src/Header/getHeaderTitle.tsx +4 -1
  134. package/src/Header/useHeaderHeight.tsx +1 -1
  135. package/src/Label/getLabel.tsx +1 -1
  136. package/src/LiquidGlassView.ios.tsx +14 -9
  137. package/src/LiquidGlassView.tsx +15 -11
  138. package/src/PlatformColor.native.tsx +1 -1
  139. package/src/PlatformColor.tsx +4 -0
  140. package/src/PlatformIcon.tsx +66 -0
  141. package/src/PlatformPressable.tsx +99 -41
  142. package/src/SafeAreaProviderCompat.tsx +1 -1
  143. package/src/Screen.tsx +40 -27
  144. package/src/index.tsx +2 -16
  145. package/src/internal.tsx +1 -1
  146. package/src/types.tsx +153 -76
  147. package/src/useFrameSize.tsx +9 -2
  148. package/lib/module/Lazy.js +0 -42
  149. package/lib/module/Lazy.js.map +0 -1
  150. package/lib/module/assets/back-icon.ios.svg +0 -4
  151. package/lib/module/assets/back-icon@1x.android.png +0 -0
  152. package/lib/module/assets/back-icon@1x.ios.png +0 -0
  153. package/lib/module/assets/back-icon@2x.android.png +0 -0
  154. package/lib/module/assets/back-icon@2x.ios.png +0 -0
  155. package/lib/module/assets/back-icon@3x.android.png +0 -0
  156. package/lib/module/assets/back-icon@3x.ios.png +0 -0
  157. package/lib/module/assets/back-icon@4x.android.png +0 -0
  158. package/lib/module/assets/back-icon@4x.ios.png +0 -0
  159. package/lib/module/assets/clear-icon@1x.png +0 -0
  160. package/lib/module/assets/clear-icon@2x.png +0 -0
  161. package/lib/module/assets/clear-icon@3x.png +0 -0
  162. package/lib/module/assets/clear-icon@4x.png +0 -0
  163. package/lib/module/assets/close-icon@1x.png +0 -0
  164. package/lib/module/assets/close-icon@2x.png +0 -0
  165. package/lib/module/assets/close-icon@3x.png +0 -0
  166. package/lib/module/assets/close-icon@4x.png +0 -0
  167. package/lib/module/assets/search-icon-legacy@1x.ios.png +0 -0
  168. package/lib/module/assets/search-icon-legacy@2x.ios.png +0 -0
  169. package/lib/module/assets/search-icon-legacy@3x.ios.png +0 -0
  170. package/lib/module/assets/search-icon-legacy@4x.ios.png +0 -0
  171. package/lib/module/assets/search-icon.ios.svg +0 -4
  172. package/lib/module/assets/search-icon@1x.android.png +0 -0
  173. package/lib/module/assets/search-icon@1x.ios.png +0 -0
  174. package/lib/module/assets/search-icon@2x.android.png +0 -0
  175. package/lib/module/assets/search-icon@2x.ios.png +0 -0
  176. package/lib/module/assets/search-icon@3x.android.png +0 -0
  177. package/lib/module/assets/search-icon@3x.ios.png +0 -0
  178. package/lib/module/assets/search-icon@4x.android.png +0 -0
  179. package/lib/module/assets/search-icon@4x.ios.png +0 -0
  180. package/lib/typescript/src/Lazy.d.ts +0 -31
  181. package/lib/typescript/src/Lazy.d.ts.map +0 -1
  182. package/src/Lazy.tsx +0 -59
  183. package/src/assets/back-icon.ios.svg +0 -4
  184. package/src/assets/back-icon@1x.android.png +0 -0
  185. package/src/assets/back-icon@1x.ios.png +0 -0
  186. package/src/assets/back-icon@2x.android.png +0 -0
  187. package/src/assets/back-icon@2x.ios.png +0 -0
  188. package/src/assets/back-icon@3x.android.png +0 -0
  189. package/src/assets/back-icon@3x.ios.png +0 -0
  190. package/src/assets/back-icon@4x.android.png +0 -0
  191. package/src/assets/back-icon@4x.ios.png +0 -0
  192. package/src/assets/clear-icon@1x.png +0 -0
  193. package/src/assets/clear-icon@2x.png +0 -0
  194. package/src/assets/clear-icon@3x.png +0 -0
  195. package/src/assets/clear-icon@4x.png +0 -0
  196. package/src/assets/close-icon@1x.png +0 -0
  197. package/src/assets/close-icon@2x.png +0 -0
  198. package/src/assets/close-icon@3x.png +0 -0
  199. package/src/assets/close-icon@4x.png +0 -0
  200. package/src/assets/search-icon-legacy@1x.ios.png +0 -0
  201. package/src/assets/search-icon-legacy@2x.ios.png +0 -0
  202. package/src/assets/search-icon-legacy@3x.ios.png +0 -0
  203. package/src/assets/search-icon-legacy@4x.ios.png +0 -0
  204. package/src/assets/search-icon.ios.svg +0 -4
  205. package/src/assets/search-icon@1x.android.png +0 -0
  206. package/src/assets/search-icon@1x.ios.png +0 -0
  207. package/src/assets/search-icon@2x.android.png +0 -0
  208. package/src/assets/search-icon@2x.ios.png +0 -0
  209. package/src/assets/search-icon@3x.android.png +0 -0
  210. package/src/assets/search-icon@3x.ios.png +0 -0
  211. package/src/assets/search-icon@4x.android.png +0 -0
  212. package/src/assets/search-icon@4x.ios.png +0 -0
@@ -12,6 +12,6 @@ type FrameSizeProviderProps = {
12
12
  onLayout: (event: LayoutChangeEvent) => void;
13
13
  }) => React.ReactNode;
14
14
  };
15
- export declare function FrameSizeProvider({ initialFrame, render, }: FrameSizeProviderProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function FrameSizeProvider({ initialFrame, render, }: FrameSizeProviderProps): React.JSX.Element;
16
16
  export {};
17
17
  //# sourceMappingURL=useFrameSize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFrameSize.d.ts","sourceRoot":"","sources":["../../../src/useFrameSize.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,KAAK,iBAAiB,EAAY,IAAI,EAAE,MAAM,cAAc,CAAC;AAItE,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAgBF,wBAAgB,YAAY,CAAC,CAAC,EAC5B,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,EAC7B,QAAQ,CAAC,EAAE,OAAO,GACjB,CAAC,CAeH;AAED,KAAK,sBAAsB,GAAG;IAC5B,YAAY,EAAE,KAAK,CAAC;IACpB,MAAM,EAAE,CAAC,KAAK,EAAE;QACd,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;KAC9C,KAAK,KAAK,CAAC,SAAS,CAAC;CACvB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,YAAY,EACZ,MAAM,GACP,EAAE,sBAAsB,2CA4GxB"}
1
+ {"version":3,"file":"useFrameSize.d.ts","sourceRoot":"","sources":["../../../src/useFrameSize.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,KAAK,iBAAiB,EAAY,IAAI,EAAE,MAAM,cAAc,CAAC;AAMtE,KAAK,KAAK,GAAG;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAiBF,wBAAgB,YAAY,CAAC,CAAC,EAC5B,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,EAC7B,QAAQ,CAAC,EAAE,OAAO,GACjB,CAAC,CAeH;AAED,KAAK,sBAAsB,GAAG;IAC5B,YAAY,EAAE,KAAK,CAAC;IACpB,MAAM,EAAE,CAAC,KAAK,EAAE;QACd,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;KAC9C,KAAK,KAAK,CAAC,SAAS,CAAC;CACvB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,YAAY,EACZ,MAAM,GACP,EAAE,sBAAsB,qBA4GxB"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-navigation/elements",
3
3
  "description": "UI Components for React Navigation",
4
- "version": "3.0.0-alpha.4",
4
+ "version": "3.0.0-alpha.40",
5
5
  "keywords": [
6
6
  "react-native",
7
7
  "react-navigation",
@@ -22,12 +22,12 @@
22
22
  "types": "./lib/typescript/src/index.d.ts",
23
23
  "exports": {
24
24
  ".": {
25
- "source": "./src/index.tsx",
25
+ "@react-navigation/source": "./src/index.tsx",
26
26
  "types": "./lib/typescript/src/index.d.ts",
27
27
  "default": "./lib/module/index.js"
28
28
  },
29
29
  "./internal": {
30
- "source": "./src/internal.tsx",
30
+ "@react-navigation/source": "./src/internal.tsx",
31
31
  "types": "./lib/typescript/src/internal.d.ts",
32
32
  "default": "./lib/module/internal.js"
33
33
  },
@@ -47,32 +47,33 @@
47
47
  "clean": "del lib"
48
48
  },
49
49
  "dependencies": {
50
- "color": "^4.2.3",
51
- "use-latest-callback": "^0.3.2",
52
- "use-sync-external-store": "^1.5.0"
50
+ "sf-symbols-typescript": "^2.2.0",
51
+ "use-latest-callback": "^0.3.4",
52
+ "use-sync-external-store": "^1.6.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@callstack/liquid-glass": "^0.6.0",
56
- "@jest/globals": "^30.0.0",
57
- "@react-navigation/native": "^8.0.0-alpha.4",
58
- "@testing-library/react-native": "^13.2.1",
59
- "@types/react": "~19.1.10",
55
+ "@callstack/liquid-glass": "^0.8.0",
56
+ "@jest/globals": "^29.7.0",
57
+ "@react-navigation/native": "^8.0.0-alpha.36",
58
+ "@testing-library/react-native": "^14.0.0",
59
+ "@types/react": "~19.2.17",
60
60
  "@types/use-sync-external-store": "^1.5.0",
61
- "del-cli": "^6.0.0",
62
- "react": "19.1.0",
63
- "react-native": "0.81.4",
64
- "react-native-builder-bob": "^0.40.12",
65
- "react-native-screens": "^4.20.0",
66
- "react-test-renderer": "19.1.0",
67
- "typescript": "^5.9.2"
61
+ "del-cli": "^7.0.0",
62
+ "react": "19.2.3",
63
+ "react-native": "0.85.3",
64
+ "react-native-builder-bob": "^0.43.0",
65
+ "react-native-safe-area-context": "~5.7.0",
66
+ "react-native-screens": "^4.26.2",
67
+ "test-renderer": "1.2.0",
68
+ "typescript": "^6.0.3"
68
69
  },
69
70
  "peerDependencies": {
70
71
  "@callstack/liquid-glass": ">= 0.6.0",
71
- "@react-navigation/native": "^8.0.0-alpha.4",
72
- "react": ">= 19.0.0",
72
+ "@react-navigation/native": "^8.0.0-alpha.36",
73
+ "react": ">= 19.2.0",
73
74
  "react-native": "*",
74
75
  "react-native-safe-area-context": ">= 5.5.0",
75
- "react-native-screens": ">= 4.19.0"
76
+ "react-native-screens": ">= 4.25.0"
76
77
  },
77
78
  "react-native-builder-bob": {
78
79
  "source": "src",
@@ -92,5 +93,5 @@
92
93
  ]
93
94
  ]
94
95
  },
95
- "gitHead": "cd1c673cd6b8f43b992b76e8ed6cf6c5c85bdd6c"
96
+ "gitHead": "0a69dc7df922ebec7f6e60a6f4eac753545e3977"
96
97
  }
@@ -0,0 +1,90 @@
1
+ import { Activity, useEffect, useState } from 'react';
2
+ import {
3
+ type HostComponent,
4
+ NativeComponentRegistry,
5
+ type ViewProps,
6
+ } from 'react-native';
7
+
8
+ // eslint-disable-next-line import-x/extensions
9
+ import type { Props } from './ActivityView.tsx';
10
+ import { Container } from './Container';
11
+
12
+ export function ActivityView({
13
+ mode,
14
+ visible,
15
+ delay = 500,
16
+ style,
17
+ children,
18
+ }: Props) {
19
+ const [delayedMode, setDelayedMode] = useState(mode);
20
+
21
+ useEffect(() => {
22
+ if (!delay) {
23
+ return;
24
+ }
25
+
26
+ const timer = setTimeout(() => {
27
+ setDelayedMode(mode);
28
+ }, delay);
29
+
30
+ return () => clearTimeout(timer);
31
+ }, [delay, mode]);
32
+
33
+ const display = visible ? 'flex' : 'none';
34
+ const activityMode =
35
+ mode !== 'paused' || (delay && delayedMode !== 'paused')
36
+ ? 'visible'
37
+ : 'hidden';
38
+
39
+ return (
40
+ <Activity mode={activityMode}>
41
+ <ActivityContentView style={{ display: 'contents' }}>
42
+ <Container
43
+ inert={mode !== 'normal'}
44
+ style={{
45
+ ...style,
46
+ /**
47
+ * The visibility of the nested view is controlled by `Activity`
48
+ * It'll be overridden to `display: 'none'` when `mode="hidden"` regardless of what we set
49
+ * So we set the visibility on another view instead
50
+ */
51
+ display,
52
+ }}
53
+ >
54
+ {children}
55
+ </Container>
56
+ </ActivityContentView>
57
+ </Activity>
58
+ );
59
+ }
60
+
61
+ const STYLE: Record<string, true | { process?: (arg1: any) => any }> = {
62
+ display: {
63
+ /**
64
+ * React `Activity` sets `display: 'none'` when `mode="hidden"` to unmount effects
65
+ * But we want to keep the content visible, so we switch it to `display: 'contents'`
66
+ */
67
+ process: () => 'contents',
68
+ },
69
+ };
70
+
71
+ const VIEW_CONFIG = {
72
+ uiViewClassName: 'RCTView',
73
+ validAttributes: {
74
+ style: STYLE,
75
+ },
76
+ };
77
+
78
+ type ActivityContentViewProps = Omit<ViewProps, 'style'> & {
79
+ style?:
80
+ | {
81
+ display?: 'contents' | undefined;
82
+ }
83
+ | undefined;
84
+ };
85
+
86
+ const ActivityContentView: HostComponent<ActivityContentViewProps> =
87
+ NativeComponentRegistry.get<ActivityContentViewProps>(
88
+ 'ReactNavigationActivityContentView',
89
+ () => VIEW_CONFIG
90
+ );
@@ -0,0 +1,129 @@
1
+ import { Activity, useCallback, useEffect, useState } from 'react';
2
+ import { Platform, View, type ViewStyle } from 'react-native';
3
+
4
+ import { Container } from './Container';
5
+
6
+ export type Props = {
7
+ /**
8
+ * Mode of the activity view
9
+ * - `normal`: The view renders normally
10
+ * - `inert`: Content is not interactive
11
+ * - `paused`: Effects are unmounted and content is not interactive
12
+ */
13
+ mode: 'normal' | 'inert' | 'paused';
14
+ /**
15
+ * Whether the content is visible or not
16
+ */
17
+ visible: boolean;
18
+ /**
19
+ * Delay before pausing effects.
20
+ * So pending animations have time to finish.
21
+ *
22
+ * Defaults to 500ms.
23
+ */
24
+ delay?: number | undefined;
25
+ /**
26
+ * The style for the container view
27
+ */
28
+ style?: Omit<React.CSSProperties & ViewStyle, 'display'> | undefined;
29
+ /**
30
+ * The content of the activity view
31
+ */
32
+ children: React.ReactNode;
33
+ };
34
+
35
+ export function ActivityView({
36
+ mode,
37
+ visible,
38
+ delay = 500,
39
+ style,
40
+ children,
41
+ }: Props) {
42
+ const [delayedMode, setDelayedMode] = useState(mode);
43
+
44
+ useEffect(() => {
45
+ if (!delay) {
46
+ return;
47
+ }
48
+
49
+ const timer = setTimeout(() => {
50
+ setDelayedMode(mode);
51
+ }, delay);
52
+
53
+ return () => clearTimeout(timer);
54
+ }, [delay, mode]);
55
+
56
+ const display = visible ? 'flex' : 'none';
57
+ const activityMode =
58
+ mode !== 'paused' || (delay && delayedMode !== 'paused')
59
+ ? 'visible'
60
+ : 'hidden';
61
+
62
+ /**
63
+ * Activity has 2 modes, visible and hidden - hidden unmounts effects
64
+ * But what we want is to unmount effects, without hiding content
65
+ * So we use hidden mode, but unset display: none to make content visible
66
+ */
67
+ const onRef = useCallback(
68
+ (node: HTMLDivElement | View | null) => {
69
+ if (Platform.OS !== 'web' || !(node && node instanceof HTMLElement)) {
70
+ return;
71
+ }
72
+
73
+ const observers: MutationObserver[] = [];
74
+
75
+ const observe = () => {
76
+ // Remove previous observers
77
+ observers.forEach((o) => o.disconnect());
78
+ observers.length = 0;
79
+
80
+ const children = node.childNodes;
81
+
82
+ // When the style attribute for children is updated by React
83
+ // We observe it and update display to make content visible
84
+ children.forEach((child) => {
85
+ if (child instanceof HTMLElement) {
86
+ child.style.display = display;
87
+
88
+ const o = new MutationObserver(() => {
89
+ child.style.display = display;
90
+ });
91
+
92
+ o.observe(child, {
93
+ attributes: true,
94
+ attributeFilter: ['style'],
95
+ });
96
+
97
+ observers.push(o);
98
+ }
99
+ });
100
+ };
101
+
102
+ observe();
103
+
104
+ // React removes refs when `Activity` is hidden
105
+ // So we render outside of the `Activity` and observer child list
106
+ const observer = new MutationObserver(observe);
107
+
108
+ observer.observe(node, {
109
+ childList: true,
110
+ });
111
+
112
+ return () => {
113
+ observer.disconnect();
114
+ observers.forEach((o) => o.disconnect());
115
+ };
116
+ },
117
+ [display]
118
+ );
119
+
120
+ return (
121
+ <Container ref={onRef} style={{ display: 'contents' }}>
122
+ <Activity mode={activityMode}>
123
+ <Container inert={mode !== 'normal'} style={{ ...style, display }}>
124
+ {children}
125
+ </Container>
126
+ </Activity>
127
+ </Container>
128
+ );
129
+ }
package/src/Badge.tsx CHANGED
@@ -11,7 +11,7 @@ import {
11
11
 
12
12
  import { Color } from './Color';
13
13
 
14
- type Props = TextProps & {
14
+ type Props = Omit<TextProps, 'style'> & {
15
15
  /**
16
16
  * Whether the badge is visible
17
17
  */
@@ -19,15 +19,15 @@ type Props = TextProps & {
19
19
  /**
20
20
  * Content of the `Badge`.
21
21
  */
22
- children?: string | number;
22
+ children?: string | number | undefined;
23
23
  /**
24
24
  * Size of the `Badge`.
25
25
  */
26
- size?: number;
26
+ size?: number | undefined;
27
27
  /**
28
28
  * Style object for the tab bar container.
29
29
  */
30
- style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
30
+ style?: Animated.WithAnimatedValue<StyleProp<TextStyle>> | undefined;
31
31
  };
32
32
 
33
33
  const useNativeDriver = Platform.OS !== 'web';
@@ -49,17 +49,19 @@ export function Badge({
49
49
  return;
50
50
  }
51
51
 
52
- Animated.timing(opacity, {
52
+ const animation = Animated.timing(opacity, {
53
53
  toValue: visible ? 1 : 0,
54
54
  duration: 150,
55
55
  useNativeDriver,
56
- }).start(({ finished }) => {
56
+ });
57
+
58
+ animation.start(({ finished }) => {
57
59
  if (finished && !visible) {
58
60
  setRendered(false);
59
61
  }
60
62
  });
61
63
 
62
- return () => opacity.stopAnimation();
64
+ return () => animation.stop();
63
65
  }, [opacity, rendered, visible]);
64
66
 
65
67
  if (!rendered) {
@@ -73,7 +75,7 @@ export function Badge({
73
75
  // @ts-expect-error: backgroundColor definitely exists
74
76
  const { backgroundColor = colors.notification, ...restStyle } =
75
77
  StyleSheet.flatten(style) || {};
76
- const textColor = Color(backgroundColor)?.isLight() ? 'black' : 'white';
78
+ const textColor = Color.foreground(backgroundColor);
77
79
 
78
80
  const borderRadius = size / 2;
79
81
  const fontSize = Math.floor((size * 3) / 4);
@@ -31,7 +31,8 @@ export function BlurEffectBackground({
31
31
  children,
32
32
  ...rest
33
33
  }: Props) {
34
- let containerStyle, blurStyle, blurBackground, colorBackground;
34
+ let blurStyle: (ViewStyle & React.CSSProperties) | undefined;
35
+ let containerStyle, blurBackground, colorBackground;
35
36
 
36
37
  if (Platform.OS === 'web' && blurEffect && blurEffect !== 'none') {
37
38
  const blurBackgroundColor = getBlurBackgroundColor(blurEffect);
@@ -40,9 +41,8 @@ export function BlurEffectBackground({
40
41
  const backdropFilter = `saturate(180%) blur(30px)`;
41
42
 
42
43
  blurStyle = {
43
- // @ts-expect-error backdropFilter is web-only
44
44
  backdropFilter,
45
- webkitBackdropFilter: backdropFilter,
45
+ WebkitBackdropFilter: backdropFilter,
46
46
  };
47
47
 
48
48
  blurBackground = (
package/src/Button.tsx CHANGED
@@ -1,71 +1,156 @@
1
1
  import {
2
2
  type LinkProps,
3
+ type ParamListBase,
3
4
  type RootParamList,
4
5
  useLinkProps,
5
6
  useTheme,
6
7
  } from '@react-navigation/native';
7
8
  import * as React from 'react';
8
- import { type ColorValue, Platform, StyleSheet } from 'react-native';
9
+ import {
10
+ ActivityIndicator,
11
+ type ColorValue,
12
+ Platform,
13
+ StyleSheet,
14
+ View,
15
+ } from 'react-native';
9
16
 
10
17
  import { Color } from './Color';
18
+ import { PlatformIcon } from './PlatformIcon';
11
19
  import {
12
20
  PlatformPressable,
13
21
  type Props as PlatformPressableProps,
14
22
  } from './PlatformPressable';
15
23
  import { Text } from './Text';
24
+ import type { Icon } from './types';
16
25
 
17
26
  type ButtonBaseProps = Omit<PlatformPressableProps, 'children'> & {
18
- variant?: 'plain' | 'tinted' | 'filled';
19
- color?: ColorValue;
20
- children: string | string[];
27
+ /**
28
+ * Variant of the button.
29
+ *
30
+ * - `plain`: transparent background, colored text
31
+ * - `tinted`: lightly tinted background, colored text
32
+ * - `filled`: colored background, contrasting text color
33
+ *
34
+ * @default 'tinted'
35
+ */
36
+ variant?: 'plain' | 'tinted' | 'filled' | undefined;
37
+ /**
38
+ * Custom color for the button.
39
+ * Changes the text color for `plain` and `tinted` variants,
40
+ * and background color for `filled` variant.
41
+ */
42
+ color?: ColorValue | undefined;
43
+ /**
44
+ * Icon to display before the label.
45
+ *
46
+ * Supported types:
47
+ * - image: custom image source
48
+ * - sfSymbol: SF Symbol icon (iOS only)
49
+ * - materialSymbol: material symbol icon (Android only)
50
+ * - React Node: function that returns a React Element
51
+ */
52
+ icon?:
53
+ | Icon
54
+ | ((props: { color: ColorValue; size: number }) => Icon | React.ReactNode)
55
+ | undefined;
56
+ /**
57
+ * Whether to show a loading indicator instead of the icon.
58
+ */
59
+ loading?: boolean | undefined;
60
+ /**
61
+ * Label text to display inside the button.
62
+ */
63
+ children: string | string[] | React.ReactElement | React.ReactElement[];
21
64
  };
22
65
 
23
66
  type ButtonLinkProps<
24
67
  ParamList extends {} = RootParamList,
25
- RouteName extends keyof ParamList = keyof ParamList,
26
- > = LinkProps<ParamList, RouteName> & Omit<ButtonBaseProps, 'onPress'>;
68
+ RouteName extends Extract<keyof ParamList, string> = Extract<
69
+ keyof ParamList,
70
+ string
71
+ >,
72
+ > = LinkProps<NoInfer<ParamList>, RouteName> & ButtonBaseProps;
27
73
 
28
74
  const BUTTON_RADIUS = 40;
75
+ const ICON_SIZE = 14;
29
76
 
77
+ /**
78
+ * Component to render a button that navigates to a screen using a path.
79
+ * Uses an anchor tag on the web.
80
+ *
81
+ * @param props.in Name of the current or parent screen whose navigator contains the target screen.
82
+ * @param props.screen Name of the screen to navigate to (e.g. `'Feeds'`).
83
+ * @param props.params Params to pass to the screen to navigate to (e.g. `{ sort: 'hot' }`).
84
+ * @param props.href Optional absolute path to use for the href (e.g. `/feeds/hot`).
85
+ * @param props.action Optional action to override the in-page navigation. The `href` is still derived from `screen`, so this can be used to render a link while dispatching a different action (e.g. a `replace`).
86
+ */
30
87
  export function Button<
31
88
  ParamList extends {} = RootParamList,
32
- RouteName extends keyof ParamList = keyof ParamList,
89
+ RouteName extends Extract<keyof ParamList, string> = Extract<
90
+ keyof ParamList,
91
+ string
92
+ >,
33
93
  >(props: ButtonLinkProps<ParamList, RouteName>): React.JSX.Element;
34
94
 
95
+ /**
96
+ * Component to render a button.
97
+ */
35
98
  export function Button(props: ButtonBaseProps): React.JSX.Element;
36
99
 
37
- export function Button<
38
- ParamList extends {} = RootParamList,
39
- RouteName extends keyof ParamList = keyof ParamList,
40
- >(props: ButtonBaseProps | ButtonLinkProps<ParamList, RouteName>) {
100
+ export function Button(
101
+ props: ButtonBaseProps | ButtonLinkProps<ParamListBase>
102
+ ) {
41
103
  if ('screen' in props || 'action' in props) {
42
- // @ts-expect-error: This is already type-checked by the prop types
43
104
  return <ButtonLink {...props} />;
44
105
  } else {
45
106
  return <ButtonBase {...props} />;
46
107
  }
47
108
  }
48
109
 
49
- function ButtonLink<
50
- const ParamList extends {} = RootParamList,
51
- const RouteName extends keyof ParamList = keyof ParamList,
52
- >({
110
+ function ButtonLink({
111
+ in: parent,
53
112
  screen,
54
113
  params,
55
114
  action,
56
115
  href,
116
+ onPress,
57
117
  ...rest
58
- }: ButtonLinkProps<ParamList, RouteName>) {
59
- // @ts-expect-error: This is already type-checked by the prop types
60
- const props = useLinkProps({ screen, params, action, href });
118
+ }: ButtonLinkProps<ParamListBase>) {
119
+ // @ts-expect-error: destructuring loses the relationship between target props
120
+ const props = useLinkProps({ in: parent, screen, params, action, href });
121
+
122
+ const [isPending, startTransition] = React.useTransition();
123
+
124
+ // Avoid flashing the loading indicator when the transition is fast
125
+ const isPendingDeferred = React.useDeferredValue(isPending);
126
+
127
+ return (
128
+ <ButtonBase
129
+ {...rest}
130
+ {...props}
131
+ loading={
132
+ typeof rest.loading === 'boolean'
133
+ ? rest.loading
134
+ : isPending && isPendingDeferred
135
+ }
136
+ onPress={(e) => {
137
+ onPress?.(e);
61
138
 
62
- return <ButtonBase {...rest} {...props} />;
139
+ startTransition(() => {
140
+ props.onPress(e);
141
+ });
142
+ }}
143
+ />
144
+ );
63
145
  }
64
146
 
65
147
  function ButtonBase({
66
148
  variant = 'tinted',
67
149
  color: customColor,
150
+ icon,
151
+ loading = false,
68
152
  android_ripple,
153
+ disabled,
69
154
  style,
70
155
  children,
71
156
  ...rest
@@ -90,18 +175,39 @@ function ButtonBase({
90
175
  break;
91
176
  case 'filled':
92
177
  backgroundColor = color;
93
- textColor = Color(color)?.isDark()
94
- ? 'white'
95
- : (Color(color)?.darken(0.71).string() ?? '#fff');
178
+ textColor = Color.foreground(backgroundColor);
96
179
  break;
97
180
  }
98
181
 
182
+ if (disabled || loading) {
183
+ textColor = dark ? 'rgba(235, 235, 245, 0.3)' : 'rgba(60, 60, 67, 0.3)';
184
+
185
+ if (variant !== 'plain') {
186
+ backgroundColor = dark
187
+ ? 'rgba(118, 118, 128, 0.24)'
188
+ : 'rgba(118, 118, 128, 0.12)';
189
+ }
190
+ }
191
+
192
+ const rippleColor =
193
+ Color(textColor)?.fade(0.88).string() ??
194
+ (variant === 'filled' || dark
195
+ ? 'rgba(255, 255, 255, 0.12)'
196
+ : 'rgba(0, 0, 0, 0.12)');
197
+
198
+ const iconNode = loading ? (
199
+ <ActivityIndicator color={textColor} size={ICON_SIZE} />
200
+ ) : (
201
+ renderIcon(icon, textColor, ICON_SIZE)
202
+ );
203
+
99
204
  return (
100
205
  <PlatformPressable
101
206
  {...rest}
207
+ disabled={disabled || loading}
102
208
  android_ripple={{
103
- radius: BUTTON_RADIUS,
104
- color: Color(textColor)?.fade(0.85).string() ?? 'rgba(0, 0, 0, 0.1)',
209
+ foreground: true,
210
+ color: rippleColor,
105
211
  ...android_ripple,
106
212
  }}
107
213
  pressOpacity={Platform.OS === 'ios' ? undefined : 1}
@@ -110,19 +216,58 @@ function ButtonBase({
110
216
  }
111
217
  style={[{ backgroundColor }, styles.button, style]}
112
218
  >
113
- <Text style={[{ color: textColor }, fonts.regular, styles.text]}>
114
- {children}
115
- </Text>
219
+ <View style={styles.content}>
220
+ {iconNode && <View style={styles.icon}>{iconNode}</View>}
221
+ <Text style={[{ color: textColor }, fonts.regular, styles.text]}>
222
+ {children}
223
+ </Text>
224
+ </View>
116
225
  </PlatformPressable>
117
226
  );
118
227
  }
119
228
 
229
+ function renderIcon(
230
+ icon: ButtonBaseProps['icon'],
231
+ color: ColorValue,
232
+ size: number
233
+ ) {
234
+ if (!icon) {
235
+ return null;
236
+ }
237
+
238
+ const iconValue = typeof icon === 'function' ? icon({ color, size }) : icon;
239
+
240
+ if (React.isValidElement(iconValue)) {
241
+ return iconValue;
242
+ }
243
+
244
+ if (
245
+ typeof iconValue === 'object' &&
246
+ iconValue != null &&
247
+ 'type' in iconValue
248
+ ) {
249
+ return <PlatformIcon icon={iconValue} size={size} color={color} />;
250
+ }
251
+
252
+ return null;
253
+ }
254
+
120
255
  const styles = StyleSheet.create({
121
256
  button: {
122
257
  paddingHorizontal: 24,
123
258
  paddingVertical: 10,
124
259
  borderRadius: BUTTON_RADIUS,
125
260
  borderCurve: 'continuous',
261
+ overflow: 'hidden',
262
+ },
263
+ content: {
264
+ flexDirection: 'row',
265
+ alignItems: 'center',
266
+ justifyContent: 'center',
267
+ gap: 8,
268
+ },
269
+ icon: {
270
+ marginLeft: -6,
126
271
  },
127
272
  text: {
128
273
  fontSize: 14,