@react-navigation/native-stack 8.0.0-alpha.3 → 8.0.0-alpha.30
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/lib/module/index.js.map +1 -1
- package/lib/module/navigators/createNativeStackNavigator.js +4 -8
- package/lib/module/navigators/createNativeStackNavigator.js.map +1 -1
- package/lib/module/utils/useAnimatedHeaderHeight.js +1 -1
- package/lib/module/utils/useAnimatedHeaderHeight.js.map +1 -1
- package/lib/module/views/NativeStackView.js +34 -24
- package/lib/module/views/NativeStackView.js.map +1 -1
- package/lib/module/views/NativeStackView.native.js +92 -68
- package/lib/module/views/NativeStackView.native.js.map +1 -1
- package/lib/module/views/useHeaderConfigProps.js +71 -21
- package/lib/module/views/useHeaderConfigProps.js.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/navigators/createNativeStackNavigator.d.ts +8 -14
- package/lib/typescript/src/navigators/createNativeStackNavigator.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +181 -146
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/views/NativeStackView.d.ts.map +1 -1
- package/lib/typescript/src/views/NativeStackView.native.d.ts.map +1 -1
- package/lib/typescript/src/views/useHeaderConfigProps.d.ts.map +1 -1
- package/package.json +17 -18
- package/src/index.tsx +1 -0
- package/src/navigators/createNativeStackNavigator.tsx +11 -47
- package/src/types.tsx +242 -184
- package/src/utils/useAnimatedHeaderHeight.tsx +1 -1
- package/src/views/NativeStackView.native.tsx +135 -89
- package/src/views/NativeStackView.tsx +52 -40
- package/src/views/useHeaderConfigProps.tsx +100 -36
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { getHeaderTitle, HeaderTitle } from '@react-navigation/elements';
|
|
2
2
|
import { Color } from '@react-navigation/elements/internal';
|
|
3
3
|
import {
|
|
4
|
+
MaterialSymbol,
|
|
4
5
|
type Route,
|
|
5
6
|
type Theme,
|
|
6
7
|
useLocale,
|
|
7
8
|
useTheme,
|
|
8
9
|
} from '@react-navigation/native';
|
|
10
|
+
import { useMemo } from 'react';
|
|
9
11
|
import { Platform, StyleSheet, type TextStyle, View } from 'react-native';
|
|
10
12
|
import {
|
|
11
|
-
type HeaderBarButtonItem,
|
|
12
13
|
type HeaderBarButtonItemMenuAction,
|
|
13
14
|
type HeaderBarButtonItemSubmenu,
|
|
15
|
+
type HeaderBarButtonItemWithAction,
|
|
16
|
+
type HeaderBarButtonItemWithMenu,
|
|
14
17
|
isSearchBarAvailableForCurrentPlatform,
|
|
15
18
|
ScreenStackHeaderBackButtonImage,
|
|
16
19
|
ScreenStackHeaderCenterView,
|
|
@@ -23,6 +26,7 @@ import {
|
|
|
23
26
|
|
|
24
27
|
import type {
|
|
25
28
|
NativeStackHeaderItem,
|
|
29
|
+
NativeStackHeaderItemButton,
|
|
26
30
|
NativeStackHeaderItemMenuAction,
|
|
27
31
|
NativeStackHeaderItemMenuSubmenu,
|
|
28
32
|
NativeStackNavigationOptions,
|
|
@@ -35,6 +39,8 @@ type Props = NativeStackNavigationOptions & {
|
|
|
35
39
|
route: Route<string>;
|
|
36
40
|
};
|
|
37
41
|
|
|
42
|
+
const ICON_SIZE = 24;
|
|
43
|
+
|
|
38
44
|
const processBarButtonItems = (
|
|
39
45
|
items: NativeStackHeaderItem[] | undefined,
|
|
40
46
|
colors: Theme['colors'],
|
|
@@ -70,7 +76,7 @@ const processBarButtonItems = (
|
|
|
70
76
|
|
|
71
77
|
const { badge, label, labelStyle, icon, ...rest } = item;
|
|
72
78
|
|
|
73
|
-
|
|
79
|
+
const processedItemCommon = {
|
|
74
80
|
...rest,
|
|
75
81
|
index,
|
|
76
82
|
title: label,
|
|
@@ -78,40 +84,43 @@ const processBarButtonItems = (
|
|
|
78
84
|
...fonts.regular,
|
|
79
85
|
...labelStyle,
|
|
80
86
|
},
|
|
81
|
-
icon:
|
|
82
|
-
icon?.type === 'image'
|
|
83
|
-
? icon.tinted === false
|
|
84
|
-
? {
|
|
85
|
-
type: 'imageSource',
|
|
86
|
-
imageSource: icon.source,
|
|
87
|
-
}
|
|
88
|
-
: {
|
|
89
|
-
type: 'templateSource',
|
|
90
|
-
templateSource: icon.source,
|
|
91
|
-
}
|
|
92
|
-
: icon,
|
|
87
|
+
icon: transformIcon(icon),
|
|
93
88
|
};
|
|
94
89
|
|
|
95
|
-
|
|
90
|
+
let processedItem:
|
|
91
|
+
| HeaderBarButtonItemWithAction
|
|
92
|
+
| HeaderBarButtonItemWithMenu;
|
|
93
|
+
|
|
94
|
+
if (processedItemCommon.type === 'menu' && item.type === 'menu') {
|
|
96
95
|
const { multiselectable, layout } = item.menu;
|
|
97
96
|
|
|
98
97
|
processedItem = {
|
|
99
|
-
...
|
|
98
|
+
...processedItemCommon,
|
|
100
99
|
menu: {
|
|
101
|
-
...
|
|
102
|
-
singleSelection:
|
|
100
|
+
...processedItemCommon.menu,
|
|
101
|
+
singleSelection:
|
|
102
|
+
typeof multiselectable === 'boolean'
|
|
103
|
+
? !multiselectable
|
|
104
|
+
: undefined,
|
|
103
105
|
displayAsPalette: layout === 'palette',
|
|
104
106
|
items: item.menu.items.map(getMenuItem),
|
|
105
107
|
},
|
|
106
108
|
};
|
|
109
|
+
} else if (
|
|
110
|
+
processedItemCommon.type === 'button' &&
|
|
111
|
+
item.type === 'button'
|
|
112
|
+
) {
|
|
113
|
+
processedItem = processedItemCommon;
|
|
114
|
+
} else {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Invalid item type: ${JSON.stringify(item)}. Valid types are 'button' and 'menu'.`
|
|
117
|
+
);
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
if (badge) {
|
|
110
121
|
const badgeBackgroundColor =
|
|
111
122
|
badge.style?.backgroundColor ?? colors.notification;
|
|
112
|
-
const badgeTextColor = Color(badgeBackgroundColor)
|
|
113
|
-
? 'black'
|
|
114
|
-
: 'white';
|
|
123
|
+
const badgeTextColor = Color.foreground(badgeBackgroundColor);
|
|
115
124
|
|
|
116
125
|
processedItem = {
|
|
117
126
|
...processedItem,
|
|
@@ -138,26 +147,44 @@ const processBarButtonItems = (
|
|
|
138
147
|
.filter((item) => item != null);
|
|
139
148
|
};
|
|
140
149
|
|
|
150
|
+
const transformIcon = (
|
|
151
|
+
icon: NativeStackHeaderItemButton['icon']
|
|
152
|
+
):
|
|
153
|
+
| HeaderBarButtonItemWithAction['icon']
|
|
154
|
+
| HeaderBarButtonItemWithMenu['icon'] => {
|
|
155
|
+
if (icon?.type === 'image') {
|
|
156
|
+
return icon.tinted === false
|
|
157
|
+
? { type: 'imageSource', imageSource: icon.source }
|
|
158
|
+
: { type: 'templateSource', templateSource: icon.source };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return icon;
|
|
162
|
+
};
|
|
163
|
+
|
|
141
164
|
const getMenuItem = (
|
|
142
165
|
item: NativeStackHeaderItemMenuAction | NativeStackHeaderItemMenuSubmenu
|
|
143
166
|
): HeaderBarButtonItemMenuAction | HeaderBarButtonItemSubmenu => {
|
|
144
167
|
if (item.type === 'submenu') {
|
|
145
|
-
const { label, inline, layout, items, multiselectable, ...rest } =
|
|
168
|
+
const { label, icon, inline, layout, items, multiselectable, ...rest } =
|
|
169
|
+
item;
|
|
146
170
|
|
|
147
171
|
return {
|
|
148
172
|
...rest,
|
|
173
|
+
icon: transformIcon(icon),
|
|
149
174
|
title: label,
|
|
150
175
|
displayAsPalette: layout === 'palette',
|
|
151
176
|
displayInline: inline,
|
|
152
|
-
singleSelection:
|
|
177
|
+
singleSelection:
|
|
178
|
+
typeof multiselectable === 'boolean' ? !multiselectable : undefined,
|
|
153
179
|
items: items.map(getMenuItem),
|
|
154
180
|
};
|
|
155
181
|
}
|
|
156
182
|
|
|
157
|
-
const { label, description, ...rest } = item;
|
|
183
|
+
const { label, icon, description, ...rest } = item;
|
|
158
184
|
|
|
159
185
|
return {
|
|
160
186
|
...rest,
|
|
187
|
+
icon: transformIcon(icon),
|
|
161
188
|
title: label,
|
|
162
189
|
subtitle: description,
|
|
163
190
|
};
|
|
@@ -216,6 +243,15 @@ export function useHeaderConfigProps({
|
|
|
216
243
|
const headerStyleFlattened = StyleSheet.flatten(headerStyle) || {};
|
|
217
244
|
const headerLargeStyleFlattened = StyleSheet.flatten(headerLargeStyle) || {};
|
|
218
245
|
|
|
246
|
+
const headerBackgroundColor =
|
|
247
|
+
headerStyleFlattened.backgroundColor ??
|
|
248
|
+
(headerBackground != null ||
|
|
249
|
+
headerTransparent ||
|
|
250
|
+
// The title becomes invisible if background color is set with large title on iOS 26
|
|
251
|
+
(Platform.OS === 'ios' && headerLargeTitleEnabled)
|
|
252
|
+
? 'transparent'
|
|
253
|
+
: colors.card);
|
|
254
|
+
|
|
219
255
|
const backTitleFontSize =
|
|
220
256
|
'fontSize' in headerBackTitleStyleFlattened
|
|
221
257
|
? headerBackTitleStyleFlattened.fontSize
|
|
@@ -226,7 +262,13 @@ export function useHeaderConfigProps({
|
|
|
226
262
|
const titleColor =
|
|
227
263
|
'color' in headerTitleStyleFlattened
|
|
228
264
|
? headerTitleStyleFlattened.color
|
|
229
|
-
:
|
|
265
|
+
: Platform.OS === 'ios' &&
|
|
266
|
+
(headerTransparent || headerBackgroundColor === 'transparent')
|
|
267
|
+
? // On iOS 26, we want header title to change color based on content underneath
|
|
268
|
+
// So we don't set an explicit color when header is transparent
|
|
269
|
+
// Unless a custom tint color is explicitly provided
|
|
270
|
+
headerTintColor
|
|
271
|
+
: (headerTintColor ?? colors.text);
|
|
230
272
|
const titleFontSize =
|
|
231
273
|
'fontSize' in headerTitleStyleFlattened
|
|
232
274
|
? headerTitleStyleFlattened.fontSize
|
|
@@ -260,15 +302,6 @@ export function useHeaderConfigProps({
|
|
|
260
302
|
headerTitleStyleSupported.fontWeight = titleFontWeight;
|
|
261
303
|
}
|
|
262
304
|
|
|
263
|
-
const headerBackgroundColor =
|
|
264
|
-
headerStyleFlattened.backgroundColor ??
|
|
265
|
-
(headerBackground != null ||
|
|
266
|
-
headerTransparent ||
|
|
267
|
-
// The title becomes invisible if background color is set with large title on iOS 26
|
|
268
|
-
(Platform.OS === 'ios' && headerLargeTitleEnabled)
|
|
269
|
-
? 'transparent'
|
|
270
|
-
: colors.card);
|
|
271
|
-
|
|
272
305
|
const canGoBack = headerBack != null;
|
|
273
306
|
|
|
274
307
|
const headerLeftElement = headerLeft?.({
|
|
@@ -342,6 +375,37 @@ export function useHeaderConfigProps({
|
|
|
342
375
|
rightItems = [...rightItems].reverse();
|
|
343
376
|
}
|
|
344
377
|
|
|
378
|
+
const backImageSource = useMemo(() => {
|
|
379
|
+
if (headerBackIcon == null && Platform.OS === 'android') {
|
|
380
|
+
try {
|
|
381
|
+
// Use Material Symbol as default back icon on Android
|
|
382
|
+
// So it's consistent with other material icons
|
|
383
|
+
// Based on the available variant and weight
|
|
384
|
+
return MaterialSymbol.getImageSource({
|
|
385
|
+
name: 'arrow_back',
|
|
386
|
+
color: tintColor,
|
|
387
|
+
size: ICON_SIZE,
|
|
388
|
+
});
|
|
389
|
+
} catch (e) {
|
|
390
|
+
// Fallback to default if symbol is not available
|
|
391
|
+
// This can happen if no font, or multiple fonts are available
|
|
392
|
+
// Or in tests where native module is not available
|
|
393
|
+
}
|
|
394
|
+
} else if (headerBackIcon?.type === 'image') {
|
|
395
|
+
return headerBackIcon.source;
|
|
396
|
+
} else if (headerBackIcon?.type === 'materialSymbol') {
|
|
397
|
+
return MaterialSymbol.getImageSource({
|
|
398
|
+
name: headerBackIcon.name,
|
|
399
|
+
variant: headerBackIcon.variant,
|
|
400
|
+
weight: headerBackIcon.weight,
|
|
401
|
+
color: tintColor,
|
|
402
|
+
size: ICON_SIZE,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return undefined;
|
|
407
|
+
}, [headerBackIcon, tintColor]);
|
|
408
|
+
|
|
345
409
|
const children = (
|
|
346
410
|
<>
|
|
347
411
|
{Platform.OS === 'ios' ? (
|
|
@@ -417,8 +481,8 @@ export function useHeaderConfigProps({
|
|
|
417
481
|
) : null}
|
|
418
482
|
</>
|
|
419
483
|
)}
|
|
420
|
-
{
|
|
421
|
-
<ScreenStackHeaderBackButtonImage source={
|
|
484
|
+
{backImageSource != null ? (
|
|
485
|
+
<ScreenStackHeaderBackButtonImage source={backImageSource} />
|
|
422
486
|
) : null}
|
|
423
487
|
{Platform.OS === 'ios' && rightItems ? (
|
|
424
488
|
rightItems.map((item, index) => {
|