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.
- package/README.md +62 -0
- package/package.json +25 -0
- 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
|
+
}
|