Skip to content

Instantly share code, notes, and snippets.

@topherPedersen
Last active July 6, 2022 14:05
Show Gist options
  • Save topherPedersen/caf43bdce394be96a3f3594f615f2588 to your computer and use it in GitHub Desktop.
Save topherPedersen/caf43bdce394be96a3f3594f615f2588 to your computer and use it in GitHub Desktop.
Custom Side-Menu / Drawer in React-Native (Demo/Prototype)
import React, { useState } from 'react';
import {
View,
SafeAreaView,
Dimensions,
Image,
} from 'react-native';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
useDerivedValue,
runOnJS,
} from 'react-native-reanimated';
const windowWidth = Dimensions.get('window').width;
const windowHeight = Dimensions.get('window').height;
const drawerWidth = windowWidth * 0.875;
const closedLeftPosition = drawerWidth * -1;
const openLeftPosition = closedLeftPosition + drawerWidth;
function Drawer(props) {
console.log(`props.drawerLeftPosition.value: ${props.drawerLeftPosition.value}`);
const animatedStyle = useAnimatedStyle(() => {
return {
position: 'absolute',
height: windowHeight,
width: drawerWidth,
top: 0,
left: props.drawerLeftPosition.value,
backgroundColor: 'white',
borderRadius: 25,
zIndex: 2000,
};
});
return (
<Animated.View style={animatedStyle}>
{/* Drawer Contents Go Here */}
</Animated.View>
);
}
function DrawerBackground(props) {
if (!props.drawerVisible) {
return null;
}
const animatedStyle = useAnimatedStyle(() => {
return {
position: 'absolute',
height: windowHeight ,
width: windowWidth,
top: 0,
right: 0,
backgroundColor: 'black',
opacity: 0.50,
zIndex: 1000,
};
});
return (
<Animated.View style={animatedStyle}>
{/* Drawer Contents Go Here */}
</Animated.View>
);
}
function App() {
const panGestureXPosition = useSharedValue(-1);
const panGestureTranslationXPosition = useSharedValue(-1);
const listenForOpenDrawerGesture = useSharedValue(true);
const openDrawerGestureDetected = useSharedValue(false);
const leaveDrawerOpen = useSharedValue(false);
const closeDrawerGestureDetected = useSharedValue(false);
const [ drawerVisible, setDrawerVisible ] = useState(false);
const drawerLeftPosition = useDerivedValue(() => {
if (openDrawerGestureDetected.value) {
return panGestureTranslationXPosition.value <= drawerWidth ? closedLeftPosition + panGestureTranslationXPosition.value : closedLeftPosition + drawerWidth;
} else if (closeDrawerGestureDetected.value) {
// EDGE CASE: Don't let user start dragging close, then start dragging back open PAST the maximum open position
if (openLeftPosition + panGestureTranslationXPosition.value >= openLeftPosition) {
return openLeftPosition;
} else if (openLeftPosition + panGestureTranslationXPosition.value >= closedLeftPosition) {
return openLeftPosition + panGestureTranslationXPosition.value
} else {
return closedLeftPosition;
}
}else if (leaveDrawerOpen.value) {
return openLeftPosition;
} else {
return closedLeftPosition;
}
});
const gesture = Gesture.Pan()
.onBegin(() => {
listenForOpenDrawerGesture.value = true;
openDrawerGestureDetected.value = false;
})
.onUpdate((e) => {
panGestureXPosition.value = e.x;
panGestureTranslationXPosition.value = e.translationX;
const swipeRight = e.velocityX > 0;
const swipeLeft = e.velocityX < 0;
if (swipeRight) {
if (listenForOpenDrawerGesture.value && !openDrawerGestureDetected.value && e.x <= 89) {
console.log(`OPEN DRAWER GESTURE DETECTED!`);
runOnJS(setDrawerVisible)(true);
openDrawerGestureDetected.value = true;
listenForOpenDrawerGesture.value = false;
} else if (listenForOpenDrawerGesture.value && !openDrawerGestureDetected.value && e.x > 89) {
console.log(`Open Drawer Gesture NOT Detected :(`);
openDrawerGestureDetected.value = false;
listenForOpenDrawerGesture.value = false;
} else {
console.log(`ELSE: listenForOpenDrawerGesture.value: ${listenForOpenDrawerGesture.value} openDrawerGestureDetected.value: ${openDrawerGestureDetected.value} e.x: ${e.x}`);
}
} else if (swipeLeft) {
if (leaveDrawerOpen.value) {
console.log(`***CLOSE*** DRAWER GESTURE DETECTED!`);
closeDrawerGestureDetected.value = true;
leaveDrawerOpen.value = false;
}
}
})
.onEnd(() => {
const drawerHalfOpened = drawerLeftPosition.value >= closedLeftPosition + (drawerWidth / 2);
if (openDrawerGestureDetected.value && drawerHalfOpened) {
leaveDrawerOpen.value = true;
} else if (openDrawerGestureDetected.value && !drawerHalfOpened) {
leaveDrawerOpen.value = false;
runOnJS(setDrawerVisible)(false);
} else if (closeDrawerGestureDetected.value && drawerHalfOpened) {
leaveDrawerOpen.value = true;
} else if (closeDrawerGestureDetected.value && !drawerHalfOpened) {
leaveDrawerOpen.value = false;
runOnJS(setDrawerVisible)(false);
}
panGestureXPosition.value = -1;
listenForOpenDrawerGesture.value = true;
openDrawerGestureDetected.value = false;
closeDrawerGestureDetected.value = false;
console.log(`Gesture.Pan().onEnd()`);
});
return (
<GestureDetector gesture={gesture}>
<SafeAreaView style={{ flex: 1, backgroundColor: 'white' }}>
<Image
source={require('./images/dashboard.png')}
style={{ width: windowWidth, height: windowHeight, zIndex: 1 }}
/>
<Drawer
drawerLeftPosition={drawerLeftPosition}
/>
<DrawerBackground
openDrawerGestureDetected={openDrawerGestureDetected}
closeDrawerGestureDetected={closeDrawerGestureDetected}
leaveDrawerOpen={leaveDrawerOpen}
drawerVisible={drawerVisible}
/>
</SafeAreaView>
</GestureDetector>
);
}
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment