gitshark

Clone repository

git clone https://git.vilip.de/git/vilip/taskflow.git
git clone git@git.vilip.de:vilip/taskflow.git

← Commits

๐Ÿ’„ Make tabs swipeable

c9434dc624c5c7dc6740622dc8a27ae8e1bff6e4 ยท vvilip ยท 2025-11-11T10:16:18Z

Changes

5 files changed, +118 -87

ADD app/(tabs)/_layout.swipe.tsx +0 -0
diff --git "a/app/\050tabs\051/_layout.swipe.tsx" "b/app/\050tabs\051/_layout.swipe.tsx"
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ "b/app/\050tabs\051/_layout.swipe.tsx"
MODIFY app/(tabs)/_layout.tsx +35 -87
diff --git "a/app/\050tabs\051/_layout.tsx" "b/app/\050tabs\051/_layout.tsx"
index b3e68af..5bf2851 100644
--- "a/app/\050tabs\051/_layout.tsx"
+++ "b/app/\050tabs\051/_layout.tsx"
@@ -1,93 +1,41 @@
1 -import { Tabs } from 'expo-router';
2 -import React from 'react';
3 -import { Ionicons } from '@expo/vector-icons';
4 -import { useSafeAreaInsets } from 'react-native-safe-area-context';
1 +import React, { useState } from 'react';
2 +import { Dimensions } from 'react-native';
3 +import { TabView, SceneMap } from 'react-native-tab-view';
4 +import InboxScreen from './index';
5 +import TodayScreen from './today';
6 +import CalendarScreen from './calendar';
7 +import ProjectsScreen from './projects';
8 +import SettingsScreen from './settings';
9 +import { SwipeableTabBar } from '@/components/swipeable-tab-bar';
5 10
6 -import { HapticTab } from '@/components/haptic-tab';
7 -import { Colors } from '@/constants/theme';
8 -import { useColorScheme } from '@/hooks/use-color-scheme';
11 +const renderScene = SceneMap({
12 + inbox: InboxScreen,
13 + today: TodayScreen,
14 + calendar: CalendarScreen,
15 + projects: ProjectsScreen,
16 + settings: SettingsScreen,
17 +});
9 18
10 19 export default function TabLayout() {
11 - const colorScheme = useColorScheme();
12 - const insets = useSafeAreaInsets();
20 + const [index, setIndex] = useState(0);
21 + const [routes] = useState([
22 + { key: 'inbox', title: 'Inbox' },
23 + { key: 'today', title: 'Today' },
24 + { key: 'calendar', title: 'Calendar' },
25 + { key: 'projects', title: 'Projects' },
26 + { key: 'settings', title: 'Settings' },
27 + ]);
28 +
29 + const initialLayout = { width: Dimensions.get('window').width };
13 30
14 31 return (
15 - <Tabs
16 - screenOptions={{
17 - tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
18 - headerShown: false,
19 - tabBarButton: HapticTab,
20 - tabBarStyle: {
21 - height: 60 + insets.bottom,
22 - paddingBottom: insets.bottom,
23 - paddingTop: 8,
24 - },
25 - }}>
26 - <Tabs.Screen
27 - name="index"
28 - options={{
29 - title: 'Inbox',
30 - tabBarIcon: ({ color, focused }) => (
31 - <Ionicons
32 - name={focused ? "mail" : "mail-outline"}
33 - size={24}
34 - color={color}
35 - />
36 - ),
37 - }}
38 - />
39 - <Tabs.Screen
40 - name="today"
41 - options={{
42 - title: 'Today',
43 - tabBarIcon: ({ color, focused }) => (
44 - <Ionicons
45 - name={focused ? "today" : "today-outline"}
46 - size={24}
47 - color={color}
48 - />
49 - ),
50 - }}
51 - />
52 - <Tabs.Screen
53 - name="calendar"
54 - options={{
55 - title: 'Calendar',
56 - tabBarIcon: ({ color, focused }) => (
57 - <Ionicons
58 - name={focused ? "calendar" : "calendar-outline"}
59 - size={24}
60 - color={color}
61 - />
62 - ),
63 - }}
64 - />
65 - <Tabs.Screen
66 - name="projects"
67 - options={{
68 - title: 'Projects',
69 - tabBarIcon: ({ color, focused }) => (
70 - <Ionicons
71 - name={focused ? "folder" : "folder-outline"}
72 - size={24}
73 - color={color}
74 - />
75 - ),
76 - }}
77 - />
78 - <Tabs.Screen
79 - name="settings"
80 - options={{
81 - title: 'Settings',
82 - tabBarIcon: ({ color, focused }) => (
83 - <Ionicons
84 - name={focused ? "settings" : "settings-outline"}
85 - size={24}
86 - color={color}
87 - />
88 - ),
89 - }}
90 - />
91 - </Tabs>
32 + <TabView
33 + navigationState={{ index, routes }}
34 + renderScene={renderScene}
35 + onIndexChange={setIndex}
36 + initialLayout={initialLayout}
37 + tabBarPosition="bottom"
38 + renderTabBar={props => <SwipeableTabBar {...props} />}
39 + />
92 40 );
93 -}
41 +}
\ No newline at end of file
ADD components/swipeable-tab-bar.tsx +54 -0
diff --git a/components/swipeable-tab-bar.tsx b/components/swipeable-tab-bar.tsx
new file mode 100644
index 0000000..0ac7fbc
--- /dev/null
+++ b/components/swipeable-tab-bar.tsx
@@ -0,0 +1,54 @@
1 +import React from 'react';
2 +import { View, TouchableOpacity, StyleSheet } from 'react-native';
3 +import { Ionicons } from '@expo/vector-icons';
4 +import { useSafeAreaInsets } from 'react-native-safe-area-context';
5 +import { ThemedText } from './themed-text';
6 +import { Colors } from '@/constants/theme';
7 +import { useColorScheme } from '@/hooks/use-color-scheme';
8 +
9 +const iconMap: { [key: string]: { focused: keyof typeof Ionicons.glyphMap; unfocused: keyof typeof Ionicons.glyphMap } } = {
10 + inbox: { focused: 'mail', unfocused: 'mail-outline' },
11 + today: { focused: 'today', unfocused: 'today-outline' },
12 + calendar: { focused: 'calendar', unfocused: 'calendar-outline' },
13 + projects: { focused: 'folder', unfocused: 'folder-outline' },
14 + settings: { focused: 'settings', unfocused: 'settings-outline' },
15 +};
16 +
17 +export function SwipeableTabBar({ navigationState, position, jumpTo }) {
18 + const colorScheme = useColorScheme();
19 + const colors = Colors[colorScheme];
20 + const insets = useSafeAreaInsets();
21 +
22 + return (
23 + <View style={[styles.tabBar, { height: 60 + insets.bottom, paddingBottom: insets.bottom }]}>
24 + {navigationState.routes.map((route, i) => {
25 + const isFocused = navigationState.index === i;
26 + const iconName = isFocused ? iconMap[route.key].focused : iconMap[route.key].unfocused;
27 + const color = isFocused ? colors.tint : colors.tabIconDefault;
28 +
29 + return (
30 + <TouchableOpacity
31 + key={route.key}
32 + onPress={() => jumpTo(route.key)}
33 + style={styles.tabItem}
34 + >
35 + <Ionicons name={iconName} size={24} color={color} />
36 + <ThemedText style={{ color, fontSize: 10 }}>{route.title}</ThemedText>
37 + </TouchableOpacity>
38 + );
39 + })}
40 + </View>
41 + );
42 +}
43 +
44 +const styles = StyleSheet.create({
45 + tabBar: {
46 + flexDirection: 'row',
47 + backgroundColor: 'transparent',
48 + },
49 + tabItem: {
50 + flex: 1,
51 + alignItems: 'center',
52 + justifyContent: 'center',
53 + },
54 +});
MODIFY package-lock.json +27 -0
diff --git a/package-lock.json b/package-lock.json
index c5b289f..7943ac8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34,9 +34,11 @@
34 34 "react-dom": "19.1.0",
35 35 "react-native": "0.81.5",
36 36 "react-native-gesture-handler": "~2.28.0",
37 + "react-native-pager-view": "^6.9.1",
37 38 "react-native-reanimated": "~4.1.1",
38 39 "react-native-safe-area-context": "~5.6.0",
39 40 "react-native-screens": "~4.16.0",
41 + "react-native-tab-view": "^4.2.0",
40 42 "react-native-web": "~0.21.0",
41 43 "react-native-worklets": "0.5.1",
42 44 "webdav": "^5.8.0"
@@ -10939,6 +10941,17 @@
10939 10941 "react-native": "*"
10940 10942 }
10941 10943 },
10944 + "node_modules/react-native-pager-view": {
10945 + "version": "6.9.1",
10946 + "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.9.1.tgz",
10947 + "integrity": "sha512-uUT0MMMbNtoSbxe9pRvdJJKEi9snjuJ3fXlZhG8F2vVMOBJVt/AFtqMPUHu9yMflmqOr08PewKzj9EPl/Yj+Gw==",
10948 + "license": "MIT",
10949 + "peer": true,
10950 + "peerDependencies": {
10951 + "react": "*",
10952 + "react-native": "*"
10953 + }
10954 + },
10942 10955 "node_modules/react-native-reanimated": {
10943 10956 "version": "4.1.3",
10944 10957 "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.3.tgz",
@@ -10995,6 +11008,20 @@
10995 11008 "react-native": "*"
10996 11009 }
10997 11010 },
11011 + "node_modules/react-native-tab-view": {
11012 + "version": "4.2.0",
11013 + "resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-4.2.0.tgz",
11014 + "integrity": "sha512-TUbh7Yr0tE/99t1pJQLbQ+4/Px67xkT7/r3AhfV+93Q3WoUira0Lx7yuKUP2C118doqxub8NCLERwcqsHr29nQ==",
11015 + "license": "MIT",
11016 + "dependencies": {
11017 + "use-latest-callback": "^0.2.4"
11018 + },
11019 + "peerDependencies": {
11020 + "react": ">= 18.2.0",
11021 + "react-native": "*",
11022 + "react-native-pager-view": ">= 6.0.0"
11023 + }
11024 + },
10998 11025 "node_modules/react-native-web": {
10999 11026 "version": "0.21.2",
11000 11027 "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
MODIFY package.json +2 -0
diff --git a/package.json b/package.json
index 7c88a19..b667cc6 100644
--- a/package.json
+++ b/package.json
@@ -37,9 +37,11 @@
37 37 "react-dom": "19.1.0",
38 38 "react-native": "0.81.5",
39 39 "react-native-gesture-handler": "~2.28.0",
40 + "react-native-pager-view": "^6.9.1",
40 41 "react-native-reanimated": "~4.1.1",
41 42 "react-native-safe-area-context": "~5.6.0",
42 43 "react-native-screens": "~4.16.0",
44 + "react-native-tab-view": "^4.2.0",
43 45 "react-native-web": "~0.21.0",
44 46 "react-native-worklets": "0.5.1",
45 47 "webdav": "^5.8.0"

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog