Created
May 13, 2020 02:16
-
-
Save guschnwg/1e02f73b534149855c138fe94a6ce98e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import {StyleSheet, TouchableOpacity} from 'react-native' | |
import {useSafeArea} from 'react-native-safe-area-context' | |
import Icon from 'react-native-vector-icons/FontAwesome' | |
import {WIDTH} from '../../config/constants' | |
import Items from './Items' | |
const Menu: React.FC<Props> = ({open = false, setOpen}) => { | |
const insets = useSafeArea() | |
return ( | |
<> | |
<TouchableOpacity | |
activeOpacity={1} | |
style={{ | |
...styles.menu, | |
bottom: insets.bottom + 15, | |
}} | |
onPress={() => setOpen(!open)} | |
> | |
<Icon name="home" size={30} color="black" /> | |
</TouchableOpacity> | |
<Items open={open} onPress={() => setOpen(false)} /> | |
</> | |
) | |
} | |
const styles = StyleSheet.create({ | |
menu: { | |
position: 'absolute', | |
bottom: 0, | |
left: WIDTH / 2 - 30, | |
height: 60, | |
width: 60, | |
borderRadius: 30, | |
backgroundColor: 'yellow', | |
alignItems: 'center', | |
justifyContent: 'center', | |
zIndex: 2, | |
shadowColor: '#000', | |
shadowOffset: {width: 0, height: 2}, | |
shadowOpacity: 0.5, | |
shadowRadius: 5, | |
elevation: 5, | |
}, | |
}) | |
interface Props { | |
open: boolean | |
setOpen: (open: boolean) => void | |
} | |
export default Menu | |
-- | |
import React from 'react' | |
import Item from './Item' | |
const ITEMS = [ | |
{ | |
title: '1', | |
icon: 'home', | |
route: 'PollinationCreate', | |
}, | |
{ | |
title: '2', | |
icon: 'home', | |
route: 'PollinationCreate', | |
}, | |
{ | |
title: '3', | |
icon: 'home', | |
route: 'PollinationCreate', | |
}, | |
{ | |
title: '4', | |
icon: 'home', | |
route: 'PollinationCreate', | |
}, | |
{ | |
title: '5', | |
icon: 'home', | |
route: 'PollinationCreate', | |
}, | |
] | |
const getAngle = (position: number, angle: number) => | |
((90 + position * angle) * Math.PI) / 180 | |
const getPosition = (position: number, radius: number, angle: number) => { | |
const itemAngle = getAngle(position, angle) | |
return { | |
x: Math.cos(itemAngle) * radius, | |
y: Math.sin(itemAngle) * radius, | |
} | |
} | |
const Items: React.FC<Props> = ({open, onPress}) => { | |
const median = (ITEMS.length - 1) / 2 | |
return ( | |
<> | |
{ITEMS.map((item, i) => ( | |
<Item | |
key={i} | |
index={i} | |
items={ITEMS.length} | |
open={open} | |
title={item.title} | |
route={item.route} | |
position={getPosition(i - median, -80, 45)} | |
onPress={onPress} | |
/> | |
))} | |
</> | |
) | |
} | |
interface Props { | |
open: boolean | |
onPress: () => void | |
} | |
export default Items | |
-- | |
import React, {useEffect, useState} from 'react' | |
import {Animated, TouchableOpacity, Text, StyleSheet} from 'react-native' | |
import {useNavigation} from '@react-navigation/native' | |
import {useSafeArea} from 'react-native-safe-area-context' | |
import {WIDTH} from '../../config/constants' | |
const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity) | |
const getDelay = (open: boolean, items: number, index: number) => | |
open ? index * 50 : (items - index) * 50 | |
const getToValue = (open: boolean, position: {x: number; y: number}) => | |
open ? {scale: 1, transform: position} : {scale: 0, transform: {x: 0, y: 0}} | |
const Item: React.FC<Props> = ({ | |
index, | |
items, | |
open, | |
title, | |
route, | |
position, | |
onPress, | |
}) => { | |
const inset = useSafeArea() | |
const [translateValue] = useState(new Animated.ValueXY({x: 0, y: 0})) | |
const [scaleValue] = useState(new Animated.Value(0)) | |
const {navigate} = useNavigation() | |
useEffect(() => { | |
const toValue = getToValue(open, position) | |
const delay = getDelay(open, items, index) | |
Animated.parallel([ | |
Animated.timing(translateValue, { | |
toValue: toValue.transform, | |
duration: 150, | |
delay, | |
useNativeDriver: true, | |
}), | |
Animated.timing(scaleValue, { | |
toValue: toValue.scale, | |
duration: 300, | |
delay, | |
useNativeDriver: true, | |
}), | |
]).start() | |
}, [translateValue, scaleValue, index, items, open, position]) | |
return ( | |
<AnimatedTouchable | |
activeOpacity={1} | |
style={{ | |
...styles.item, | |
bottom: inset.bottom + 20, | |
transform: [ | |
{ | |
translateX: translateValue.x, | |
}, | |
{ | |
translateY: translateValue.y, | |
}, | |
{ | |
scaleX: scaleValue, | |
}, | |
{ | |
scaleY: scaleValue, | |
}, | |
], | |
}} | |
onPress={() => { | |
navigate(route) | |
onPress() | |
}} | |
> | |
<Text>{title}</Text> | |
</AnimatedTouchable> | |
) | |
} | |
const styles = StyleSheet.create({ | |
item: { | |
borderRadius: 20, | |
position: 'absolute', | |
backgroundColor: 'yellow', | |
height: 40, | |
width: 40, | |
left: WIDTH / 2 - 20, | |
zIndex: 1, | |
alignItems: 'center', | |
justifyContent: 'center', | |
shadowColor: '#000', | |
shadowOffset: {width: 0, height: 2}, | |
shadowOpacity: 0.3, | |
shadowRadius: 5, | |
elevation: 3, | |
}, | |
}) | |
interface Props { | |
index: number | |
items: number | |
open: boolean | |
title: string | |
route: string | |
position: {x: number; y: number} | |
onPress: () => void | |
} | |
export default Item |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment