fast-navigation 0.1.0

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 (3) hide show
  1. package/README.md +62 -0
  2. package/package.json +25 -0
  3. package/src/index.js +264 -0
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # fast-navigation
2
+
3
+ Reusable drawer and floating quick-tabs navigation components for React Native apps.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install fast-navigation
9
+ ```
10
+
11
+ Install the React Navigation peer dependencies required by your app:
12
+
13
+ ```sh
14
+ npm install @react-navigation/native @react-navigation/drawer @react-navigation/native-stack react-native-safe-area-context react-native-screens react-native-gesture-handler react-native-reanimated
15
+ ```
16
+
17
+ `@expo/vector-icons` is included by this package so MaterialCommunityIcons are available out of the box.
18
+
19
+ ## Usage
20
+
21
+ ```jsx
22
+ import { NavigationContainer, createNavigationContainerRef } from '@react-navigation/native';
23
+ import {
24
+ FastDrawerNavigator,
25
+ FastQuickTabsBar,
26
+ FastStackNavigator,
27
+ QuickTabsVisibilityProvider,
28
+ } from 'fast-navigation';
29
+
30
+ const navigationRef = createNavigationContainerRef();
31
+
32
+ const drawerRoutes = [
33
+ { name: 'Home', component: HomeScreen, icon: 'home-outline' },
34
+ { name: 'Clients', component: ClientsScreen, icon: 'account-group-outline' },
35
+ ];
36
+
37
+ const stackRoutes = [
38
+ { name: 'ClientForm', component: ClientFormScreen },
39
+ ];
40
+
41
+ const tabs = [
42
+ { screen: 'Clients', icon: 'account-group-outline' },
43
+ { screen: 'Home', icon: 'home', center: true },
44
+ ];
45
+
46
+ function DrawerNavigator() {
47
+ return <FastDrawerNavigator routes={drawerRoutes} />;
48
+ }
49
+
50
+ export default function App() {
51
+ return (
52
+ <QuickTabsVisibilityProvider>
53
+ <NavigationContainer ref={navigationRef}>
54
+ <FastStackNavigator drawerComponent={DrawerNavigator} screens={stackRoutes} />
55
+ <FastQuickTabsBar navigationRef={navigationRef} tabs={tabs} />
56
+ </NavigationContainer>
57
+ </QuickTabsVisibilityProvider>
58
+ );
59
+ }
60
+ ```
61
+
62
+ Use `useQuickTabsVisibility().handleScroll` on scrollable screens to hide the tabs while scrolling down and show them when scrolling up.
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "fast-navigation",
3
+ "version": "0.1.0",
4
+ "description": "Reusable drawer and floating quick-tabs navigation components for React Native apps.",
5
+ "main": "src/index.js",
6
+ "keywords": [
7
+ "react-native",
8
+ "navigation",
9
+ "drawer",
10
+ "tabs",
11
+ "expo"
12
+ ],
13
+ "license": "MIT",
14
+ "dependencies": {
15
+ "@expo/vector-icons": "^15.0.3"
16
+ },
17
+ "peerDependencies": {
18
+ "@react-navigation/drawer": ">=7",
19
+ "@react-navigation/native": ">=7",
20
+ "@react-navigation/native-stack": ">=7",
21
+ "react": ">=19",
22
+ "react-native": ">=0.81",
23
+ "react-native-safe-area-context": ">=5"
24
+ }
25
+ }
package/src/index.js ADDED
@@ -0,0 +1,264 @@
1
+ import { MaterialCommunityIcons } from '@expo/vector-icons';
2
+ import { createDrawerNavigator } from '@react-navigation/drawer';
3
+ import { createNativeStackNavigator } from '@react-navigation/native-stack';
4
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
5
+ import { Animated, Platform, StyleSheet, TouchableOpacity, View } from 'react-native';
6
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
7
+
8
+ const Drawer = createDrawerNavigator();
9
+ const Stack = createNativeStackNavigator();
10
+
11
+ export const fastNavigationColors = {
12
+ border: '#DDEAE5',
13
+ primary: '#0D9A69',
14
+ primaryDark: '#006B49',
15
+ primarySoft: '#58CFAC',
16
+ shadow: '#0B3D2F',
17
+ surface: '#FFFFFF',
18
+ surfaceMuted: '#EAF4F0',
19
+ textMuted: '#657873',
20
+ };
21
+
22
+ const QuickTabsVisibilityContext = createContext({
23
+ visible: true,
24
+ showTabs: () => {},
25
+ hideTabs: () => {},
26
+ handleScroll: () => {},
27
+ });
28
+
29
+ export function QuickTabsVisibilityProvider({ children }) {
30
+ const [visible, setVisible] = useState(true);
31
+ const lastOffset = useRef(0);
32
+
33
+ const showTabs = useCallback(() => setVisible(true), []);
34
+ const hideTabs = useCallback(() => setVisible(false), []);
35
+
36
+ const handleScroll = useCallback((event) => {
37
+ const currentOffset = event.nativeEvent.contentOffset.y;
38
+ const diff = currentOffset - lastOffset.current;
39
+
40
+ if (currentOffset < 24 || diff < -8) {
41
+ setVisible(true);
42
+ } else if (diff > 8 && currentOffset > 80) {
43
+ setVisible(false);
44
+ }
45
+
46
+ lastOffset.current = currentOffset;
47
+ }, []);
48
+
49
+ const value = useMemo(
50
+ () => ({ visible, showTabs, hideTabs, handleScroll }),
51
+ [handleScroll, hideTabs, showTabs, visible]
52
+ );
53
+
54
+ return (
55
+ <QuickTabsVisibilityContext.Provider value={value}>
56
+ {children}
57
+ </QuickTabsVisibilityContext.Provider>
58
+ );
59
+ }
60
+
61
+ export function useQuickTabsVisibility() {
62
+ return useContext(QuickTabsVisibilityContext);
63
+ }
64
+
65
+ export function FastQuickTabsBar({
66
+ barHeight = 58,
67
+ colors = fastNavigationColors,
68
+ iconSize = 25,
69
+ centerIconSize = 30,
70
+ navigationRef,
71
+ onTabPress,
72
+ rootDrawerName = 'RootDrawer',
73
+ tabs = [],
74
+ }) {
75
+ const { visible } = useQuickTabsVisibility();
76
+ const insets = useSafeAreaInsets();
77
+ const translateY = useRef(new Animated.Value(0)).current;
78
+ const bottomInset = Math.max(insets.bottom, Platform.OS === 'android' ? 12 : 0);
79
+ const computedBarHeight = barHeight + bottomInset;
80
+ const styles = useMemo(() => createQuickTabsStyles(colors), [colors]);
81
+
82
+ useEffect(() => {
83
+ Animated.timing(translateY, {
84
+ duration: 180,
85
+ toValue: visible ? 0 : computedBarHeight + 26,
86
+ useNativeDriver: true,
87
+ }).start();
88
+ }, [computedBarHeight, translateY, visible]);
89
+
90
+ function handlePress(item) {
91
+ if (onTabPress) {
92
+ onTabPress(item);
93
+ return;
94
+ }
95
+
96
+ navigateToDrawerScreen(navigationRef, item.screen, rootDrawerName);
97
+ }
98
+
99
+ return (
100
+ <Animated.View pointerEvents="box-none" style={[styles.wrap, { transform: [{ translateY }] }]}>
101
+ <View
102
+ style={[
103
+ styles.tabBar,
104
+ { height: computedBarHeight, paddingBottom: bottomInset + 4 },
105
+ ]}
106
+ >
107
+ {tabs.map((item) => (
108
+ <TouchableOpacity
109
+ activeOpacity={0.78}
110
+ key={item.key ?? item.screen ?? item.icon}
111
+ onPress={() => handlePress(item)}
112
+ style={item.center ? styles.centerButtonWrap : styles.tabButton}
113
+ >
114
+ <View style={item.center ? styles.centerButton : styles.iconSlot}>
115
+ <MaterialCommunityIcons
116
+ name={item.icon}
117
+ size={item.center ? centerIconSize : iconSize}
118
+ color={item.center ? colors.surface : item.color ?? colors.textMuted}
119
+ />
120
+ </View>
121
+ </TouchableOpacity>
122
+ ))}
123
+ </View>
124
+ </Animated.View>
125
+ );
126
+ }
127
+
128
+ export function FastDrawerNavigator({
129
+ colors = fastNavigationColors,
130
+ routes = [],
131
+ screenOptions,
132
+ }) {
133
+ const defaultScreenOptions = useMemo(
134
+ () => ({
135
+ drawerActiveBackgroundColor: colors.surfaceMuted,
136
+ drawerActiveTintColor: colors.primary,
137
+ drawerInactiveTintColor: colors.textMuted,
138
+ drawerStyle: {
139
+ backgroundColor: colors.surface,
140
+ width: 288,
141
+ },
142
+ headerTintColor: colors.surface,
143
+ headerStyle: {
144
+ backgroundColor: colors.primaryDark,
145
+ },
146
+ headerTitleStyle: {
147
+ fontWeight: '700',
148
+ },
149
+ ...screenOptions,
150
+ }),
151
+ [colors, screenOptions]
152
+ );
153
+
154
+ return (
155
+ <Drawer.Navigator screenOptions={defaultScreenOptions}>
156
+ {routes.map((route) => (
157
+ <Drawer.Screen
158
+ component={route.component}
159
+ initialParams={route.initialParams}
160
+ key={route.name}
161
+ name={route.name}
162
+ options={{
163
+ drawerIcon: route.icon
164
+ ? ({ color, size }) => (
165
+ <MaterialCommunityIcons name={route.icon} color={color} size={size} />
166
+ )
167
+ : undefined,
168
+ ...route.options,
169
+ }}
170
+ />
171
+ ))}
172
+ </Drawer.Navigator>
173
+ );
174
+ }
175
+
176
+ export function FastStackNavigator({
177
+ drawerComponent,
178
+ drawerName = 'RootDrawer',
179
+ screenOptions = { headerShown: false },
180
+ screens = [],
181
+ }) {
182
+ return (
183
+ <Stack.Navigator screenOptions={screenOptions}>
184
+ <Stack.Screen name={drawerName} component={drawerComponent} />
185
+ {screens.map((screen) => (
186
+ <Stack.Screen
187
+ component={screen.component}
188
+ initialParams={screen.initialParams}
189
+ key={screen.name}
190
+ name={screen.name}
191
+ options={screen.options}
192
+ />
193
+ ))}
194
+ </Stack.Navigator>
195
+ );
196
+ }
197
+
198
+ export const SemaneroQuickTabsBar = FastQuickTabsBar;
199
+ export const SemaneroDrawerNavigator = FastDrawerNavigator;
200
+ export const SemaneroStackNavigator = FastStackNavigator;
201
+ export const semaneroNavigationColors = fastNavigationColors;
202
+
203
+ export function navigateToDrawerScreen(navigationRef, screen, rootDrawerName = 'RootDrawer') {
204
+ if (navigationRef?.isReady?.()) {
205
+ navigationRef.navigate(rootDrawerName, { screen });
206
+ }
207
+ }
208
+
209
+ function createQuickTabsStyles(colors) {
210
+ return StyleSheet.create({
211
+ wrap: {
212
+ bottom: 0,
213
+ left: 0,
214
+ position: 'absolute',
215
+ right: 0,
216
+ },
217
+ tabBar: {
218
+ alignItems: 'center',
219
+ backgroundColor: colors.surface,
220
+ borderTopLeftRadius: 26,
221
+ borderTopRightRadius: 26,
222
+ elevation: 20,
223
+ flexDirection: 'row',
224
+ justifyContent: 'space-around',
225
+ paddingHorizontal: 18,
226
+ paddingTop: 8,
227
+ shadowColor: colors.shadow,
228
+ shadowOffset: { width: 0, height: -8 },
229
+ shadowOpacity: 0.12,
230
+ shadowRadius: 18,
231
+ },
232
+ tabButton: {
233
+ alignItems: 'center',
234
+ height: 44,
235
+ justifyContent: 'center',
236
+ width: 50,
237
+ },
238
+ iconSlot: {
239
+ alignItems: 'center',
240
+ justifyContent: 'center',
241
+ },
242
+ centerButtonWrap: {
243
+ alignItems: 'center',
244
+ justifyContent: 'center',
245
+ top: -18,
246
+ width: 64,
247
+ },
248
+ centerButton: {
249
+ alignItems: 'center',
250
+ backgroundColor: colors.primarySoft,
251
+ borderColor: colors.surface,
252
+ borderRadius: 29,
253
+ borderWidth: 6,
254
+ elevation: 12,
255
+ height: 58,
256
+ justifyContent: 'center',
257
+ shadowColor: colors.shadow,
258
+ shadowOffset: { width: 0, height: 8 },
259
+ shadowOpacity: 0.28,
260
+ shadowRadius: 12,
261
+ width: 58,
262
+ },
263
+ });
264
+ }