@react-navigation/elements 3.0.0-alpha.5 → 3.0.0-alpha.51
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/ActivityView.js +90 -0
- package/lib/module/ActivityView.js.map +1 -0
- package/lib/module/ActivityView.native.js +67 -0
- package/lib/module/ActivityView.native.js.map +1 -0
- package/lib/module/Badge.js +6 -7
- package/lib/module/Badge.js.map +1 -1
- package/lib/module/BlurEffectBackground.js +3 -3
- package/lib/module/BlurEffectBackground.js.map +1 -1
- package/lib/module/Button.js +99 -14
- package/lib/module/Button.js.map +1 -1
- package/lib/module/Color.js +565 -4
- package/lib/module/Color.js.map +1 -1
- package/lib/module/Container.js +23 -1
- package/lib/module/Container.js.map +1 -1
- package/lib/module/Header/Header.js +30 -18
- package/lib/module/Header/Header.js.map +1 -1
- package/lib/module/Header/HeaderBackButton.js +27 -13
- package/lib/module/Header/HeaderBackButton.js.map +1 -1
- package/lib/module/Header/HeaderButton.js +4 -5
- package/lib/module/Header/HeaderButton.js.map +1 -1
- package/lib/module/Header/HeaderButtonBackground.js +1 -2
- package/lib/module/Header/HeaderButtonBackground.js.map +1 -1
- package/lib/module/Header/HeaderIcon.js +14 -17
- package/lib/module/Header/HeaderIcon.js.map +1 -1
- package/lib/module/Header/HeaderSearchBar.js +36 -26
- package/lib/module/Header/HeaderSearchBar.js.map +1 -1
- package/lib/module/Header/getHeaderTitle.js.map +1 -1
- package/lib/module/Header/useHeaderHeight.js +1 -1
- package/lib/module/Header/useHeaderHeight.js.map +1 -1
- package/lib/module/Label/getLabel.js.map +1 -1
- package/lib/module/LiquidGlassView.ios.js +1 -2
- package/lib/module/LiquidGlassView.ios.js.map +1 -1
- package/lib/module/LiquidGlassView.js +0 -4
- package/lib/module/LiquidGlassView.js.map +1 -1
- package/lib/module/PlatformColor.js +1 -0
- package/lib/module/PlatformColor.js.map +1 -1
- package/lib/module/PlatformColor.native.js +1 -1
- package/lib/module/PlatformColor.native.js.map +1 -1
- package/lib/module/PlatformIcon.js +46 -0
- package/lib/module/PlatformIcon.js.map +1 -0
- package/lib/module/PlatformPressable.js +64 -21
- package/lib/module/PlatformPressable.js.map +1 -1
- package/lib/module/SafeAreaProviderCompat.js +2 -4
- package/lib/module/SafeAreaProviderCompat.js.map +1 -1
- package/lib/module/Screen.js +59 -20
- package/lib/module/Screen.js.map +1 -1
- package/lib/module/index.js +2 -7
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal.js +1 -1
- package/lib/module/internal.js.map +1 -1
- package/lib/module/useFrameSize.js +55 -5
- package/lib/module/useFrameSize.js.map +1 -1
- package/lib/typescript/src/ActivityView.d.ts +31 -0
- package/lib/typescript/src/ActivityView.d.ts.map +1 -0
- package/lib/typescript/src/ActivityView.native.d.ts +3 -0
- package/lib/typescript/src/ActivityView.native.d.ts.map +1 -0
- package/lib/typescript/src/Badge.d.ts +7 -6
- package/lib/typescript/src/Badge.d.ts.map +1 -1
- package/lib/typescript/src/BlurEffectBackground.d.ts +2 -2
- package/lib/typescript/src/BlurEffectBackground.d.ts.map +1 -1
- package/lib/typescript/src/Button.d.ts +54 -6
- package/lib/typescript/src/Button.d.ts.map +1 -1
- package/lib/typescript/src/Color.d.ts +4 -4
- package/lib/typescript/src/Color.d.ts.map +1 -1
- package/lib/typescript/src/Container.d.ts +8 -4
- package/lib/typescript/src/Container.d.ts.map +1 -1
- package/lib/typescript/src/Header/Header.d.ts +5 -4
- package/lib/typescript/src/Header/Header.d.ts.map +1 -1
- package/lib/typescript/src/Header/HeaderBackButton.d.ts +3 -2
- package/lib/typescript/src/Header/HeaderBackButton.d.ts.map +1 -1
- package/lib/typescript/src/Header/HeaderBackground.d.ts +5 -5
- package/lib/typescript/src/Header/HeaderBackground.d.ts.map +1 -1
- package/lib/typescript/src/Header/HeaderButton.d.ts +7 -2
- package/lib/typescript/src/Header/HeaderButton.d.ts.map +1 -1
- package/lib/typescript/src/Header/HeaderButtonBackground.d.ts +1 -1
- package/lib/typescript/src/Header/HeaderButtonBackground.d.ts.map +1 -1
- package/lib/typescript/src/Header/HeaderIcon.d.ts +10 -4
- package/lib/typescript/src/Header/HeaderIcon.d.ts.map +1 -1
- package/lib/typescript/src/Header/HeaderSearchBar.d.ts +10 -7
- package/lib/typescript/src/Header/HeaderSearchBar.d.ts.map +1 -1
- package/lib/typescript/src/Header/HeaderTitle.d.ts +4 -4
- package/lib/typescript/src/Header/HeaderTitle.d.ts.map +1 -1
- package/lib/typescript/src/Header/getHeaderTitle.d.ts +2 -2
- package/lib/typescript/src/Header/getHeaderTitle.d.ts.map +1 -1
- package/lib/typescript/src/Label/Label.d.ts +1 -1
- package/lib/typescript/src/Label/Label.d.ts.map +1 -1
- package/lib/typescript/src/Label/getLabel.d.ts +2 -2
- package/lib/typescript/src/Label/getLabel.d.ts.map +1 -1
- package/lib/typescript/src/LiquidGlassView.d.ts +7 -5
- package/lib/typescript/src/LiquidGlassView.d.ts.map +1 -1
- package/lib/typescript/src/LiquidGlassView.ios.d.ts +4 -2
- package/lib/typescript/src/LiquidGlassView.ios.d.ts.map +1 -1
- package/lib/typescript/src/MissingIcon.d.ts +3 -3
- package/lib/typescript/src/MissingIcon.d.ts.map +1 -1
- package/lib/typescript/src/PlatformColor.d.ts +1 -0
- package/lib/typescript/src/PlatformColor.d.ts.map +1 -1
- package/lib/typescript/src/PlatformColor.native.d.ts +1 -1
- package/lib/typescript/src/PlatformColor.native.d.ts.map +1 -1
- package/lib/typescript/src/PlatformIcon.d.ts +11 -0
- package/lib/typescript/src/PlatformIcon.d.ts.map +1 -0
- package/lib/typescript/src/PlatformPressable.d.ts +12 -15
- package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
- package/lib/typescript/src/SafeAreaProviderCompat.d.ts +1 -1
- package/lib/typescript/src/SafeAreaProviderCompat.d.ts.map +1 -1
- package/lib/typescript/src/Screen.d.ts +10 -7
- package/lib/typescript/src/Screen.d.ts.map +1 -1
- package/lib/typescript/src/Text.d.ts +1 -1
- package/lib/typescript/src/Text.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +21 -21
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/internal.d.ts +6 -6
- package/lib/typescript/src/internal.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +120 -70
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useFrameSize.d.ts +2 -2
- package/lib/typescript/src/useFrameSize.d.ts.map +1 -1
- package/package.json +27 -22
- package/src/ActivityView.native.tsx +88 -0
- package/src/ActivityView.tsx +127 -0
- package/src/Badge.tsx +11 -10
- package/src/BlurEffectBackground.tsx +3 -3
- package/src/Button.tsx +177 -28
- package/src/Color.tsx +755 -8
- package/src/Container.tsx +27 -4
- package/src/Header/Header.tsx +47 -27
- package/src/Header/HeaderBackButton.tsx +34 -20
- package/src/Header/HeaderBackground.tsx +3 -3
- package/src/Header/HeaderButton.tsx +16 -18
- package/src/Header/HeaderButtonBackground.tsx +1 -4
- package/src/Header/HeaderIcon.tsx +26 -13
- package/src/Header/HeaderSearchBar.tsx +58 -49
- package/src/Header/HeaderTitle.tsx +3 -3
- package/src/Header/getHeaderTitle.tsx +4 -1
- package/src/Header/useHeaderHeight.tsx +1 -1
- package/src/Label/getLabel.tsx +1 -1
- package/src/LiquidGlassView.ios.tsx +14 -9
- package/src/LiquidGlassView.tsx +15 -11
- package/src/MissingIcon.tsx +2 -2
- package/src/PlatformColor.native.tsx +1 -1
- package/src/PlatformColor.tsx +4 -0
- package/src/PlatformIcon.tsx +70 -0
- package/src/PlatformPressable.tsx +108 -48
- package/src/SafeAreaProviderCompat.tsx +2 -3
- package/src/Screen.tsx +87 -29
- package/src/index.tsx +2 -16
- package/src/internal.tsx +1 -1
- package/src/types.tsx +153 -76
- package/src/useFrameSize.tsx +71 -7
- package/lib/module/Lazy.js +0 -42
- package/lib/module/Lazy.js.map +0 -1
- package/lib/module/assets/back-icon.ios.svg +0 -4
- 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@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@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-legacy@1x.ios.png +0 -0
- package/lib/module/assets/search-icon-legacy@2x.ios.png +0 -0
- package/lib/module/assets/search-icon-legacy@3x.ios.png +0 -0
- package/lib/module/assets/search-icon-legacy@4x.ios.png +0 -0
- package/lib/module/assets/search-icon.ios.svg +0 -4
- 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/typescript/src/Lazy.d.ts +0 -31
- package/lib/typescript/src/Lazy.d.ts.map +0 -1
- package/src/Lazy.tsx +0 -59
- package/src/assets/back-icon.ios.svg +0 -4
- 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@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@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-legacy@1x.ios.png +0 -0
- package/src/assets/search-icon-legacy@2x.ios.png +0 -0
- package/src/assets/search-icon-legacy@3x.ios.png +0 -0
- package/src/assets/search-icon-legacy@4x.ios.png +0 -0
- package/src/assets/search-icon.ios.svg +0 -4
- 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
|
@@ -0,0 +1,127 @@
|
|
|
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 [delayedPaused, setDelayedPaused] = useState(mode === 'paused');
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!delay) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const timer = setTimeout(() => {
|
|
50
|
+
setDelayedPaused(mode === 'paused');
|
|
51
|
+
}, delay);
|
|
52
|
+
|
|
53
|
+
return () => clearTimeout(timer);
|
|
54
|
+
}, [delay, mode]);
|
|
55
|
+
|
|
56
|
+
const display = visible ? 'flex' : 'none';
|
|
57
|
+
const activityMode =
|
|
58
|
+
mode !== 'paused' || (delay && !delayedPaused) ? 'visible' : 'hidden';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Activity has 2 modes, visible and hidden - hidden unmounts effects
|
|
62
|
+
* But what we want is to unmount effects, without hiding content
|
|
63
|
+
* So we use hidden mode, but unset display: none to make content visible
|
|
64
|
+
*/
|
|
65
|
+
const onRef = useCallback(
|
|
66
|
+
(node: HTMLDivElement | React.ComponentRef<typeof View> | null) => {
|
|
67
|
+
if (Platform.OS !== 'web' || !(node && node instanceof HTMLElement)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const observers: MutationObserver[] = [];
|
|
72
|
+
|
|
73
|
+
const observe = () => {
|
|
74
|
+
// Remove previous observers
|
|
75
|
+
observers.forEach((o) => o.disconnect());
|
|
76
|
+
observers.length = 0;
|
|
77
|
+
|
|
78
|
+
const children = node.childNodes;
|
|
79
|
+
|
|
80
|
+
// When the style attribute for children is updated by React
|
|
81
|
+
// We observe it and update display to make content visible
|
|
82
|
+
children.forEach((child) => {
|
|
83
|
+
if (child instanceof HTMLElement) {
|
|
84
|
+
child.style.display = display;
|
|
85
|
+
|
|
86
|
+
const o = new MutationObserver(() => {
|
|
87
|
+
child.style.display = display;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
o.observe(child, {
|
|
91
|
+
attributes: true,
|
|
92
|
+
attributeFilter: ['style'],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
observers.push(o);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
observe();
|
|
101
|
+
|
|
102
|
+
// React removes refs when `Activity` is hidden
|
|
103
|
+
// So we render outside of the `Activity` and observer child list
|
|
104
|
+
const observer = new MutationObserver(observe);
|
|
105
|
+
|
|
106
|
+
observer.observe(node, {
|
|
107
|
+
childList: true,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
observer.disconnect();
|
|
112
|
+
observers.forEach((o) => o.disconnect());
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
[display]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<Container ref={onRef} style={{ display: 'contents' }}>
|
|
120
|
+
<Activity mode={activityMode}>
|
|
121
|
+
<Container inert={mode !== 'normal'} style={{ ...style, display }}>
|
|
122
|
+
{children}
|
|
123
|
+
</Container>
|
|
124
|
+
</Activity>
|
|
125
|
+
</Container>
|
|
126
|
+
);
|
|
127
|
+
}
|
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?:
|
|
30
|
+
style?: 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
|
-
})
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
animation.start(({ finished }) => {
|
|
57
59
|
if (finished && !visible) {
|
|
58
60
|
setRendered(false);
|
|
59
61
|
}
|
|
60
62
|
});
|
|
61
63
|
|
|
62
|
-
return () =>
|
|
64
|
+
return () => animation.stop();
|
|
63
65
|
}, [opacity, rendered, visible]);
|
|
64
66
|
|
|
65
67
|
if (!rendered) {
|
|
@@ -70,10 +72,9 @@ export function Badge({
|
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
// @ts-expect-error: backgroundColor definitely exists
|
|
74
75
|
const { backgroundColor = colors.notification, ...restStyle } =
|
|
75
|
-
StyleSheet.flatten(style)
|
|
76
|
-
const textColor = Color(backgroundColor)
|
|
76
|
+
StyleSheet.flatten(style) ?? {};
|
|
77
|
+
const textColor = Color.foreground(backgroundColor);
|
|
77
78
|
|
|
78
79
|
const borderRadius = size / 2;
|
|
79
80
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 =
|
|
26
|
-
|
|
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 =
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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<
|
|
59
|
-
// @ts-expect-error:
|
|
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();
|
|
61
123
|
|
|
62
|
-
|
|
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);
|
|
138
|
+
|
|
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,43 @@ function ButtonBase({
|
|
|
90
175
|
break;
|
|
91
176
|
case 'filled':
|
|
92
177
|
backgroundColor = color;
|
|
93
|
-
textColor = Color(
|
|
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
|
+
aria-disabled={
|
|
208
|
+
// Keep the button focusable while loading
|
|
209
|
+
disabled || loading || undefined
|
|
210
|
+
}
|
|
211
|
+
disabled={disabled}
|
|
102
212
|
android_ripple={{
|
|
103
|
-
|
|
104
|
-
color:
|
|
213
|
+
foreground: true,
|
|
214
|
+
color: rippleColor,
|
|
105
215
|
...android_ripple,
|
|
106
216
|
}}
|
|
107
217
|
pressOpacity={Platform.OS === 'ios' ? undefined : 1}
|
|
@@ -110,19 +220,58 @@ function ButtonBase({
|
|
|
110
220
|
}
|
|
111
221
|
style={[{ backgroundColor }, styles.button, style]}
|
|
112
222
|
>
|
|
113
|
-
<
|
|
114
|
-
{
|
|
115
|
-
|
|
223
|
+
<View style={styles.content}>
|
|
224
|
+
{iconNode && <View style={styles.icon}>{iconNode}</View>}
|
|
225
|
+
<Text style={[{ color: textColor }, fonts.regular, styles.text]}>
|
|
226
|
+
{children}
|
|
227
|
+
</Text>
|
|
228
|
+
</View>
|
|
116
229
|
</PlatformPressable>
|
|
117
230
|
);
|
|
118
231
|
}
|
|
119
232
|
|
|
233
|
+
function renderIcon(
|
|
234
|
+
icon: ButtonBaseProps['icon'],
|
|
235
|
+
color: ColorValue,
|
|
236
|
+
size: number
|
|
237
|
+
) {
|
|
238
|
+
if (!icon) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const iconValue = typeof icon === 'function' ? icon({ color, size }) : icon;
|
|
243
|
+
|
|
244
|
+
if (React.isValidElement(iconValue)) {
|
|
245
|
+
return iconValue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (
|
|
249
|
+
typeof iconValue === 'object' &&
|
|
250
|
+
iconValue != null &&
|
|
251
|
+
'type' in iconValue
|
|
252
|
+
) {
|
|
253
|
+
return <PlatformIcon icon={iconValue} size={size} color={color} />;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
120
259
|
const styles = StyleSheet.create({
|
|
121
260
|
button: {
|
|
122
261
|
paddingHorizontal: 24,
|
|
123
262
|
paddingVertical: 10,
|
|
124
263
|
borderRadius: BUTTON_RADIUS,
|
|
125
264
|
borderCurve: 'continuous',
|
|
265
|
+
overflow: 'hidden',
|
|
266
|
+
},
|
|
267
|
+
content: {
|
|
268
|
+
flexDirection: 'row',
|
|
269
|
+
alignItems: 'center',
|
|
270
|
+
justifyContent: 'center',
|
|
271
|
+
gap: 8,
|
|
272
|
+
},
|
|
273
|
+
icon: {
|
|
274
|
+
marginLeft: -6,
|
|
126
275
|
},
|
|
127
276
|
text: {
|
|
128
277
|
fontSize: 14,
|