expo-template-default 55.1.2 → 55.1.4
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/package.json +11 -11
- package/src/components/animated-icon.tsx +58 -34
- package/src/components/{animated-icon.native.tsx → animated-icon.web.tsx} +34 -58
- package/src/components/app-tabs.tsx +17 -106
- package/src/components/app-tabs.web.tsx +116 -0
- package/src/components/app-tabs.native.tsx +0 -27
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "expo-template-default",
|
|
3
3
|
"license": "0BSD",
|
|
4
4
|
"main": "expo-router/entry",
|
|
5
|
-
"version": "55.1.
|
|
5
|
+
"version": "55.1.4",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "expo start",
|
|
8
8
|
"reset-project": "node ./scripts/reset-project.js",
|
|
@@ -16,19 +16,19 @@
|
|
|
16
16
|
"@react-navigation/bottom-tabs": "^7.7.3",
|
|
17
17
|
"@react-navigation/elements": "^2.8.1",
|
|
18
18
|
"@react-navigation/native": "^7.1.28",
|
|
19
|
-
"expo": "~55.0.0-preview.
|
|
20
|
-
"expo-constants": "~55.0.
|
|
21
|
-
"expo-device": "~55.0.
|
|
19
|
+
"expo": "~55.0.0-preview.10",
|
|
20
|
+
"expo-constants": "~55.0.4",
|
|
21
|
+
"expo-device": "~55.0.6",
|
|
22
22
|
"expo-font": "~55.0.3",
|
|
23
|
-
"expo-glass-effect": "~55.0.
|
|
23
|
+
"expo-glass-effect": "~55.0.5",
|
|
24
24
|
"expo-image": "~55.0.3",
|
|
25
|
-
"expo-linking": "~55.0.
|
|
26
|
-
"expo-router": "~55.0.0-preview.
|
|
27
|
-
"expo-splash-screen": "~55.0.
|
|
25
|
+
"expo-linking": "~55.0.4",
|
|
26
|
+
"expo-router": "~55.0.0-preview.7",
|
|
27
|
+
"expo-splash-screen": "~55.0.5",
|
|
28
28
|
"expo-status-bar": "~55.0.2",
|
|
29
29
|
"expo-symbols": "~55.0.3",
|
|
30
|
-
"expo-system-ui": "~55.0.
|
|
31
|
-
"expo-web-browser": "~55.0.
|
|
30
|
+
"expo-system-ui": "~55.0.5",
|
|
31
|
+
"expo-web-browser": "~55.0.5",
|
|
32
32
|
"react": "19.2.0",
|
|
33
33
|
"react-dom": "19.2.0",
|
|
34
34
|
"react-native": "0.83.1",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"react-native-worklets": "0.7.2",
|
|
37
37
|
"react-native-reanimated": "~4.2.1",
|
|
38
38
|
"react-native-safe-area-context": "~5.6.2",
|
|
39
|
-
"react-native-screens": "~4.
|
|
39
|
+
"react-native-screens": "~4.22.0",
|
|
40
40
|
"react-native-web": "~0.21.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
@@ -1,53 +1,79 @@
|
|
|
1
1
|
import { Image } from 'expo-image';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Dimensions, StyleSheet, View } from 'react-native';
|
|
4
|
+
import Animated, { Easing, Keyframe } from 'react-native-reanimated';
|
|
5
|
+
import { scheduleOnRN } from 'react-native-worklets';
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
const DURATION =
|
|
7
|
+
const INITIAL_SCALE_FACTOR = Dimensions.get('screen').height / 90;
|
|
8
|
+
const DURATION = 600;
|
|
9
|
+
|
|
10
|
+
const splashKeyframe = new Keyframe({
|
|
11
|
+
0: {
|
|
12
|
+
transform: [{ scale: INITIAL_SCALE_FACTOR }],
|
|
13
|
+
opacity: 1,
|
|
14
|
+
},
|
|
15
|
+
20: {
|
|
16
|
+
opacity: 1,
|
|
17
|
+
},
|
|
18
|
+
70: {
|
|
19
|
+
opacity: 0,
|
|
20
|
+
easing: Easing.elastic(0.7),
|
|
21
|
+
},
|
|
22
|
+
100: {
|
|
23
|
+
opacity: 0,
|
|
24
|
+
transform: [{ scale: 1 }],
|
|
25
|
+
easing: Easing.elastic(0.7),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
7
28
|
|
|
8
29
|
export function AnimatedSplashOverlay() {
|
|
9
|
-
|
|
30
|
+
const [visible, setVisible] = useState(true);
|
|
31
|
+
|
|
32
|
+
if (!visible) return null;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Animated.View
|
|
36
|
+
entering={splashKeyframe.duration(DURATION).withCallback((finished) => {
|
|
37
|
+
'worklet';
|
|
38
|
+
if (finished) {
|
|
39
|
+
scheduleOnRN(setVisible, false);
|
|
40
|
+
}
|
|
41
|
+
})}
|
|
42
|
+
style={styles.backgroundSolidColor}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
10
45
|
}
|
|
11
46
|
|
|
12
47
|
const keyframe = new Keyframe({
|
|
13
48
|
0: {
|
|
14
|
-
transform: [{ scale:
|
|
15
|
-
},
|
|
16
|
-
60: {
|
|
17
|
-
transform: [{ scale: 1.2 }],
|
|
18
|
-
easing: Easing.elastic(1.2),
|
|
49
|
+
transform: [{ scale: INITIAL_SCALE_FACTOR }],
|
|
19
50
|
},
|
|
20
51
|
100: {
|
|
21
52
|
transform: [{ scale: 1 }],
|
|
22
|
-
easing: Easing.elastic(
|
|
53
|
+
easing: Easing.elastic(0.7),
|
|
23
54
|
},
|
|
24
55
|
});
|
|
25
56
|
|
|
26
57
|
const logoKeyframe = new Keyframe({
|
|
27
58
|
0: {
|
|
59
|
+
transform: [{ scale: 1.3 }],
|
|
28
60
|
opacity: 0,
|
|
29
61
|
},
|
|
30
|
-
|
|
31
|
-
transform: [{ scale: 1.
|
|
62
|
+
40: {
|
|
63
|
+
transform: [{ scale: 1.3 }],
|
|
32
64
|
opacity: 0,
|
|
33
|
-
easing: Easing.elastic(
|
|
65
|
+
easing: Easing.elastic(0.7),
|
|
34
66
|
},
|
|
35
67
|
100: {
|
|
36
|
-
transform: [{ scale: 1 }],
|
|
37
68
|
opacity: 1,
|
|
38
|
-
|
|
69
|
+
transform: [{ scale: 1 }],
|
|
70
|
+
easing: Easing.elastic(0.7),
|
|
39
71
|
},
|
|
40
72
|
});
|
|
41
73
|
|
|
42
74
|
const glowKeyframe = new Keyframe({
|
|
43
75
|
0: {
|
|
44
|
-
transform: [{ rotateZ: '
|
|
45
|
-
opacity: 0,
|
|
46
|
-
},
|
|
47
|
-
[DURATION / 1000]: {
|
|
48
|
-
transform: [{ rotateZ: '0deg' }, { scale: 1 }],
|
|
49
|
-
opacity: 1,
|
|
50
|
-
easing: Easing.elastic(0.7),
|
|
76
|
+
transform: [{ rotateZ: '0deg' }],
|
|
51
77
|
},
|
|
52
78
|
100: {
|
|
53
79
|
transform: [{ rotateZ: '7200deg' }],
|
|
@@ -61,10 +87,7 @@ export function AnimatedIcon() {
|
|
|
61
87
|
<Image style={styles.glow} source={require('@/assets/images/logo-glow.png')} />
|
|
62
88
|
</Animated.View>
|
|
63
89
|
|
|
64
|
-
<Animated.View
|
|
65
|
-
<div className={classes.expoLogoBackground} />
|
|
66
|
-
</Animated.View>
|
|
67
|
-
|
|
90
|
+
<Animated.View entering={keyframe.duration(DURATION)} style={styles.background} />
|
|
68
91
|
<Animated.View style={styles.imageContainer} entering={logoKeyframe.duration(DURATION)}>
|
|
69
92
|
<Image style={styles.image} source={require('@/assets/images/expo-logo.png')} />
|
|
70
93
|
</Animated.View>
|
|
@@ -73,13 +96,6 @@ export function AnimatedIcon() {
|
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
const styles = StyleSheet.create({
|
|
76
|
-
container: {
|
|
77
|
-
alignItems: 'center',
|
|
78
|
-
width: '100%',
|
|
79
|
-
zIndex: 1000,
|
|
80
|
-
position: 'absolute',
|
|
81
|
-
top: 128 / 2 + 138,
|
|
82
|
-
},
|
|
83
99
|
imageContainer: {
|
|
84
100
|
justifyContent: 'center',
|
|
85
101
|
alignItems: 'center',
|
|
@@ -94,6 +110,7 @@ const styles = StyleSheet.create({
|
|
|
94
110
|
alignItems: 'center',
|
|
95
111
|
width: 128,
|
|
96
112
|
height: 128,
|
|
113
|
+
zIndex: 100,
|
|
97
114
|
},
|
|
98
115
|
image: {
|
|
99
116
|
position: 'absolute',
|
|
@@ -101,8 +118,15 @@ const styles = StyleSheet.create({
|
|
|
101
118
|
height: 71,
|
|
102
119
|
},
|
|
103
120
|
background: {
|
|
121
|
+
borderRadius: 40,
|
|
122
|
+
experimental_backgroundImage: `linear-gradient(180deg, #3C9FFE, #0274DF)`,
|
|
104
123
|
width: 128,
|
|
105
124
|
height: 128,
|
|
106
125
|
position: 'absolute',
|
|
107
126
|
},
|
|
127
|
+
backgroundSolidColor: {
|
|
128
|
+
...StyleSheet.absoluteFillObject,
|
|
129
|
+
backgroundColor: '#208AEF',
|
|
130
|
+
zIndex: 1000,
|
|
131
|
+
},
|
|
108
132
|
});
|
|
@@ -1,79 +1,53 @@
|
|
|
1
1
|
import { Image } from 'expo-image';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import Animated, { Easing, Keyframe } from 'react-native-reanimated';
|
|
5
|
-
import { scheduleOnRN } from 'react-native-worklets';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import Animated, { Keyframe, Easing } from 'react-native-reanimated';
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
const DURATION =
|
|
9
|
-
|
|
10
|
-
const splashKeyframe = new Keyframe({
|
|
11
|
-
0: {
|
|
12
|
-
transform: [{ scale: INITIAL_SCALE_FACTOR }],
|
|
13
|
-
opacity: 1,
|
|
14
|
-
},
|
|
15
|
-
20: {
|
|
16
|
-
opacity: 1,
|
|
17
|
-
},
|
|
18
|
-
70: {
|
|
19
|
-
opacity: 0,
|
|
20
|
-
easing: Easing.elastic(0.7),
|
|
21
|
-
},
|
|
22
|
-
100: {
|
|
23
|
-
opacity: 0,
|
|
24
|
-
transform: [{ scale: 1 }],
|
|
25
|
-
easing: Easing.elastic(0.7),
|
|
26
|
-
},
|
|
27
|
-
});
|
|
5
|
+
import classes from './animated-icon.module.css';
|
|
6
|
+
const DURATION = 300;
|
|
28
7
|
|
|
29
8
|
export function AnimatedSplashOverlay() {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!visible) return null;
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<Animated.View
|
|
36
|
-
entering={splashKeyframe.duration(DURATION).withCallback((finished) => {
|
|
37
|
-
'worklet';
|
|
38
|
-
if (finished) {
|
|
39
|
-
scheduleOnRN(setVisible, false);
|
|
40
|
-
}
|
|
41
|
-
})}
|
|
42
|
-
style={styles.backgroundSolidColor}
|
|
43
|
-
/>
|
|
44
|
-
);
|
|
9
|
+
return null;
|
|
45
10
|
}
|
|
46
11
|
|
|
47
12
|
const keyframe = new Keyframe({
|
|
48
13
|
0: {
|
|
49
|
-
transform: [{ scale:
|
|
14
|
+
transform: [{ scale: 0 }],
|
|
15
|
+
},
|
|
16
|
+
60: {
|
|
17
|
+
transform: [{ scale: 1.2 }],
|
|
18
|
+
easing: Easing.elastic(1.2),
|
|
50
19
|
},
|
|
51
20
|
100: {
|
|
52
21
|
transform: [{ scale: 1 }],
|
|
53
|
-
easing: Easing.elastic(
|
|
22
|
+
easing: Easing.elastic(1.2),
|
|
54
23
|
},
|
|
55
24
|
});
|
|
56
25
|
|
|
57
26
|
const logoKeyframe = new Keyframe({
|
|
58
27
|
0: {
|
|
59
|
-
transform: [{ scale: 1.3 }],
|
|
60
28
|
opacity: 0,
|
|
61
29
|
},
|
|
62
|
-
|
|
63
|
-
transform: [{ scale: 1.
|
|
30
|
+
60: {
|
|
31
|
+
transform: [{ scale: 1.2 }],
|
|
64
32
|
opacity: 0,
|
|
65
|
-
easing: Easing.elastic(
|
|
33
|
+
easing: Easing.elastic(1.2),
|
|
66
34
|
},
|
|
67
35
|
100: {
|
|
68
|
-
opacity: 1,
|
|
69
36
|
transform: [{ scale: 1 }],
|
|
70
|
-
|
|
37
|
+
opacity: 1,
|
|
38
|
+
easing: Easing.elastic(1.2),
|
|
71
39
|
},
|
|
72
40
|
});
|
|
73
41
|
|
|
74
42
|
const glowKeyframe = new Keyframe({
|
|
75
43
|
0: {
|
|
76
|
-
transform: [{ rotateZ: '
|
|
44
|
+
transform: [{ rotateZ: '-180deg' }, { scale: 0.8 }],
|
|
45
|
+
opacity: 0,
|
|
46
|
+
},
|
|
47
|
+
[DURATION / 1000]: {
|
|
48
|
+
transform: [{ rotateZ: '0deg' }, { scale: 1 }],
|
|
49
|
+
opacity: 1,
|
|
50
|
+
easing: Easing.elastic(0.7),
|
|
77
51
|
},
|
|
78
52
|
100: {
|
|
79
53
|
transform: [{ rotateZ: '7200deg' }],
|
|
@@ -87,7 +61,10 @@ export function AnimatedIcon() {
|
|
|
87
61
|
<Image style={styles.glow} source={require('@/assets/images/logo-glow.png')} />
|
|
88
62
|
</Animated.View>
|
|
89
63
|
|
|
90
|
-
<Animated.View entering={keyframe.duration(DURATION)}
|
|
64
|
+
<Animated.View style={styles.background} entering={keyframe.duration(DURATION)}>
|
|
65
|
+
<div className={classes.expoLogoBackground} />
|
|
66
|
+
</Animated.View>
|
|
67
|
+
|
|
91
68
|
<Animated.View style={styles.imageContainer} entering={logoKeyframe.duration(DURATION)}>
|
|
92
69
|
<Image style={styles.image} source={require('@/assets/images/expo-logo.png')} />
|
|
93
70
|
</Animated.View>
|
|
@@ -96,6 +73,13 @@ export function AnimatedIcon() {
|
|
|
96
73
|
}
|
|
97
74
|
|
|
98
75
|
const styles = StyleSheet.create({
|
|
76
|
+
container: {
|
|
77
|
+
alignItems: 'center',
|
|
78
|
+
width: '100%',
|
|
79
|
+
zIndex: 1000,
|
|
80
|
+
position: 'absolute',
|
|
81
|
+
top: 128 / 2 + 138,
|
|
82
|
+
},
|
|
99
83
|
imageContainer: {
|
|
100
84
|
justifyContent: 'center',
|
|
101
85
|
alignItems: 'center',
|
|
@@ -110,7 +94,6 @@ const styles = StyleSheet.create({
|
|
|
110
94
|
alignItems: 'center',
|
|
111
95
|
width: 128,
|
|
112
96
|
height: 128,
|
|
113
|
-
zIndex: 100,
|
|
114
97
|
},
|
|
115
98
|
image: {
|
|
116
99
|
position: 'absolute',
|
|
@@ -118,15 +101,8 @@ const styles = StyleSheet.create({
|
|
|
118
101
|
height: 71,
|
|
119
102
|
},
|
|
120
103
|
background: {
|
|
121
|
-
borderRadius: 40,
|
|
122
|
-
experimental_backgroundImage: `linear-gradient(180deg, #3C9FFE, #0274DF)`,
|
|
123
104
|
width: 128,
|
|
124
105
|
height: 128,
|
|
125
106
|
position: 'absolute',
|
|
126
107
|
},
|
|
127
|
-
backgroundSolidColor: {
|
|
128
|
-
...StyleSheet.absoluteFillObject,
|
|
129
|
-
backgroundColor: '#208AEF',
|
|
130
|
-
zIndex: 1000,
|
|
131
|
-
},
|
|
132
108
|
});
|
|
@@ -1,116 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Tabs,
|
|
3
|
-
TabList,
|
|
4
|
-
TabTrigger,
|
|
5
|
-
TabSlot,
|
|
6
|
-
TabTriggerSlotProps,
|
|
7
|
-
TabListProps,
|
|
8
|
-
} from 'expo-router/ui';
|
|
9
|
-
import { SymbolView } from 'expo-symbols';
|
|
1
|
+
import { NativeTabs } from 'expo-router/unstable-native-tabs';
|
|
10
2
|
import React from 'react';
|
|
11
|
-
import {
|
|
3
|
+
import { useColorScheme } from 'react-native';
|
|
12
4
|
|
|
13
|
-
import {
|
|
14
|
-
import { ThemedText } from './themed-text';
|
|
15
|
-
import { ThemedView } from './themed-view';
|
|
16
|
-
|
|
17
|
-
import { Colors, MaxContentWidth, Spacing } from '@/constants/theme';
|
|
5
|
+
import { Colors } from '@/constants/theme';
|
|
18
6
|
|
|
19
7
|
export default function AppTabs() {
|
|
20
|
-
return (
|
|
21
|
-
<Tabs>
|
|
22
|
-
<TabSlot style={{ height: '100%' }} />
|
|
23
|
-
<TabList asChild>
|
|
24
|
-
<CustomTabList>
|
|
25
|
-
<TabTrigger name="home" href="/" asChild>
|
|
26
|
-
<TabButton>Home</TabButton>
|
|
27
|
-
</TabTrigger>
|
|
28
|
-
<TabTrigger name="explore" href="/explore" asChild>
|
|
29
|
-
<TabButton>Explore</TabButton>
|
|
30
|
-
</TabTrigger>
|
|
31
|
-
</CustomTabList>
|
|
32
|
-
</TabList>
|
|
33
|
-
</Tabs>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function TabButton({ children, isFocused, ...props }: TabTriggerSlotProps) {
|
|
38
|
-
return (
|
|
39
|
-
<Pressable {...props} style={({ pressed }) => pressed && styles.pressed}>
|
|
40
|
-
<ThemedView
|
|
41
|
-
type={isFocused ? 'backgroundSelected' : 'backgroundElement'}
|
|
42
|
-
style={styles.tabButtonView}>
|
|
43
|
-
<ThemedText type="small" themeColor={isFocused ? 'text' : 'textSecondary'}>
|
|
44
|
-
{children}
|
|
45
|
-
</ThemedText>
|
|
46
|
-
</ThemedView>
|
|
47
|
-
</Pressable>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function CustomTabList(props: TabListProps) {
|
|
52
8
|
const scheme = useColorScheme();
|
|
53
9
|
const colors = Colors[scheme === 'unspecified' ? 'light' : scheme];
|
|
54
10
|
|
|
55
11
|
return (
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
size={12}
|
|
71
|
-
/>
|
|
72
|
-
</Pressable>
|
|
73
|
-
</ExternalLink>
|
|
74
|
-
</ThemedView>
|
|
75
|
-
</View>
|
|
12
|
+
<NativeTabs
|
|
13
|
+
backgroundColor={colors.background}
|
|
14
|
+
indicatorColor={colors.backgroundElement}
|
|
15
|
+
labelStyle={{ selected: { color: colors.text } }}>
|
|
16
|
+
<NativeTabs.Trigger name="index">
|
|
17
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
18
|
+
<NativeTabs.Trigger.Icon src={require('@/assets/images/tabIcons/home.png')} />
|
|
19
|
+
</NativeTabs.Trigger>
|
|
20
|
+
|
|
21
|
+
<NativeTabs.Trigger name="explore">
|
|
22
|
+
<NativeTabs.Trigger.Label>Explore</NativeTabs.Trigger.Label>
|
|
23
|
+
<NativeTabs.Trigger.Icon src={require('@/assets/images/tabIcons/explore.png')} />
|
|
24
|
+
</NativeTabs.Trigger>
|
|
25
|
+
</NativeTabs>
|
|
76
26
|
);
|
|
77
27
|
}
|
|
78
|
-
|
|
79
|
-
const styles = StyleSheet.create({
|
|
80
|
-
tabListContainer: {
|
|
81
|
-
position: 'absolute',
|
|
82
|
-
width: '100%',
|
|
83
|
-
padding: Spacing.three,
|
|
84
|
-
justifyContent: 'center',
|
|
85
|
-
alignItems: 'center',
|
|
86
|
-
flexDirection: 'row',
|
|
87
|
-
},
|
|
88
|
-
innerContainer: {
|
|
89
|
-
paddingVertical: Spacing.two,
|
|
90
|
-
paddingHorizontal: Spacing.five,
|
|
91
|
-
borderRadius: Spacing.five,
|
|
92
|
-
flexDirection: 'row',
|
|
93
|
-
alignItems: 'center',
|
|
94
|
-
flexGrow: 1,
|
|
95
|
-
gap: Spacing.two,
|
|
96
|
-
maxWidth: MaxContentWidth,
|
|
97
|
-
},
|
|
98
|
-
brandText: {
|
|
99
|
-
marginRight: 'auto',
|
|
100
|
-
},
|
|
101
|
-
pressed: {
|
|
102
|
-
opacity: 0.7,
|
|
103
|
-
},
|
|
104
|
-
tabButtonView: {
|
|
105
|
-
paddingVertical: Spacing.one,
|
|
106
|
-
paddingHorizontal: Spacing.three,
|
|
107
|
-
borderRadius: Spacing.three,
|
|
108
|
-
},
|
|
109
|
-
externalPressable: {
|
|
110
|
-
flexDirection: 'row',
|
|
111
|
-
justifyContent: 'center',
|
|
112
|
-
alignItems: 'center',
|
|
113
|
-
gap: Spacing.one,
|
|
114
|
-
marginLeft: Spacing.three,
|
|
115
|
-
},
|
|
116
|
-
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Tabs,
|
|
3
|
+
TabList,
|
|
4
|
+
TabTrigger,
|
|
5
|
+
TabSlot,
|
|
6
|
+
TabTriggerSlotProps,
|
|
7
|
+
TabListProps,
|
|
8
|
+
} from 'expo-router/ui';
|
|
9
|
+
import { SymbolView } from 'expo-symbols';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { Pressable, useColorScheme, View, StyleSheet } from 'react-native';
|
|
12
|
+
|
|
13
|
+
import { ExternalLink } from './external-link';
|
|
14
|
+
import { ThemedText } from './themed-text';
|
|
15
|
+
import { ThemedView } from './themed-view';
|
|
16
|
+
|
|
17
|
+
import { Colors, MaxContentWidth, Spacing } from '@/constants/theme';
|
|
18
|
+
|
|
19
|
+
export default function AppTabs() {
|
|
20
|
+
return (
|
|
21
|
+
<Tabs>
|
|
22
|
+
<TabSlot style={{ height: '100%' }} />
|
|
23
|
+
<TabList asChild>
|
|
24
|
+
<CustomTabList>
|
|
25
|
+
<TabTrigger name="home" href="/" asChild>
|
|
26
|
+
<TabButton>Home</TabButton>
|
|
27
|
+
</TabTrigger>
|
|
28
|
+
<TabTrigger name="explore" href="/explore" asChild>
|
|
29
|
+
<TabButton>Explore</TabButton>
|
|
30
|
+
</TabTrigger>
|
|
31
|
+
</CustomTabList>
|
|
32
|
+
</TabList>
|
|
33
|
+
</Tabs>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function TabButton({ children, isFocused, ...props }: TabTriggerSlotProps) {
|
|
38
|
+
return (
|
|
39
|
+
<Pressable {...props} style={({ pressed }) => pressed && styles.pressed}>
|
|
40
|
+
<ThemedView
|
|
41
|
+
type={isFocused ? 'backgroundSelected' : 'backgroundElement'}
|
|
42
|
+
style={styles.tabButtonView}>
|
|
43
|
+
<ThemedText type="small" themeColor={isFocused ? 'text' : 'textSecondary'}>
|
|
44
|
+
{children}
|
|
45
|
+
</ThemedText>
|
|
46
|
+
</ThemedView>
|
|
47
|
+
</Pressable>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function CustomTabList(props: TabListProps) {
|
|
52
|
+
const scheme = useColorScheme();
|
|
53
|
+
const colors = Colors[scheme === 'unspecified' ? 'light' : scheme];
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<View {...props} style={styles.tabListContainer}>
|
|
57
|
+
<ThemedView type="backgroundElement" style={styles.innerContainer}>
|
|
58
|
+
<ThemedText type="smallBold" style={styles.brandText}>
|
|
59
|
+
Expo Starter
|
|
60
|
+
</ThemedText>
|
|
61
|
+
|
|
62
|
+
{props.children}
|
|
63
|
+
|
|
64
|
+
<ExternalLink href="https://docs.expo.dev" asChild>
|
|
65
|
+
<Pressable style={styles.externalPressable}>
|
|
66
|
+
<ThemedText type="link">Doc</ThemedText>
|
|
67
|
+
<SymbolView
|
|
68
|
+
tintColor={colors.text}
|
|
69
|
+
name={{ ios: 'arrow.up.right.square', web: 'link' }}
|
|
70
|
+
size={12}
|
|
71
|
+
/>
|
|
72
|
+
</Pressable>
|
|
73
|
+
</ExternalLink>
|
|
74
|
+
</ThemedView>
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const styles = StyleSheet.create({
|
|
80
|
+
tabListContainer: {
|
|
81
|
+
position: 'absolute',
|
|
82
|
+
width: '100%',
|
|
83
|
+
padding: Spacing.three,
|
|
84
|
+
justifyContent: 'center',
|
|
85
|
+
alignItems: 'center',
|
|
86
|
+
flexDirection: 'row',
|
|
87
|
+
},
|
|
88
|
+
innerContainer: {
|
|
89
|
+
paddingVertical: Spacing.two,
|
|
90
|
+
paddingHorizontal: Spacing.five,
|
|
91
|
+
borderRadius: Spacing.five,
|
|
92
|
+
flexDirection: 'row',
|
|
93
|
+
alignItems: 'center',
|
|
94
|
+
flexGrow: 1,
|
|
95
|
+
gap: Spacing.two,
|
|
96
|
+
maxWidth: MaxContentWidth,
|
|
97
|
+
},
|
|
98
|
+
brandText: {
|
|
99
|
+
marginRight: 'auto',
|
|
100
|
+
},
|
|
101
|
+
pressed: {
|
|
102
|
+
opacity: 0.7,
|
|
103
|
+
},
|
|
104
|
+
tabButtonView: {
|
|
105
|
+
paddingVertical: Spacing.one,
|
|
106
|
+
paddingHorizontal: Spacing.three,
|
|
107
|
+
borderRadius: Spacing.three,
|
|
108
|
+
},
|
|
109
|
+
externalPressable: {
|
|
110
|
+
flexDirection: 'row',
|
|
111
|
+
justifyContent: 'center',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
gap: Spacing.one,
|
|
114
|
+
marginLeft: Spacing.three,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { NativeTabs } from 'expo-router/unstable-native-tabs';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { useColorScheme } from 'react-native';
|
|
4
|
-
|
|
5
|
-
import { Colors } from '@/constants/theme';
|
|
6
|
-
|
|
7
|
-
export default function AppTabs() {
|
|
8
|
-
const scheme = useColorScheme();
|
|
9
|
-
const colors = Colors[scheme === 'unspecified' ? 'light' : scheme];
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<NativeTabs
|
|
13
|
-
backgroundColor={colors.background}
|
|
14
|
-
indicatorColor={colors.backgroundElement}
|
|
15
|
-
labelStyle={{ selected: { color: colors.text } }}>
|
|
16
|
-
<NativeTabs.Trigger name="index">
|
|
17
|
-
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
18
|
-
<NativeTabs.Trigger.Icon src={require('@/assets/images/tabIcons/home.png')} />
|
|
19
|
-
</NativeTabs.Trigger>
|
|
20
|
-
|
|
21
|
-
<NativeTabs.Trigger name="explore">
|
|
22
|
-
<NativeTabs.Trigger.Label>Explore</NativeTabs.Trigger.Label>
|
|
23
|
-
<NativeTabs.Trigger.Icon src={require('@/assets/images/tabIcons/explore.png')} />
|
|
24
|
-
</NativeTabs.Trigger>
|
|
25
|
-
</NativeTabs>
|
|
26
|
-
);
|
|
27
|
-
}
|